Skip to content
Shining Ying edited this page Jun 17, 2020 · 19 revisions

Concept and Ideas behind the Template

"universal_tool_template.py" is created to ease the repetition steps during tool develop, as lot of tools share the major structure in codes, and only UI and Functions are different.

By automate the window, menu, connection, UI element management into a template, developer can focus on writing UI structure and function code. Hiding those cross-application, cross-platform things behind the template.

Advantage of the Template

The main advantage is the standardization enforced automatically enforced in the whole development process, from naming convention to code structure to the way to write codes, just like Python language itself.

Thus, it enable people in the team use the same way to write code, and allow other people easily pickup someone's project.

Also, it allow to train new people into development.

The Future of the Template

The idea of automation in tool development never stop here, eventually, in future, the UI structure text format syntax will auto-handling cross-framework things as well, enable you use the same UI creation code to seamlessly using another python UI library, like Kivy, wxPython, by auto loading the corresponding quickUI() function after detection.

The Extension of the Template

The cross-application, cross-platform, cross-framework are not the end as well, the concept will eventually go cross-language, which enables you to automatically convert your UI code into another language, or load as a UI description file, like xamarin xaml, Qt qml.

However, as each language is designed to handle different task, so most likely you won't likely re-use those UI code or function code.

The main idea is the quick UI and Function creation process and anything else automated in-between powered by good enforced naming convention integrated. Therefore, people can quickly adapt to another difference language development workflow, like html5, php.

log

  • refer to the readme

Template Features

  1. Template version indicator
tpl_ver = 10.0
  1. Mode Detection
hostMode = "": (maya, houdini, nuke, blender, desktop)
qtMode = 0 : (0: PySide; 1 : PyQt, 2: PySide2, 3: PyQt5)
pyMode = '' : ('2.7', '3.6')
osMode = 'other' : ('win', 'mac', 'linux')
  1. module preload
sys, os, glob, partial, json, cPickle, re, ctypes, subprocess
  1. template class
QtWidgets.QMainWindow
  1. initial built-in parameter
class UniversalToolUI(super_class): 
    def __init__(self, parent=None, mode=0):
  1. built-in attribute
self.version = "0.1"
self.log = 'no version log in user class'
self.help = 'no help guide in user class'
self.uiList={} # for ui obj storage
self.memoData = {} # key based variable data storage
# file or app location
self.location = sys.executable
self.location = os.path.realpath(sys.modules[self.__class__.__module__].__file__)
# app icon
self.name = self.__class__.__name__
self.iconPath = os.path.join(os.path.dirname(self.location),'icons',self.name+'.png')
self.iconPix = QtGui.QPixmap(self.iconPath)
self.icon = QtGui.QIcon(self.iconPath)
self.fileType='.{0}_EXT'.format(self.name)
# qui function short naming convention definition
self.qui_core_dict = {
    'vbox': 'QVBoxLayout','hbox':'QHBoxLayout','grid':'QGridLayout', 'form':'QFormLayout',
    'split': 'QSplitter', 'grp':'QGroupBox', 'tab':'QTabWidget',
    'btn':'QPushButton', 'btnMsg':'QPushButton', 'label':'QLabel', 'input':'QLineEdit', 'check':'QCheckBox', 'choice':'QComboBox',
    'txt': 'QTextEdit',
    'list': 'QListWidget', 'tree': 'QTreeWidget', 'table': 'QTableWidget',
    'space': 'QSpacerItem',
    'menu' : 'QMenu', 'menubar' : 'QMenuBar',
}
self.qui_user_dict = {}
  1. Style setting
QtWidgets.QApplication.setStyle(QtWidgets.QStyleFactory.create('Cleanlooks'))
self.setStyleSheet("QLineEdit:disabled{background-color: gray;}")
  1. Menu default
self.uiList['help_menu'] : (helpHostMode_atnNone, helpPyMode_atnNone, helpQtMode_atnNone, helpTemplate_atnNone, helpGuide_atnMsg, helpLog_atnMsg)
message : helpGuide_msg, helpLog_msg
  1. Title default
self.setWindowTitle(self.name + " - v" + self.version + " - host: " + hostMode)
self.setWindowIcon(self.icon)
self.drag_position=QtGui.QCursor.pos()
  1. Core Functions
# short syntax UI generation
def qui(self, ui_list_string, parent_ui_string='', insert_opt='')
# setup main_layout for UI
def setupUI(self, layout='grid')
# link action, button automatically to function with same prefix
def Establish_Connections(self)
# (overideable) a empty default function for un-connected button and action
def default_action(self, ui_name)
# default message function for un-connectied _btnMsg button and _atnMsg action
def default_message(self, ui_name)
UI element qui() syntax
QVBoxLayout self.qui('my_vbox')
self.qui('my_layout;vbox')
QHBoxLayout self.qui('my_hbox')
self.qui('my_layout;hbox')
QGridLayout self.qui('my_grid')
self.qui('my_layout;grid')
self.qui('box_btn;Box | sphere_btn;Sphere | ring_btn;Ring', 'my_layout;grid', 'h')
create and insert those 3 buttons as horizontal row in the grid
self.qui('box_btn;Box | sphere_btn;Sphere | ring_btn;Ring', 'my_layout;grid', 'h')
self.qui('box2_btn;Box2 | sphere2_btn;Sphere2 | ring2_btn;Ring2', 'my_layout', 'h')
create 2 rows of button, first row (Box,Sphere,Rig), second row (Box2,Sphere2,Ring2)
self.qui('cat_btn;Cat | dog_btn;Dog | pig_btn;Pig', 'pet_layout;grid', 'v')
self.qui('cat2_btn;Cat2 | dog2_btn;Dog2 | pig2_btn;Pig2', 'pet_layout', 'v')
create 2 column of button, first column (cat,dog,pig), second column (cat2,dog2,pig2)
QFormLayout self.qui('my_form')
self.qui('my_layout;form')
self.qui('name_input@Name:;John | email_input@Email:;test@test.com', 'entry_form')
create a name input with label "Name:" and default text "John", and then create email input with label "Email" and default text "test@test.com" inside entry_form form layout
QSplitter self.qui('user_layout | info_layout', 'my_split;h')
self.qui('user_grp | info_tab', 'my_split;h')
put user UI and info UI side by side in split,
child can be either a layout or widget
QGroupBox self.qui('user2_btn;User2 | info2_btn;Info2', 'my_grp;vbox;Personal Data')
put user UI and info UI inside "my_grp" group box and with internal vbox layout and title as "Personal Data"
Note:no ,() in title
QWidget self.qui('user_label;Name | user_input', 'user_widget;vbox')
like Groupbox widget, but without a title, useful for hide show a set of UIs
QTabWidget self.qui('client_layout | product_layout', 'database_tab;h', '(Client,Product)')
put client UI and product UI into database_tab tabwidget, as horizontal tab, with title as "Client", "Product", UI can be either widget or layout
QPushButton self.qui('my_btn;Submit')
create pushbutton with title "Submit"
self.qui('my_btnMsg;Info')
create pushbutton with title "Info" with automatically pop up a dialog show text string stored in self.uiList['my_msg']
QLabel self.qui('info_label;Please select all objects')
a label with text "Please select all objects"
QLineEdit self.qui('user_input;Your Email')
a line input with default text "Your Email"
QSpinBox self.qui('user_spin')
a integer input
QCheckBox self.qui('testOnly_check;Run as Test Only')
create a check box with title "Run as Test Only"
QComboBox self.qui('objectType_choice;(Box,Sphere,Ring)')
create a drop down list with option "Box", "Sphere", "Ring"
QTextEdit self.qui('comment_txt;Please write details here')
create a text area with default text "Please write details here"
QListWidget self.qui('name_list')
note, list has no header and single column
QTreeWidget self.qui('file_tree;(Name,Path)')
create a tree widget with column names as "Name", "Path"
QTableWidget self.qui('data_table;(Name,Email,Phone)')
create a data table with column name as "Name", "Email", "Phone"
QSpacerItem self.qui('user_space;(100,30,4,3)')
create a space item with policy expanding horizontally and normal vertically
# 0 = fixed; 1 > min; 2 < max; 3 = prefered; 4 = <expanding>; 5 = expanding> Aggresive; 6=4 ignored size input
  1. QLineEdit input check and get and set functions
def input_text(self, input_name, msg=None)
def input_int(self, input_name, min=None, max=None, msg=None)
input_float(self, input_name, min=None, max=None, msg=None)
input_choice(self, ui_name)
output_text(self, ui_name, text)
  1. Data File (json, binary) and Text File read and write function
readDataFile(self,file,binary=0) # json, pickle binary
writeDataFile(self,data,file,binary=0)  # json, pickle binary
readTextFile(self, file)
writeTextFile(self, txt, file)
  1. UI language translation creation, loading, exporting functions
def loadLang(self)
    self.memoData['lang']={}
    self.memoData['lang']['default']={}
    self.memoData['lang']['default'][ui_name] = 'ui_text'
 
    self.uiList['language_menu'] : (langDefault_atnLang, )
 
    self.memoData['lang'][ langName ] = self.readFileData( filePath )
    self.uiList[langName+'_atnLang']
 
    self.uiList['langExport_atnLang']
 
def setLang(self, langName)
def exportLang(self)
    self.writeDataFile( self.memoData['lang']['default'], file )
  1. Menu generation function
# create top menu items, ui_names can be ['file_menu', 'setting_menu'] or 'file_menu | setting_menu'
def quickMenu(self, ui_names)
    menubar = self.menuBar()
    menubar.addMenu(self.uiList[uiName])
# create submenu item
def quickMenuAction(self, objName, title, tip, icon, menuObj)
  1. UI generation function
# generate layout
def quickLayout(self, type, ui_name="")
# generate UI
def quickUI(self, part_list, parentObject="", insert_opt="")
# generate splitter holder
quickSplitUI(self, name, part_list, type)
    return self.uiList[name]
# generate tab holder
def quickTabUI(self, name, tab_list, tab_names)
    return self.uiList[name]
# generate group holder
def quickGrpUI(self, ui_name, ui_label, ui_layout)
    return [self.uiList[ui_name], ui_layout]
  1. UI property function
# 0 = fixed; 1 > min; 2 < max; 3 = prefered; 4 = <expanding>; 5 = expanding> Aggresive; 6=4 ignored size input
def quickPolicy(self, ui_list, w, h)
  1. Feedback UI function
def quickInfo(self, info)
    self.statusBar().showMessage(info)
def quickMsg(self, msg)
    tmpMsg = QtWidgets.QMessageBox()
def quickMsgAsk(self, msg, mode=0, choice=[])
    modeOpt = (QtWidgets.QLineEdit.Normal, QtWidgets.QLineEdit.NoEcho, QtWidgets.QLineEdit.Password, QtWidgets.QLineEdit.PasswordEchoOnEdit)
    txt, ok = QtWidgets.QInputDialog.getText
    txt, ok = QtWidgets.QInputDialog.getItem
def quickFileAsk(self, type, ext=None)
    return file
def quickFolderAsk(self)
def openFolder(self, folderPath)
  1. maya UI and Qt UI conversion
def mui_to_qt(self, mui_name)
def qt_to_mui(self, qt_obj)
  1. TreeWidget Process Functions
def path_pattern_to_task(self, path_pattern)
def getPathChild(self, scanPath, pattern='', isfile=0)
def DirToData(self, scanPath, task_config, pattern_config, currentTag='')
def DirToTree(self, cur_tree, parentNode, scanPath, task_config, pattern_config)
def TreeToData(self, tree, cur_node)
def DataToTree(self, tree, cur_node, data, filter='')
def DataChildCheck(self, DataChild, pattern)
def TreeExport(self, tree_name, file)
def TreeImport(self, tree_name, file)

UserClassUI features

  1. Import Other Qt module example
QtNetwork = getattr(__import__(qtModeList[qtMode], fromlist=['QtNetwork']), 'QtNetwork')
  1. optional more input userClass initial parameter
class UserClassUI(UniversalToolUI):
    def __init__(self, parent=None, mode=0):
        UniversalToolUI.__init__(self, parent)
  1. optional more qui() _subfix definition
self.qui_user_dict = {} # e.g: 'edit': 'LNTextEdit',
  1. UI initialize steps
self.setupStyle()
if isinstance(self, QtWidgets.QMainWindow):
    self.setupMenu()
self.setupWin()
self.setupUI()
self.Establish_Connections()
self.loadData()
self.loadLang()
  1. user menu + template menu
def setupMenu(self)
    self.quickMenu('file_menu;&File | setting_menu;&Setting | help_menu;&Help')
    super(self.__class__,self).setupMenu()
  1. template + user window setting
def setupWin(self)
    super(self.__class__,self).setupWin()
    self.setGeometry(500, 300, 250, 110)
  1. template + user UI creation
# template main_layout
super(self.__class__,self).setupUI('hbox')
# user UI
self.qui('box_btn;Box | sphere_btn;Sphere', 'main_layout')
# layout margin setup
keep_margin_layout = ['main_layout']
# optional UI display setting
self.quickInfo('Ready')
self.statusBar().hide()
  1. template + user UI function connection
def Establish_Connections(self)
    super(self.__class__,self).Establish_Connections()
    # custom ui response
    # shortcut connection
    self.hotkey = {}
  1. load and export Config and Data function
def loadData(self)
    self.memoData['config'] = {}
def closeEvent(self, event)
def dict_merge(self, default_dict, extra_dict, addKey=0)
def exportConfig_action(self)
def importConfig_action(self)
  1. user UI response function
def process_action(self)

main Instance feature

  1. single UI for both maya,nuke and desktop (win)