├── docs ├── .nojekyll ├── objects.inv ├── _static │ ├── up.png │ ├── down.png │ ├── file.png │ ├── minus.png │ ├── plus.png │ ├── comment.png │ ├── ajax-loader.gif │ ├── up-pressed.png │ ├── comment-close.png │ ├── down-pressed.png │ ├── comment-bright.png │ ├── pygments.css │ ├── classic.css │ └── sidebar.js ├── _sources │ ├── modules.rst.txt │ ├── qualityAssurance.install.rst.txt │ ├── qualityAssurance.ui.utils.rst.txt │ ├── qualityAssurance.utils.qa.rst.txt │ ├── qualityAssurance.checks.uv.rst.txt │ ├── qualityAssurance.ui.widgets.rst.txt │ ├── qualityAssurance.ui.window.rst.txt │ ├── qualityAssurance.utils.api.rst.txt │ ├── qualityAssurance.utils.path.rst.txt │ ├── qualityAssurance.utils.skin.rst.txt │ ├── qualityAssurance.utils.undo.rst.txt │ ├── qualityAssurance.checks.scene.rst.txt │ ├── qualityAssurance.collections.rst.txt │ ├── qualityAssurance.checks.geometry.rst.txt │ ├── qualityAssurance.checks.rigging.rst.txt │ ├── qualityAssurance.checks.shaders.rst.txt │ ├── qualityAssurance.checks.skinning.rst.txt │ ├── qualityAssurance.checks.textures.rst.txt │ ├── qualityAssurance.utils.animation.rst.txt │ ├── qualityAssurance.utils.reference.rst.txt │ ├── qualityAssurance.checks.animation.rst.txt │ ├── qualityAssurance.checks.modelling.rst.txt │ ├── qualityAssurance.utils.decorators.rst.txt │ ├── qualityAssurance.checks.renderStats.rst.txt │ ├── qualityAssurance.checks.renderLayers.rst.txt │ ├── qualityAssurance.ui.rst.txt │ ├── qualityAssurance.rst.txt │ ├── index.rst.txt │ ├── qualityAssurance.utils.rst.txt │ └── qualityAssurance.checks.rst.txt ├── .buildinfo ├── search.html ├── qualityAssurance.install.html ├── qualityAssurance.utils.undo.html ├── qualityAssurance.ui.html ├── qualityAssurance.utils.decorators.html ├── qualityAssurance.checks.textures.html ├── qualityAssurance.utils.reference.html ├── qualityAssurance.utils.animation.html ├── qualityAssurance.ui.window.html ├── qualityAssurance.utils.html ├── qualityAssurance.ui.utils.html ├── qualityAssurance.checks.uv.html ├── qualityAssurance.checks.skinning.html ├── qualityAssurance.utils.api.html ├── qualityAssurance.collections.html ├── qualityAssurance.checks.rigging.html ├── qualityAssurance.checks.shaders.html ├── modules.html ├── qualityAssurance.checks.renderLayers.html ├── qualityAssurance.utils.skin.html └── qualityAssurance.utils.path.html ├── .gitignore ├── scripts ├── qualityAssurance │ ├── ui │ │ ├── __init__.py │ │ ├── window.py │ │ └── utils.py │ ├── utils │ │ ├── __init__.py │ │ ├── reference.py │ │ ├── undo.py │ │ ├── animation.py │ │ ├── decorators.py │ │ ├── api.py │ │ ├── path.py │ │ ├── skin.py │ │ └── qa.py │ ├── checks │ │ ├── textures.py │ │ ├── rigging.py │ │ ├── __init__.py │ │ ├── uv.py │ │ ├── shaders.py │ │ ├── renderStats.py │ │ ├── modelling.py │ │ └── skinning.py │ ├── install.py │ ├── collections │ │ └── __init__.py │ └── __init__.py └── userSetup.py ├── icons ├── QA_fix.png └── QA_icon.png ├── qualityAssurance.mod └── README.md /docs/.nojekyll: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | *.py[cod] 3 | -------------------------------------------------------------------------------- /scripts/qualityAssurance/ui/__init__.py: -------------------------------------------------------------------------------- 1 | from .window import show 2 | -------------------------------------------------------------------------------- /scripts/qualityAssurance/utils/__init__.py: -------------------------------------------------------------------------------- 1 | from .qa import QualityAssurance 2 | -------------------------------------------------------------------------------- /docs/objects.inv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robertjoosten/maya-quality-assurance/HEAD/docs/objects.inv -------------------------------------------------------------------------------- /icons/QA_fix.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robertjoosten/maya-quality-assurance/HEAD/icons/QA_fix.png -------------------------------------------------------------------------------- /icons/QA_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robertjoosten/maya-quality-assurance/HEAD/icons/QA_icon.png -------------------------------------------------------------------------------- /docs/_static/up.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robertjoosten/maya-quality-assurance/HEAD/docs/_static/up.png -------------------------------------------------------------------------------- /docs/_static/down.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robertjoosten/maya-quality-assurance/HEAD/docs/_static/down.png -------------------------------------------------------------------------------- /docs/_static/file.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robertjoosten/maya-quality-assurance/HEAD/docs/_static/file.png -------------------------------------------------------------------------------- /docs/_static/minus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robertjoosten/maya-quality-assurance/HEAD/docs/_static/minus.png -------------------------------------------------------------------------------- /docs/_static/plus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robertjoosten/maya-quality-assurance/HEAD/docs/_static/plus.png -------------------------------------------------------------------------------- /docs/_static/comment.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robertjoosten/maya-quality-assurance/HEAD/docs/_static/comment.png -------------------------------------------------------------------------------- /docs/_static/ajax-loader.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robertjoosten/maya-quality-assurance/HEAD/docs/_static/ajax-loader.gif -------------------------------------------------------------------------------- /docs/_static/up-pressed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robertjoosten/maya-quality-assurance/HEAD/docs/_static/up-pressed.png -------------------------------------------------------------------------------- /docs/_static/comment-close.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robertjoosten/maya-quality-assurance/HEAD/docs/_static/comment-close.png -------------------------------------------------------------------------------- /docs/_static/down-pressed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robertjoosten/maya-quality-assurance/HEAD/docs/_static/down-pressed.png -------------------------------------------------------------------------------- /docs/_static/comment-bright.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robertjoosten/maya-quality-assurance/HEAD/docs/_static/comment-bright.png -------------------------------------------------------------------------------- /docs/_sources/modules.rst.txt: -------------------------------------------------------------------------------- 1 | qualityAssurance 2 | ================ 3 | 4 | .. toctree:: 5 | :maxdepth: 4 6 | 7 | qualityAssurance 8 | -------------------------------------------------------------------------------- /scripts/userSetup.py: -------------------------------------------------------------------------------- 1 | from maya import utils 2 | 3 | import qualityAssurance.install 4 | utils.executeDeferred(qualityAssurance.install.shelf) 5 | -------------------------------------------------------------------------------- /docs/_sources/qualityAssurance.install.rst.txt: -------------------------------------------------------------------------------- 1 | qualityAssurance\.install module 2 | ================================ 3 | 4 | .. automodule:: qualityAssurance.install 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | -------------------------------------------------------------------------------- /docs/_sources/qualityAssurance.ui.utils.rst.txt: -------------------------------------------------------------------------------- 1 | qualityAssurance\.ui\.utils module 2 | ================================== 3 | 4 | .. automodule:: qualityAssurance.ui.utils 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | -------------------------------------------------------------------------------- /docs/_sources/qualityAssurance.utils.qa.rst.txt: -------------------------------------------------------------------------------- 1 | qualityAssurance\.utils\.qa module 2 | ================================== 3 | 4 | .. automodule:: qualityAssurance.utils.qa 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | -------------------------------------------------------------------------------- /docs/_sources/qualityAssurance.checks.uv.rst.txt: -------------------------------------------------------------------------------- 1 | qualityAssurance\.checks\.uv module 2 | =================================== 3 | 4 | .. automodule:: qualityAssurance.checks.uv 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | -------------------------------------------------------------------------------- /docs/_sources/qualityAssurance.ui.widgets.rst.txt: -------------------------------------------------------------------------------- 1 | qualityAssurance\.ui\.widgets module 2 | ==================================== 3 | 4 | .. automodule:: qualityAssurance.ui.widgets 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | -------------------------------------------------------------------------------- /docs/_sources/qualityAssurance.ui.window.rst.txt: -------------------------------------------------------------------------------- 1 | qualityAssurance\.ui\.window module 2 | =================================== 3 | 4 | .. automodule:: qualityAssurance.ui.window 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | -------------------------------------------------------------------------------- /docs/_sources/qualityAssurance.utils.api.rst.txt: -------------------------------------------------------------------------------- 1 | qualityAssurance\.utils\.api module 2 | =================================== 3 | 4 | .. automodule:: qualityAssurance.utils.api 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | -------------------------------------------------------------------------------- /docs/_sources/qualityAssurance.utils.path.rst.txt: -------------------------------------------------------------------------------- 1 | qualityAssurance\.utils\.path module 2 | ==================================== 3 | 4 | .. automodule:: qualityAssurance.utils.path 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | -------------------------------------------------------------------------------- /docs/_sources/qualityAssurance.utils.skin.rst.txt: -------------------------------------------------------------------------------- 1 | qualityAssurance\.utils\.skin module 2 | ==================================== 3 | 4 | .. automodule:: qualityAssurance.utils.skin 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | -------------------------------------------------------------------------------- /docs/_sources/qualityAssurance.utils.undo.rst.txt: -------------------------------------------------------------------------------- 1 | qualityAssurance\.utils\.undo module 2 | ==================================== 3 | 4 | .. automodule:: qualityAssurance.utils.undo 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | -------------------------------------------------------------------------------- /docs/.buildinfo: -------------------------------------------------------------------------------- 1 | # Sphinx build info version 1 2 | # This file hashes the configuration used when building these files. When it is not found, a full rebuild will be done. 3 | config: f315c9ff75ecd54364139830171b9671 4 | tags: 645f666f9bcd5a90fca523b33c5a78b7 5 | -------------------------------------------------------------------------------- /docs/_sources/qualityAssurance.checks.scene.rst.txt: -------------------------------------------------------------------------------- 1 | qualityAssurance\.checks\.scene module 2 | ====================================== 3 | 4 | .. automodule:: qualityAssurance.checks.scene 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | -------------------------------------------------------------------------------- /docs/_sources/qualityAssurance.collections.rst.txt: -------------------------------------------------------------------------------- 1 | qualityAssurance\.collections package 2 | ===================================== 3 | 4 | .. automodule:: qualityAssurance.collections 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | 9 | -------------------------------------------------------------------------------- /docs/_sources/qualityAssurance.checks.geometry.rst.txt: -------------------------------------------------------------------------------- 1 | qualityAssurance\.checks\.geometry module 2 | ========================================= 3 | 4 | .. automodule:: qualityAssurance.checks.geometry 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | -------------------------------------------------------------------------------- /docs/_sources/qualityAssurance.checks.rigging.rst.txt: -------------------------------------------------------------------------------- 1 | qualityAssurance\.checks\.rigging module 2 | ======================================== 3 | 4 | .. automodule:: qualityAssurance.checks.rigging 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | -------------------------------------------------------------------------------- /docs/_sources/qualityAssurance.checks.shaders.rst.txt: -------------------------------------------------------------------------------- 1 | qualityAssurance\.checks\.shaders module 2 | ======================================== 3 | 4 | .. automodule:: qualityAssurance.checks.shaders 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | -------------------------------------------------------------------------------- /docs/_sources/qualityAssurance.checks.skinning.rst.txt: -------------------------------------------------------------------------------- 1 | qualityAssurance\.checks\.skinning module 2 | ========================================= 3 | 4 | .. automodule:: qualityAssurance.checks.skinning 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | -------------------------------------------------------------------------------- /docs/_sources/qualityAssurance.checks.textures.rst.txt: -------------------------------------------------------------------------------- 1 | qualityAssurance\.checks\.textures module 2 | ========================================= 3 | 4 | .. automodule:: qualityAssurance.checks.textures 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | -------------------------------------------------------------------------------- /docs/_sources/qualityAssurance.utils.animation.rst.txt: -------------------------------------------------------------------------------- 1 | qualityAssurance\.utils\.animation module 2 | ========================================= 3 | 4 | .. automodule:: qualityAssurance.utils.animation 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | -------------------------------------------------------------------------------- /docs/_sources/qualityAssurance.utils.reference.rst.txt: -------------------------------------------------------------------------------- 1 | qualityAssurance\.utils\.reference module 2 | ========================================= 3 | 4 | .. automodule:: qualityAssurance.utils.reference 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | -------------------------------------------------------------------------------- /docs/_sources/qualityAssurance.checks.animation.rst.txt: -------------------------------------------------------------------------------- 1 | qualityAssurance\.checks\.animation module 2 | ========================================== 3 | 4 | .. automodule:: qualityAssurance.checks.animation 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | -------------------------------------------------------------------------------- /docs/_sources/qualityAssurance.checks.modelling.rst.txt: -------------------------------------------------------------------------------- 1 | qualityAssurance\.checks\.modelling module 2 | ========================================== 3 | 4 | .. automodule:: qualityAssurance.checks.modelling 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | -------------------------------------------------------------------------------- /docs/_sources/qualityAssurance.utils.decorators.rst.txt: -------------------------------------------------------------------------------- 1 | qualityAssurance\.utils\.decorators module 2 | ========================================== 3 | 4 | .. automodule:: qualityAssurance.utils.decorators 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | -------------------------------------------------------------------------------- /docs/_sources/qualityAssurance.checks.renderStats.rst.txt: -------------------------------------------------------------------------------- 1 | qualityAssurance\.checks\.renderStats module 2 | ============================================ 3 | 4 | .. automodule:: qualityAssurance.checks.renderStats 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | -------------------------------------------------------------------------------- /docs/_sources/qualityAssurance.checks.renderLayers.rst.txt: -------------------------------------------------------------------------------- 1 | qualityAssurance\.checks\.renderLayers module 2 | ============================================= 3 | 4 | .. automodule:: qualityAssurance.checks.renderLayers 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | -------------------------------------------------------------------------------- /qualityAssurance.mod: -------------------------------------------------------------------------------- 1 | + MAYAVERSION:2014 qualityAssurance 0.1.0 2 | + MAYAVERSION:2015 qualityAssurance 0.1.0 3 | + MAYAVERSION:2016 qualityAssurance 0.1.0 4 | + MAYAVERSION:2017 qualityAssurance 0.1.0 5 | + MAYAVERSION:2018 qualityAssurance 0.1.0 6 | + MAYAVERSION:2019 qualityAssurance 0.1.0 7 | + MAYAVERSION:2020 qualityAssurance 0.1.0 -------------------------------------------------------------------------------- /docs/_sources/qualityAssurance.ui.rst.txt: -------------------------------------------------------------------------------- 1 | qualityAssurance\.ui package 2 | ============================ 3 | 4 | .. automodule:: qualityAssurance.ui 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | 9 | Submodules 10 | ---------- 11 | 12 | .. toctree:: 13 | 14 | qualityAssurance.ui.utils 15 | qualityAssurance.ui.widgets 16 | qualityAssurance.ui.window 17 | 18 | -------------------------------------------------------------------------------- /scripts/qualityAssurance/utils/reference.py: -------------------------------------------------------------------------------- 1 | from maya import cmds 2 | 3 | 4 | def removeReferenced(nodes): 5 | """ 6 | Remove all referenced nodes from list. 7 | 8 | :param list nodes: List of strings 9 | :return: Filtered list without referenced nodes 10 | :rtype: generator 11 | """ 12 | for node in nodes: 13 | if cmds.referenceQuery(node, inr=True): 14 | continue 15 | 16 | yield node 17 | -------------------------------------------------------------------------------- /scripts/qualityAssurance/utils/undo.py: -------------------------------------------------------------------------------- 1 | from maya import cmds 2 | 3 | 4 | class UndoContext(object): 5 | """ 6 | This context will create a undo chunk of every commands that is ran within 7 | the context. 8 | 9 | .. code-block:: python 10 | 11 | with UndoContext: 12 | # code 13 | """ 14 | def __enter__(self): 15 | cmds.undoInfo(openChunk=True) 16 | 17 | def __exit__(self, *exc_info): 18 | cmds.undoInfo(closeChunk=True) 19 | -------------------------------------------------------------------------------- /docs/_sources/qualityAssurance.rst.txt: -------------------------------------------------------------------------------- 1 | qualityAssurance package 2 | ======================== 3 | 4 | .. automodule:: qualityAssurance 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | 9 | Subpackages 10 | ----------- 11 | 12 | .. toctree:: 13 | 14 | qualityAssurance.checks 15 | qualityAssurance.collections 16 | qualityAssurance.ui 17 | qualityAssurance.utils 18 | 19 | Submodules 20 | ---------- 21 | 22 | .. toctree:: 23 | 24 | qualityAssurance.install 25 | 26 | -------------------------------------------------------------------------------- /scripts/qualityAssurance/utils/animation.py: -------------------------------------------------------------------------------- 1 | from maya import cmds 2 | 3 | 4 | def removeDrivenAnimCurves(animCurves): 5 | """ 6 | Remove set driven animation curves from the list. 7 | 8 | :param list animCurves: List of strings 9 | :return: Filtered list without set driven anim curves 10 | :rtype: generator 11 | """ 12 | for animCurve in animCurves: 13 | if cmds.listConnections("{0}.input".format(animCurve)): 14 | continue 15 | 16 | yield animCurve 17 | -------------------------------------------------------------------------------- /scripts/qualityAssurance/utils/decorators.py: -------------------------------------------------------------------------------- 1 | from functools import wraps 2 | 3 | 4 | def ifNoErrorsReturn(argument): 5 | """ 6 | If the error list is emtpy return the argument provided. 7 | 8 | :param argument: 9 | """ 10 | def decorator(func): 11 | @wraps(func) 12 | def wrapper(self, *args, **kwargs): 13 | if not self.errors: 14 | return argument 15 | 16 | return func(self, *args, **kwargs) 17 | return wrapper 18 | return decorator 19 | -------------------------------------------------------------------------------- /docs/_sources/index.rst.txt: -------------------------------------------------------------------------------- 1 | .. qualityAssurance documentation master file, created by 2 | sphinx-quickstart on Wed Jun 26 08:18:42 2019. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | Welcome to qualityAssurance's documentation! 7 | ============================================ 8 | 9 | .. toctree:: 10 | :maxdepth: 2 11 | :caption: Contents: 12 | 13 | 14 | 15 | Indices and tables 16 | ================== 17 | 18 | * :ref:`genindex` 19 | * :ref:`modindex` 20 | * :ref:`search` 21 | -------------------------------------------------------------------------------- /docs/_sources/qualityAssurance.utils.rst.txt: -------------------------------------------------------------------------------- 1 | qualityAssurance\.utils package 2 | =============================== 3 | 4 | .. automodule:: qualityAssurance.utils 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | 9 | Submodules 10 | ---------- 11 | 12 | .. toctree:: 13 | 14 | qualityAssurance.utils.animation 15 | qualityAssurance.utils.api 16 | qualityAssurance.utils.decorators 17 | qualityAssurance.utils.path 18 | qualityAssurance.utils.qa 19 | qualityAssurance.utils.reference 20 | qualityAssurance.utils.skin 21 | qualityAssurance.utils.undo 22 | 23 | -------------------------------------------------------------------------------- /docs/_sources/qualityAssurance.checks.rst.txt: -------------------------------------------------------------------------------- 1 | qualityAssurance\.checks package 2 | ================================ 3 | 4 | .. automodule:: qualityAssurance.checks 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | 9 | Submodules 10 | ---------- 11 | 12 | .. toctree:: 13 | 14 | qualityAssurance.checks.animation 15 | qualityAssurance.checks.geometry 16 | qualityAssurance.checks.modelling 17 | qualityAssurance.checks.renderLayers 18 | qualityAssurance.checks.renderStats 19 | qualityAssurance.checks.rigging 20 | qualityAssurance.checks.scene 21 | qualityAssurance.checks.shaders 22 | qualityAssurance.checks.skinning 23 | qualityAssurance.checks.textures 24 | qualityAssurance.checks.uv 25 | 26 | -------------------------------------------------------------------------------- /scripts/qualityAssurance/utils/api.py: -------------------------------------------------------------------------------- 1 | from maya import cmds, OpenMaya 2 | 3 | 4 | def toMObject(node): 5 | """ 6 | Convert a node into a OpenMaya.MObject. 7 | 8 | :param str node: 9 | :return: MObject of parsed node 10 | :rtype: OpenMaya.MObject 11 | """ 12 | selectionList = OpenMaya.MSelectionList() 13 | selectionList.add(node) 14 | obj = OpenMaya.MObject() 15 | selectionList.getDependNode(0, obj) 16 | 17 | return obj 18 | 19 | 20 | def toMDagPath(node): 21 | """ 22 | Convert a node into a OpenMaya.MDagPath. 23 | 24 | :param str node: 25 | :return: MDagPath of parsed node 26 | :rtype: OpenMaya.MDagPath 27 | """ 28 | obj = toMObject(node) 29 | if obj.hasFn(OpenMaya.MFn.kDagNode): 30 | dag = OpenMaya.MDagPath.getAPathTo(obj) 31 | return dag 32 | -------------------------------------------------------------------------------- /scripts/qualityAssurance/ui/window.py: -------------------------------------------------------------------------------- 1 | from . import utils, widgets 2 | 3 | 4 | class QualityAssuranceWindow(utils.QWidget): 5 | def __init__(self, parent, collection): 6 | utils.QWidget.__init__(self, parent) 7 | 8 | # set ui 9 | self.setParent(parent) 10 | self.setWindowFlags(utils.Qt.Window) 11 | 12 | self.setWindowTitle("Quality Assurance") 13 | self.setWindowIcon( 14 | utils.QIcon(utils.getIconPath("QA_icon.png")) 15 | ) 16 | self.resize(500, 500) 17 | 18 | # create layout 19 | layout = utils.QVBoxLayout(self) 20 | layout.setContentsMargins(5, 5, 5, 5) 21 | layout.setSpacing(5) 22 | 23 | # add collections 24 | self.collections = widgets.CollectionsWidget(self, collection) 25 | layout.addWidget(self.collections) 26 | 27 | # add container 28 | self.container = widgets.QualityAssuranceWidget(self, collection) 29 | layout.addWidget(self.container) 30 | 31 | # connections 32 | self.collections.currentIndexChanged.connect(self.container.refresh) 33 | 34 | 35 | def show(collection="modelling"): 36 | qa = QualityAssuranceWindow(utils.mayaWindow(), collection) 37 | qa.show() 38 | -------------------------------------------------------------------------------- /scripts/qualityAssurance/checks/textures.py: -------------------------------------------------------------------------------- 1 | import os 2 | from maya import cmds 3 | from ..utils import QualityAssurance, reference 4 | 5 | 6 | class NonExistingTextures(QualityAssurance): 7 | """ 8 | Animation curves will be checked to see if they are not set driven keys. 9 | Non set driven keys should not be present in the scene and will be deleted 10 | when fixing this error. 11 | """ 12 | def __init__(self): 13 | QualityAssurance.__init__(self) 14 | 15 | self._name = "Non Existing Textures" 16 | self._message = "{0} file(s) contain a link to a not existing texture" 17 | self._categories = ["Textures"] 18 | self._selectable = True 19 | 20 | # ------------------------------------------------------------------------ 21 | 22 | def _find(self): 23 | """ 24 | :return: Meshes with non-deformer history 25 | :rtype: generator 26 | """ 27 | fileNodes = self.ls(type="file") 28 | for fileNode in fileNodes: 29 | path = cmds.getAttr("{0}.fileTextureName".format(fileNode)) 30 | if not os.path.exists(path): 31 | yield fileNode 32 | 33 | def _fix(self, fileNode): 34 | """ 35 | :param str fileNode: 36 | """ 37 | cmds.setAttr("{0}.disableFileLoad".format(fileNode), 1) 38 | -------------------------------------------------------------------------------- /scripts/qualityAssurance/install.py: -------------------------------------------------------------------------------- 1 | from maya import cmds, mel 2 | 3 | 4 | # ---------------------------------------------------------------------------- 5 | 6 | 7 | ROOT_PACKAGE = __name__.rsplit(".", 1)[0] 8 | 9 | SHELF_NAME = "MiscTools" 10 | SHELF_TOOL = { 11 | "label": "qualityAssurance", 12 | "command": "import {0}.ui; {0}.ui.show()".format(ROOT_PACKAGE), 13 | "annotation": "Quality assurance framework", 14 | "image1": "QA_icon.png", 15 | "sourceType": "python" 16 | } 17 | 18 | 19 | # ---------------------------------------------------------------------------- 20 | 21 | 22 | def shelf(): 23 | """ 24 | Add a new shelf in Maya with the tools that is provided in the SHELF_TOOL 25 | variable. If the tab exists it will be checked to see if the button is 26 | already added. If this is the case the previous button will be deleted and 27 | a new one will be created in its place. 28 | """ 29 | # get top shelf 30 | gShelfTopLevel = mel.eval("$tmpVar=$gShelfTopLevel") 31 | 32 | # get top shelf names 33 | shelves = cmds.tabLayout(gShelfTopLevel, query=1, ca=1) 34 | 35 | # create shelf 36 | if SHELF_NAME not in shelves: 37 | cmds.shelfLayout(SHELF_NAME, parent=gShelfTopLevel) 38 | 39 | # get existing members 40 | names = cmds.shelfLayout(SHELF_NAME, query=True, childArray=True) or [] 41 | labels = [cmds.shelfButton(n, query=True, label=True) for n in names] 42 | 43 | # delete existing button 44 | if SHELF_TOOL.get("label") in labels: 45 | index = labels.index(SHELF_TOOL.get("label")) 46 | cmds.deleteUI(names[index]) 47 | 48 | # add button 49 | cmds.shelfButton(style="iconOnly", parent=SHELF_NAME, **SHELF_TOOL) 50 | -------------------------------------------------------------------------------- /scripts/qualityAssurance/collections/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | New collections can be added in the COLLECTIONS variable. Since this 3 | COLLECTIONS variable is an OrderedDict, it will keep the order. A 4 | collection can be defined by who will be using it. Currently it is 5 | divided by different specialties. Each specialty contains a list of 6 | categories that will be displayed. The category names link to the categories 7 | defined in the quality assurance checks themselves. 8 | """ 9 | from collections import OrderedDict 10 | 11 | 12 | COLLECTION = OrderedDict( 13 | [ 14 | ( 15 | "animation", [ 16 | "Animation", 17 | "Scene" 18 | ] 19 | ), 20 | ( 21 | "modelling", [ 22 | "Modelling", 23 | "Geometry", 24 | "UV", 25 | "Shaders", 26 | "Render Stats", 27 | "Scene" 28 | ] 29 | ), 30 | ( 31 | "rigging", [ 32 | "Rigging", 33 | "Skinning", 34 | "Shaders", 35 | "Render Stats", 36 | "Scene" 37 | ] 38 | ), 39 | ( 40 | "look-dev", [ 41 | "Shaders", 42 | "Textures", 43 | "UV", 44 | "Render Layers", 45 | "Render Stats", 46 | "Scene" 47 | ] 48 | ) 49 | ] 50 | ) 51 | 52 | 53 | def getCollectionsCategories(): 54 | """ 55 | :return: List of all collection names 56 | :rtype: list 57 | """ 58 | return COLLECTION.keys() 59 | 60 | 61 | def getCollections(): 62 | """ 63 | :return: Collection overview 64 | :rtype: OrderedDict 65 | """ 66 | return COLLECTION 67 | -------------------------------------------------------------------------------- /scripts/qualityAssurance/utils/path.py: -------------------------------------------------------------------------------- 1 | def baseName(name): 2 | """ 3 | This function will strip the namespaces and grouping information of a name. 4 | Useful when working with fullPaths but needing the base for naming. 5 | 6 | :param str name: 7 | :return: Base name of string 8 | :rtype: str 9 | """ 10 | return name.split("|")[-1].split(":")[-1] 11 | 12 | 13 | def rootName(name): 14 | """ 15 | This function will strip the grouping information of a name. 16 | Useful when working with fullPaths but needing the base for naming. 17 | 18 | :param str name: 19 | :return: Root name of string 20 | :rtype: str 21 | """ 22 | return name.split("|")[-1] 23 | 24 | 25 | def namespace(name): 26 | """ 27 | This function will return the namespace if any of the object 28 | 29 | :param str name: 30 | :returns: namespace 31 | :rtype: str/None 32 | """ 33 | if name.find(":") != -1: 34 | return name.split("|")[-1].rsplit(":", 1)[0] 35 | 36 | 37 | # ---------------------------------------------------------------------------- 38 | 39 | 40 | def asFlatList(input): 41 | """ 42 | Convert the input to a flat list. 43 | 44 | :param input: 45 | :return: Flattened list 46 | :rtype: list 47 | """ 48 | if type(input) != list: 49 | # return list with input as content 50 | return [input] 51 | 52 | elif type(input) == list and type(input[0]) == list: 53 | # if the first element of the list is also a list. loop over the lists 54 | # within the lists, and extend them into a new list making the return 55 | # value a combined list. 56 | content = [] 57 | for i in input: 58 | content.extend(i) 59 | return content 60 | 61 | elif type(input) == list: 62 | # return input value 63 | return input 64 | 65 | # return empty list 66 | return [] 67 | -------------------------------------------------------------------------------- /scripts/qualityAssurance/ui/utils.py: -------------------------------------------------------------------------------- 1 | import os 2 | from maya import OpenMaya, OpenMayaUI, cmds 3 | 4 | 5 | # ---------------------------------------------------------------------------- 6 | 7 | 8 | # import pyside, do qt version check for maya 2017 > 9 | qtVersion = cmds.about(qtVersion=True) 10 | if qtVersion.startswith("4") or type(qtVersion) not in [str, unicode]: 11 | from PySide.QtGui import * 12 | from PySide.QtCore import * 13 | import shiboken 14 | else: 15 | from PySide2.QtGui import * 16 | from PySide2.QtCore import * 17 | from PySide2.QtWidgets import * 18 | import shiboken2 as shiboken 19 | 20 | 21 | # ---------------------------------------------------------------------------- 22 | 23 | 24 | FONT = QFont() 25 | FONT.setFamily("Consolas") 26 | 27 | BOLT_FONT = QFont() 28 | BOLT_FONT.setFamily("Consolas") 29 | BOLT_FONT.setWeight(100) 30 | 31 | 32 | # ---------------------------------------------------------------------------- 33 | 34 | 35 | ALIGN_LEFT_STYLESHEET = "QPushButton{text-align: left}" 36 | URGENCY_STYLESHEET = { 37 | 0: "QPushButton{ background-color: green;}", 38 | 1: "QPushButton{ background-color: orange;}", 39 | 2: "QPushButton{ background-color: red;}", 40 | } 41 | 42 | CHECK_ICON = ":/checkboxOff.png" 43 | SELECT_ICON = ":/redSelect.png" 44 | FIX_ICON = ":/interactivePlayback.png" 45 | 46 | COLLAPSE_ICONS = { 47 | True: ":/arrowDown.png", 48 | False: ":/arrowRight.png" 49 | } 50 | 51 | 52 | # ---------------------------------------------------------------------------- 53 | 54 | 55 | def mayaWindow(): 56 | """ 57 | Get Maya's main window. 58 | 59 | :rtype: QMainWindow 60 | """ 61 | window = OpenMayaUI.MQtUtil.mainWindow() 62 | window = shiboken.wrapInstance(long(window), QMainWindow) 63 | 64 | return window 65 | 66 | 67 | # ---------------------------------------------------------------------------- 68 | 69 | 70 | def getIconPath(name): 71 | """ 72 | Get an icon path based on file name. All paths in the XBMLANGPATH variable 73 | processed to see if the provided icon can be found. 74 | 75 | :param str name: 76 | :return: Icon path 77 | :rtype: str/None 78 | """ 79 | for path in os.environ.get("XBMLANGPATH").split(os.pathsep): 80 | iconPath = os.path.join(path, name) 81 | if os.path.exists(iconPath): 82 | return iconPath.replace("\\", "/") 83 | -------------------------------------------------------------------------------- /scripts/qualityAssurance/checks/rigging.py: -------------------------------------------------------------------------------- 1 | from maya import cmds 2 | from ..utils import QualityAssurance, reference 3 | 4 | 5 | class DeleteNonDeformerHistory(QualityAssurance): 6 | """ 7 | Meshes will be checked to see if they contain non-deformer history. Be 8 | carefull with this ceck as it is not always nessecary to fix this. History 9 | is not always a bad thing. When fixing this error the partial history will 10 | be baked. 11 | """ 12 | def __init__(self): 13 | QualityAssurance.__init__(self) 14 | 15 | self._name = "Non Deformer History" 16 | self._message = "{0} mesh(es) contain non-deformer history nodes" 17 | self._categories = ["Rigging"] 18 | self._selectable = True 19 | 20 | self._ignoreNodeTypes = [ 21 | "geometryFilter", "tweak", "groupParts", 22 | "groupId", "shape", "dagPose", 23 | "joint", "shadingEngine", "cluster", 24 | "transform", "diskCache", "time" 25 | ] 26 | 27 | # ------------------------------------------------------------------------ 28 | 29 | @property 30 | def ignoreNodeTypes(self): 31 | return self._ignoreNodeTypes 32 | 33 | # ------------------------------------------------------------------------ 34 | 35 | def _find(self): 36 | """ 37 | :return: Meshes with non-deformer history 38 | :rtype: generator 39 | """ 40 | meshes = self.ls(type="mesh") 41 | meshes = reference.removeReferenced(meshes) 42 | 43 | for mesh in meshes: 44 | history = cmds.listHistory(mesh) 45 | deformers = cmds.ls(history, type=self.ignoreNodeTypes) 46 | 47 | if len(history) != len(deformers): 48 | yield mesh 49 | 50 | def _fix(self, mesh): 51 | """ 52 | :param str mesh: 53 | """ 54 | cmds.bakePartialHistory(mesh, prePostDeformers=True) 55 | 56 | 57 | class DeleteNonSetDrivenAnimation(QualityAssurance): 58 | """ 59 | Animation curves will be checked to see if they are not set driven keys. 60 | Non set driven keys should not be present in the scene and will be deleted 61 | when fixing this error. 62 | """ 63 | def __init__(self): 64 | QualityAssurance.__init__(self) 65 | 66 | self._name = "Non Set-Driven Animation" 67 | self._message = "{0} non set-driven animation curve(s) in the scene" 68 | self._categories = ["Rigging"] 69 | self._selectable = True 70 | 71 | # ------------------------------------------------------------------------ 72 | 73 | def _find(self): 74 | """ 75 | :return: Non set driven key animation curves 76 | :rtype: generator 77 | """ 78 | animCurves = self.ls(type="animCurve") 79 | animCurves = reference.removeReferenced(animCurves) 80 | 81 | for animCurve in animCurves: 82 | if not cmds.listConnections("{0}.input".format(animCurve)): 83 | yield animCurve 84 | 85 | def _fix(self, animCurve): 86 | """ 87 | :param str animCurve: 88 | """ 89 | cmds.delete(animCurve) 90 | -------------------------------------------------------------------------------- /scripts/qualityAssurance/checks/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | New checks can be written in one of the sub modules of this package. 3 | Writing new checks in existing modules they will automatically be picked 4 | up by the script. When adding a new sub module it is important the contents 5 | of the sub module into this one. 6 | 7 | .. code-block:: python 8 | 9 | from .animation import * 10 | 11 | All checks are sorted in order of occurrence in the source code. This can be 12 | used to make sure certain checks are after others. 13 | """ 14 | import sys 15 | import inspect 16 | from collections import OrderedDict 17 | 18 | from .uv import * 19 | from .scene import * 20 | from .rigging import * 21 | from .shaders import * 22 | from .textures import * 23 | from .geometry import * 24 | from .skinning import * 25 | from .modelling import * 26 | from .animation import * 27 | from .renderStats import * 28 | from .renderLayers import * 29 | 30 | from .. import collections 31 | from ..utils.qa import QualityAssurance 32 | 33 | 34 | def getChecks(): 35 | """ 36 | Get all checks available in this module. 37 | 38 | :return: All available error checks 39 | :rtype: list 40 | """ 41 | checks = [] 42 | 43 | # get quality assurance checks. 44 | for name, obj in inspect.getmembers(sys.modules[__name__]): 45 | if ( 46 | inspect.isclass(obj) 47 | and issubclass(obj, QualityAssurance) 48 | and obj.__name__ != QualityAssurance.__name__ 49 | ): 50 | 51 | checks.append(obj()) 52 | 53 | # sort checks based on location in source code. 54 | checks.sort( 55 | key=lambda x: 56 | inspect.getsource( 57 | inspect.getmodule(x) 58 | ).find( 59 | x.__class__.__name__ 60 | ) 61 | ) 62 | 63 | return checks 64 | 65 | 66 | def getChecksFromCollection(collection): 67 | """ 68 | Get all check based on a collections. The collections are stored in the 69 | collection module, or custom collections made by the user are saved to 70 | disk. 71 | 72 | :param str collection: 73 | :return: Ordered dictionary of checks 74 | :rtype: OrderedDict 75 | """ 76 | data = getChecksSplitByCategory() 77 | 78 | categories = getChecksCategories() 79 | categories = collections.getCollections().get(collection) or categories 80 | 81 | return OrderedDict( 82 | [ 83 | (category, data.get(category)) 84 | for category in categories 85 | if data.get(category) 86 | ] 87 | ) 88 | 89 | 90 | def getChecksSplitByCategory(): 91 | """ 92 | Get all of the checks and split them based on category as defined in the 93 | classes themselves. 94 | 95 | :return: All error checks split by category 96 | :rtype: dict 97 | """ 98 | data = {} 99 | 100 | for check in getChecks(): 101 | for category in check.categories: 102 | if category not in data.keys(): 103 | data[category] = [] 104 | 105 | data[category].append(check) 106 | 107 | return data 108 | 109 | 110 | def getChecksCategories(): 111 | """ 112 | Get a list of all the categories used. 113 | 114 | :return: Category list 115 | :rtype: list 116 | """ 117 | return getChecksSplitByCategory().keys() -------------------------------------------------------------------------------- /scripts/qualityAssurance/checks/uv.py: -------------------------------------------------------------------------------- 1 | from maya import cmds 2 | from ..utils import QualityAssurance, reference 3 | 4 | 5 | class EmptyUVSets(QualityAssurance): 6 | """ 7 | Meshes will be checked to see if they have empty uv sets. When fixing the 8 | uv set will be deleted. 9 | """ 10 | def __init__(self): 11 | QualityAssurance.__init__(self) 12 | 13 | self._name = "Empty UV Sets" 14 | self._message = "{0} empty uv set(s)" 15 | self._categories = ["UV"] 16 | self._selectable = False 17 | 18 | # ------------------------------------------------------------------------ 19 | 20 | def _find(self): 21 | """ 22 | :return: Empty UV Sets 23 | :rtype: generator 24 | """ 25 | meshes = cmds.ls(type="mesh", noIntermediate=True, l=True) 26 | meshes = reference.removeReferenced(meshes) 27 | 28 | for mesh in meshes: 29 | uvSets = cmds.polyUVSet(mesh, query=True, allUVSets=True) 30 | uvSetsIndex = cmds.polyUVSet(mesh, query=True, allUVSetsIndices=True) 31 | 32 | for uvSet, index in zip(uvSets, uvSetsIndex): 33 | if index == 0: 34 | continue 35 | 36 | if not cmds.polyEvaluate(mesh, uvcoord=True, uvSetName=uvSet): 37 | yield "{0}.uvSet[{1}].uvSetName".format(mesh, index) 38 | 39 | def _fix(self, meshAttribute): 40 | """ 41 | :param str meshAttribute: 42 | """ 43 | mesh = meshAttribute.split(".")[0] 44 | uvSet = cmds.getAttr(meshAttribute) 45 | 46 | cmds.polyUVSet(mesh, edit=True, delete=True, uvSet=uvSet) 47 | 48 | 49 | class UnusedUVSets(QualityAssurance): 50 | """ 51 | Meshes will be checked to see if they have unused uv sets. When fixing the 52 | uv set will be deleted. 53 | """ 54 | def __init__(self): 55 | QualityAssurance.__init__(self) 56 | 57 | self._name = "Unused UV Sets" 58 | self._message = "{0} unused uv set(s)" 59 | self._categories = ["UV"] 60 | self._selectable = False 61 | 62 | self._ignoreUvSets = [ 63 | "hairUVSet", 64 | ] 65 | 66 | # ------------------------------------------------------------------------ 67 | 68 | @property 69 | def ignoreUvSets(self): 70 | return self._ignoreUvSets 71 | 72 | # ------------------------------------------------------------------------ 73 | 74 | def _find(self): 75 | """ 76 | :return: Empty UV Sets 77 | :rtype: generator 78 | """ 79 | meshes = cmds.ls(type="mesh", noIntermediate=True, l=True) 80 | meshes = reference.removeReferenced(meshes) 81 | 82 | for mesh in meshes: 83 | uvSets = cmds.polyUVSet(mesh, query=True, allUVSets=True) 84 | uvSetsIndex = cmds.polyUVSet(mesh, query=True, allUVSetsIndices=True) 85 | 86 | for uvSet, index in zip(uvSets, uvSetsIndex): 87 | if index == 0 or uvSet in self.ignoreUvSets: 88 | continue 89 | 90 | attr = "{0}.uvSet[{1}].uvSetName".format(mesh, index) 91 | if not cmds.listConnections(attr): 92 | yield attr 93 | 94 | def _fix(self, meshAttribute): 95 | """ 96 | :param str meshAttribute: 97 | """ 98 | mesh = meshAttribute.split(".")[0] 99 | uvSet = cmds.getAttr(meshAttribute) 100 | 101 | cmds.polyUVSet(mesh, edit=True, delete=True, uvSet=uvSet) 102 | -------------------------------------------------------------------------------- /docs/search.html: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | Search — qualityAssurance '' documentation 9 | 10 | 11 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 48 | 49 |
50 |
51 |
52 |
53 | 54 |

Search

55 |
56 | 57 |

58 | Please activate JavaScript to enable the search 59 | functionality. 60 |

61 |
62 |

63 | From here you can search these documents. Enter your search 64 | words into the box below and click "search". Note that the search 65 | function will automatically search for all of the words. Pages 66 | containing fewer words won't appear in the result list. 67 |

68 |
69 | 70 | 71 | 72 |
73 | 74 |
75 | 76 |
77 | 78 |
79 |
80 |
81 | 85 |
86 |
87 | 99 | 103 | 104 | -------------------------------------------------------------------------------- /scripts/qualityAssurance/utils/skin.py: -------------------------------------------------------------------------------- 1 | from maya import cmds, OpenMaya, OpenMayaAnim 2 | 3 | 4 | def getInfluencesApi(skinFn): 5 | """ 6 | Get influence data from a skin cluster 7 | Code written by Tyler Thornock: http://www.charactersetup.com/home.html 8 | 9 | :param OpenMayaAnim.MFnSkinCluster skinFn: 10 | :return: Influences dictionary and list 11 | :rtype: tuple() 12 | """ 13 | # get the MDagPath for all influence 14 | infDags = OpenMaya.MDagPathArray() 15 | skinFn.influenceObjects(infDags) 16 | 17 | # create dictionary with MDagPath 18 | # create list with full path of influences 19 | infIds = {} 20 | infPaths = [] 21 | for x in xrange(infDags.length()): 22 | infPath = infDags[x].fullPathName() 23 | infId = int(skinFn.indexForInfluenceObject(infDags[x])) 24 | infIds[infId] = x 25 | infPaths.append(infPath) 26 | 27 | return infIds, infPaths 28 | 29 | 30 | def getWeightsApiGenerator(skinFn, infIds): 31 | """ 32 | Get skin weights from a skin cluster reading its attributes. 33 | Code written by Tyler Thornock: http://www.charactersetup.com/home.html 34 | 35 | :param OpenMayaAnim.MFnSkinCluster skinFn: 36 | :param dict infIds: 37 | :return: Skin weights per vertex 38 | :rtype: generator 39 | """ 40 | wlPlug = skinFn.findPlug("weightList") 41 | wPlug = skinFn.findPlug("weights") 42 | wlAttr = wlPlug.attribute() 43 | wAttr = wPlug.attribute() 44 | wInfIds = OpenMaya.MIntArray() 45 | 46 | # the weights are stored in dictionary, the key is the vertId, 47 | # the value is another dictionary whose key is the influence id and 48 | # value is the weight for that influence 49 | for vId in xrange(wlPlug.numElements()): 50 | vWeights = {} 51 | # tell the weights attribute which vertex id it represents 52 | wPlug.selectAncestorLogicalIndex(vId, wlAttr) 53 | 54 | # get the indice of all non-zero weights for this vert 55 | wPlug.getExistingArrayAttributeIndices(wInfIds) 56 | 57 | # create a copy of the current wPlug 58 | infPlug = OpenMaya.MPlug(wPlug) 59 | for infId in wInfIds: 60 | # tell the infPlug it represents the current influence id 61 | infPlug.selectAncestorLogicalIndex(infId, wAttr) 62 | 63 | # add this influence and its weight to this verts weights 64 | try: 65 | vWeights[infIds[infId]] = infPlug.asDouble() 66 | except KeyError: 67 | # assumes a removed influence 68 | pass 69 | 70 | yield vWeights 71 | 72 | 73 | def getWeightsApi(skinFn, infIds): 74 | """ 75 | Get skin weights from a skin cluster reading its attributes. 76 | Code written by Tyler Thornock: http://www.charactersetup.com/home.html 77 | 78 | :param OpenMayaAnim.MFnSkinCluster skinFn: 79 | :param dict infIds: 80 | :return: Dictionary of skin weights. 81 | :rtype: dict 82 | """ 83 | weights = {} 84 | 85 | wlPlug = skinFn.findPlug("weightList") 86 | wPlug = skinFn.findPlug("weights") 87 | wlAttr = wlPlug.attribute() 88 | wAttr = wPlug.attribute() 89 | wInfIds = OpenMaya.MIntArray() 90 | 91 | # the weights are stored in dictionary, the key is the vertId, 92 | # the value is another dictionary whose key is the influence id and 93 | # value is the weight for that influence 94 | for vId in xrange(wlPlug.numElements()): 95 | vWeights = {} 96 | # tell the weights attribute which vertex id it represents 97 | wPlug.selectAncestorLogicalIndex(vId, wlAttr) 98 | 99 | # get the indice of all non-zero weights for this vert 100 | wPlug.getExistingArrayAttributeIndices(wInfIds) 101 | 102 | # create a copy of the current wPlug 103 | infPlug = OpenMaya.MPlug(wPlug) 104 | for infId in wInfIds: 105 | # tell the infPlug it represents the current influence id 106 | infPlug.selectAncestorLogicalIndex(infId, wAttr) 107 | 108 | # add this influence and its weight to this verts weights 109 | try: 110 | vWeights[infIds[infId]] = infPlug.asDouble() 111 | except KeyError: 112 | # assumes a removed influence 113 | pass 114 | 115 | weights[vId] = vWeights 116 | 117 | return weights 118 | -------------------------------------------------------------------------------- /docs/_static/pygments.css: -------------------------------------------------------------------------------- 1 | .highlight .hll { background-color: #ffffcc } 2 | .highlight { background: #eeffcc; } 3 | .highlight .c { color: #408090; font-style: italic } /* Comment */ 4 | .highlight .err { border: 1px solid #FF0000 } /* Error */ 5 | .highlight .k { color: #007020; font-weight: bold } /* Keyword */ 6 | .highlight .o { color: #666666 } /* Operator */ 7 | .highlight .ch { color: #408090; font-style: italic } /* Comment.Hashbang */ 8 | .highlight .cm { color: #408090; font-style: italic } /* Comment.Multiline */ 9 | .highlight .cp { color: #007020 } /* Comment.Preproc */ 10 | .highlight .cpf { color: #408090; font-style: italic } /* Comment.PreprocFile */ 11 | .highlight .c1 { color: #408090; font-style: italic } /* Comment.Single */ 12 | .highlight .cs { color: #408090; background-color: #fff0f0 } /* Comment.Special */ 13 | .highlight .gd { color: #A00000 } /* Generic.Deleted */ 14 | .highlight .ge { font-style: italic } /* Generic.Emph */ 15 | .highlight .gr { color: #FF0000 } /* Generic.Error */ 16 | .highlight .gh { color: #000080; font-weight: bold } /* Generic.Heading */ 17 | .highlight .gi { color: #00A000 } /* Generic.Inserted */ 18 | .highlight .go { color: #333333 } /* Generic.Output */ 19 | .highlight .gp { color: #c65d09; font-weight: bold } /* Generic.Prompt */ 20 | .highlight .gs { font-weight: bold } /* Generic.Strong */ 21 | .highlight .gu { color: #800080; font-weight: bold } /* Generic.Subheading */ 22 | .highlight .gt { color: #0044DD } /* Generic.Traceback */ 23 | .highlight .kc { color: #007020; font-weight: bold } /* Keyword.Constant */ 24 | .highlight .kd { color: #007020; font-weight: bold } /* Keyword.Declaration */ 25 | .highlight .kn { color: #007020; font-weight: bold } /* Keyword.Namespace */ 26 | .highlight .kp { color: #007020 } /* Keyword.Pseudo */ 27 | .highlight .kr { color: #007020; font-weight: bold } /* Keyword.Reserved */ 28 | .highlight .kt { color: #902000 } /* Keyword.Type */ 29 | .highlight .m { color: #208050 } /* Literal.Number */ 30 | .highlight .s { color: #4070a0 } /* Literal.String */ 31 | .highlight .na { color: #4070a0 } /* Name.Attribute */ 32 | .highlight .nb { color: #007020 } /* Name.Builtin */ 33 | .highlight .nc { color: #0e84b5; font-weight: bold } /* Name.Class */ 34 | .highlight .no { color: #60add5 } /* Name.Constant */ 35 | .highlight .nd { color: #555555; font-weight: bold } /* Name.Decorator */ 36 | .highlight .ni { color: #d55537; font-weight: bold } /* Name.Entity */ 37 | .highlight .ne { color: #007020 } /* Name.Exception */ 38 | .highlight .nf { color: #06287e } /* Name.Function */ 39 | .highlight .nl { color: #002070; font-weight: bold } /* Name.Label */ 40 | .highlight .nn { color: #0e84b5; font-weight: bold } /* Name.Namespace */ 41 | .highlight .nt { color: #062873; font-weight: bold } /* Name.Tag */ 42 | .highlight .nv { color: #bb60d5 } /* Name.Variable */ 43 | .highlight .ow { color: #007020; font-weight: bold } /* Operator.Word */ 44 | .highlight .w { color: #bbbbbb } /* Text.Whitespace */ 45 | .highlight .mb { color: #208050 } /* Literal.Number.Bin */ 46 | .highlight .mf { color: #208050 } /* Literal.Number.Float */ 47 | .highlight .mh { color: #208050 } /* Literal.Number.Hex */ 48 | .highlight .mi { color: #208050 } /* Literal.Number.Integer */ 49 | .highlight .mo { color: #208050 } /* Literal.Number.Oct */ 50 | .highlight .sa { color: #4070a0 } /* Literal.String.Affix */ 51 | .highlight .sb { color: #4070a0 } /* Literal.String.Backtick */ 52 | .highlight .sc { color: #4070a0 } /* Literal.String.Char */ 53 | .highlight .dl { color: #4070a0 } /* Literal.String.Delimiter */ 54 | .highlight .sd { color: #4070a0; font-style: italic } /* Literal.String.Doc */ 55 | .highlight .s2 { color: #4070a0 } /* Literal.String.Double */ 56 | .highlight .se { color: #4070a0; font-weight: bold } /* Literal.String.Escape */ 57 | .highlight .sh { color: #4070a0 } /* Literal.String.Heredoc */ 58 | .highlight .si { color: #70a0d0; font-style: italic } /* Literal.String.Interpol */ 59 | .highlight .sx { color: #c65d09 } /* Literal.String.Other */ 60 | .highlight .sr { color: #235388 } /* Literal.String.Regex */ 61 | .highlight .s1 { color: #4070a0 } /* Literal.String.Single */ 62 | .highlight .ss { color: #517918 } /* Literal.String.Symbol */ 63 | .highlight .bp { color: #007020 } /* Name.Builtin.Pseudo */ 64 | .highlight .fm { color: #06287e } /* Name.Function.Magic */ 65 | .highlight .vc { color: #bb60d5 } /* Name.Variable.Class */ 66 | .highlight .vg { color: #bb60d5 } /* Name.Variable.Global */ 67 | .highlight .vi { color: #bb60d5 } /* Name.Variable.Instance */ 68 | .highlight .vm { color: #bb60d5 } /* Name.Variable.Magic */ 69 | .highlight .il { color: #208050 } /* Literal.Number.Integer.Long */ -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # maya-quality-assurance 2 |

3 | Quality assurance framework for Maya. Focused on many parts of a production pipeline, collections are created for animators, modelers, riggers and look-dev. 4 | 5 |

6 | 7 | ## Installation 8 | * Extract the content of the .rar file anywhere on disk. 9 | * Drag the qualityAssurance.mel file in Maya to permanently install the script. 10 | 11 | ## Usage 12 | A button on the MiscTools shelf will be created that will allow easy access to the ui, this way the user doesn't need to worry about any of the code. 13 | If user wishes to not use the shelf button the following commands can be used. 14 | 15 | Display UI: 16 | 17 | ```python 18 | import qualityAssurance.ui 19 | qualityAssurance.ui.show("rigging") 20 | ``` 21 | 22 | The show function takes in a collection argument, if you work within one of the specialties you can simply call the show function with the collection you want to see by default. To see all available collections run the following code. 23 | 24 | ```python 25 | import qualityAssurance.collections 26 | print qualityAssurance.collections.getCollectionsCategories() 27 | ``` 28 | 29 | ## Adding Quality Assurance Checks 30 | The quality assurance framework is setup so new quality assurance checks can easily be added. 31 | 32 | ### Location 33 | All quality assurance checks are located in the **checks** folder. New checks can be written in one of the sub modules of the **checks** folder. Writing new checks in existing modules will automatically be picked up by the script. When adding a new sub module it is important to import the contents of the sub module into the **__init__.py** file in the **checks** folder.

34 | 35 | ```python 36 | from .animation import * 37 | ``` 38 | 39 | All checks are sorted in order of occurrence in the source code. This can be used to make sure certain checks are after others. 40 | 41 | ### Collections 42 | New collections can be added in the **COLLECTIONS** variable. Since this **COLLECTIONS** variable is an OrderedDict, it will keep the order. A collection can be defined by who will be using it. Currently it is divided by different specialties. Each specialty contains a list of categories that will be displayed. The category names link to the categories defined in the quality assurance checks themselves. 43 | 44 | ### Sub Classing 45 | New quality assurance checks can be created by sub classing the **QualityAssurance** base class. It is important to extend the class with a **_find** and **_fix** function and update the meta data in the **__init__** function. 46 | 47 | ```python 48 | from ..utils import QualityAssurance, reference 49 | 50 | class TestCheck(QualityAssurance): 51 | def __init__(self): 52 | QualityAssurance.__init__(self) 53 | 54 | self._name = "Unused Animation" 55 | self._message = "{0} animation curve(s) are unused" 56 | self._categories = ["Animation"] 57 | self._selectable = True 58 | 59 | # ------------------------------------------------------------------------ 60 | 61 | def _find(self): 62 | animCurves = self.ls(type="animCurve") 63 | animCurves = reference.removeReferenced(animCurves) 64 | 65 | for animCurve in animCurves: 66 | # check if the output plug is connected 67 | if cmds.listConnections("{0}.output".format(animCurve)): 68 | continue 69 | 70 | # yield error 71 | yield animCurve 72 | 73 | def _fix(self, animCurve): 74 | cmds.delete(animCurve) 75 | ``` 76 | 77 | #### Meta Data 78 | * **self._name:** Name of the quality assurance check 79 | * **self._urgency:** Urgency level, 1=orange, 2=red. 80 | * **self._message:** Format-able message when errors are found. 81 | * **self._categories:** List of categories the quality assurance check should part of. 82 | * **self._selectable:** Boolean value if the error list is selectable. 83 | 84 | #### Find and Fix Function 85 | * The **_find** function is a generator that yields errors as they get found. 86 | * The **_fix** function fixes one of these errors at a time. In the example above we could find multiple animation curves, but the fix only deletes one animation curve at a time. 87 | 88 | ## Note 89 | Inspired by Martin Orlowski's **Quality GuAard**, I've decided to write my own quality assurance framework and make it freely available. The project is available on [Git](https://github.com/robertjoosten/maya-quality-assurance). Free for anybody that wishes to contribute to this tool and add additional quality assurance checks. -------------------------------------------------------------------------------- /docs/qualityAssurance.install.html: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | qualityAssurance.install module — qualityAssurance '' documentation 9 | 10 | 11 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 40 | 41 |
42 |
43 |
44 |
45 | 46 |
47 |

qualityAssurance.install module

48 |
49 |
50 | qualityAssurance.install.shelf()
51 |

Add a new shelf in Maya with the tools that is provided in the SHELF_TOOL 52 | variable. If the tab exists it will be checked to see if the button is 53 | already added. If this is the case the previous button will be deleted and 54 | a new one will be created in its place.

55 |
56 | 57 |
58 | 59 | 60 |
61 |
62 |
63 | 84 |
85 |
86 | 98 | 102 | 103 | -------------------------------------------------------------------------------- /scripts/qualityAssurance/checks/shaders.py: -------------------------------------------------------------------------------- 1 | from maya import cmds 2 | from ..utils import QualityAssurance, reference 3 | 4 | 5 | class NoShadingGroup(QualityAssurance): 6 | """ 7 | Meshes will be checked to see if they have a shading group attached. 8 | """ 9 | def __init__(self): 10 | QualityAssurance.__init__(self) 11 | 12 | self._name = "No Shading Group Assignment" 13 | self._message = "{0} mesh(es) are not connected to any shading group" 14 | self._categories = ["Shaders"] 15 | self._selectable = True 16 | 17 | # ------------------------------------------------------------------------ 18 | 19 | def _find(self): 20 | """ 21 | :return: Meshes without shading group attachment 22 | :rtype: generator 23 | """ 24 | meshes = self.ls(type="mesh", noIntermediate=True, l=True) 25 | meshes = reference.removeReferenced(meshes) 26 | 27 | for mesh in meshes: 28 | shadingGroups = cmds.listConnections(mesh, type="shadingEngine") 29 | if not shadingGroups: 30 | yield mesh 31 | 32 | 33 | class InitialShadingGroup(QualityAssurance): 34 | """ 35 | Meshes will be checked to see if they have the default shading group 36 | attached. 37 | """ 38 | def __init__(self): 39 | QualityAssurance.__init__(self) 40 | 41 | self._name = "Initial Shading Group Assignment" 42 | self._message = "{0} object(s) are connected to the initial shading group" 43 | self._categories = ["Shaders"] 44 | self._selectable = True 45 | 46 | # ------------------------------------------------------------------------ 47 | 48 | def _find(self): 49 | """ 50 | :return: Meshes without shading group attachment 51 | :rtype: generator 52 | """ 53 | shadingGroups = cmds.ls(type="shadingEngine") 54 | 55 | if "initialShadingGroup" not in shadingGroups: 56 | return 57 | 58 | connections = cmds.sets("initialShadingGroup", query=True) or [] 59 | if not connections: 60 | return 61 | 62 | connections = cmds.ls(connections, l=True) 63 | for connection in connections: 64 | yield connection 65 | 66 | 67 | class FaceAssignedShading(QualityAssurance): 68 | """ 69 | Shading groups will be checked to see if they contain component 70 | connections. When fixing it will force a shape connections. If you 71 | intentially have component assigment, it is best to skip this check. 72 | """ 73 | def __init__(self): 74 | QualityAssurance.__init__(self) 75 | 76 | self._name = "Face Assignment" 77 | self._urgency = 1 78 | self._message = "{0} shading group(s) contain a component connection" 79 | self._categories = ["Shaders"] 80 | self._selectable = True 81 | 82 | # ------------------------------------------------------------------------ 83 | 84 | def _find(self): 85 | """ 86 | :return: Empty UV Sets 87 | :rtype: generator 88 | """ 89 | shadingGroups = self.ls(type="shadingEngine") 90 | 91 | for shadingGroup in shadingGroups: 92 | connections = cmds.sets(shadingGroup, query=True) or [] 93 | components = [ 94 | connection.split(".")[0] 95 | for connection in connections 96 | if connection.count(".") 97 | ] 98 | 99 | if components: 100 | yield shadingGroup 101 | 102 | def _fix(self, shadingGroup): 103 | """ 104 | :param str node: 105 | """ 106 | connections = cmds.sets(shadingGroup, query=True) or [] 107 | components = [ 108 | connection 109 | for connection in connections 110 | if connection.count(".") 111 | ] 112 | 113 | if not components: 114 | return 115 | 116 | overview = dict() 117 | components = cmds.ls(components, fl=True, l=True) 118 | 119 | # collect faces per object 120 | for face in components: 121 | obj = face.split(".")[0] 122 | if cmds.nodeType(obj) == "transform": 123 | shape = cmds.listRelatives(obj, s=True, ni=True, f=True)[0] 124 | else: 125 | shape = obj[:] 126 | 127 | if not shape in overview.keys(): 128 | overview[shape] = [] 129 | 130 | overview[shape].append(face) 131 | 132 | # loop overview 133 | for shape, remove in overview.iteritems(): 134 | if len(remove) != cmds.polyEvaluate(shape, face=True): 135 | raise RuntimeError("Incomplete Face Assigment") 136 | 137 | cmds.sets(remove, edit=True, remove=shadingGroup) 138 | cmds.sets(shape, edit=True, forceElement=shadingGroup) 139 | -------------------------------------------------------------------------------- /docs/qualityAssurance.utils.undo.html: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | qualityAssurance.utils.undo module — qualityAssurance '' documentation 9 | 10 | 11 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 40 | 41 |
42 |
43 |
44 |
45 | 46 |
47 |

qualityAssurance.utils.undo module

48 |
49 |
50 | class qualityAssurance.utils.undo.UndoContext
51 |

Bases: object

52 |

This context will create a undo chunk of every commands that is ran within 53 | the context.

54 |
with UndoContext:
 55 |     # code
 56 | 
57 |
58 |
59 | 60 |
61 | 62 | 63 |
64 |
65 |
66 | 87 |
88 |
89 | 101 | 105 | 106 | -------------------------------------------------------------------------------- /docs/qualityAssurance.ui.html: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | qualityAssurance.ui package — qualityAssurance '' documentation 9 | 10 | 11 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 40 | 41 |
42 |
43 |
44 |
45 | 46 |
47 |

qualityAssurance.ui package

48 | 58 |
59 | 60 | 61 |
62 |
63 |
64 | 93 |
94 |
95 | 107 | 111 | 112 | -------------------------------------------------------------------------------- /docs/qualityAssurance.utils.decorators.html: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | qualityAssurance.utils.decorators module — qualityAssurance '' documentation 9 | 10 | 11 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 40 | 41 |
42 |
43 |
44 |
45 | 46 |
47 |

qualityAssurance.utils.decorators module

48 |
49 |
50 | qualityAssurance.utils.decorators.ifNoErrorsReturn(argument)
51 |

If the error list is emtpy return the argument provided.

52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 |
Parameters:argument
60 |
61 | 62 |
63 | 64 | 65 |
66 |
67 |
68 | 89 |
90 |
91 | 103 | 107 | 108 | -------------------------------------------------------------------------------- /docs/qualityAssurance.checks.textures.html: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | qualityAssurance.checks.textures module — qualityAssurance '' documentation 9 | 10 | 11 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 40 | 41 |
42 |
43 |
44 |
45 | 46 |
47 |

qualityAssurance.checks.textures module

48 |
49 |
50 | class qualityAssurance.checks.textures.NonExistingTextures
51 |

Bases: qualityAssurance.utils.qa.QualityAssurance

52 |

Animation curves will be checked to see if they are not set driven keys. 53 | Non set driven keys should not be present in the scene and will be deleted 54 | when fixing this error.

55 |
56 | 57 |
58 | 59 | 60 |
61 |
62 |
63 | 84 |
85 |
86 | 98 | 102 | 103 | -------------------------------------------------------------------------------- /scripts/qualityAssurance/checks/renderStats.py: -------------------------------------------------------------------------------- 1 | from maya import cmds 2 | from ..utils import QualityAssurance 3 | 4 | 5 | class PrimaryVisibility(QualityAssurance): 6 | """ 7 | Meshes will be checked to see if the primary visibility is on. When 8 | fixing this error the primary visibility will be turned on. 9 | """ 10 | def __init__(self): 11 | QualityAssurance.__init__(self) 12 | 13 | self._name = "Primary Visibility" 14 | self._message = "{0} mesh(es) are not visible" 15 | self._categories = ["Render Stats"] 16 | self._selectable = True 17 | 18 | self._attribute = ".primaryVisibility" 19 | self._errorBool = False 20 | 21 | # ------------------------------------------------------------------------ 22 | 23 | @property 24 | def attribute(self): 25 | return self._attribute 26 | 27 | @property 28 | def errorBool(self): 29 | return self._errorBool 30 | 31 | # ------------------------------------------------------------------------ 32 | 33 | def _find(self): 34 | """ 35 | :return: Find meshes that have the attribute set to the error boolean 36 | :rtype: generator 37 | """ 38 | meshes = self.ls(type="mesh") 39 | 40 | for mesh in meshes: 41 | if cmds.getAttr(mesh + self.attribute) == self.errorBool: 42 | yield mesh 43 | 44 | def _fix(self, mesh): 45 | """ 46 | :param str mesh: 47 | """ 48 | cmds.setAttr(mesh + self.attribute, not self.errorBool) 49 | 50 | 51 | class VisibleInRefraction(PrimaryVisibility): 52 | """ 53 | Meshes will be checked to see if they are visible in refraction. When 54 | fixing this error the visible in refraction will be turned on. 55 | """ 56 | def __init__(self): 57 | PrimaryVisibility.__init__(self) 58 | 59 | self._name = "Visible in Refraction" 60 | self._message = "{0} mesh(es) are not visible in refraction" 61 | 62 | self._attribute = ".visibleInRefractions" 63 | self._errorBool = False 64 | 65 | 66 | class VisibleInReflection(PrimaryVisibility): 67 | """ 68 | Meshes will be checked to see if they are visible in reflection. When 69 | fixing this error the visible in reflection will be turned on. 70 | """ 71 | def __init__(self): 72 | PrimaryVisibility.__init__(self) 73 | 74 | self._name = "Visible in Reflection" 75 | self._message = "{0} mesh(es) are not visible in reflection" 76 | 77 | self._attribute = ".visibleInReflections" 78 | self._errorBool = False 79 | 80 | 81 | class CastShadows(PrimaryVisibility): 82 | """ 83 | Meshes will be checked to see if they cast shadows. When fixing this 84 | error the casting of shadows will be turned on. 85 | """ 86 | def __init__(self): 87 | PrimaryVisibility.__init__(self) 88 | 89 | self._name = "Cast Shadows" 90 | self._message = "{0} mesh(es) don't cast shadows" 91 | 92 | self._attribute = ".castsShadows" 93 | self._errorBool = False 94 | 95 | 96 | class ReceiveShadows(PrimaryVisibility): 97 | """ 98 | Meshes will be checked to see if they receive shadows. When fixing this 99 | error the receiving of shadows will be turned on. 100 | """ 101 | def __init__(self): 102 | PrimaryVisibility.__init__(self) 103 | 104 | self._name = "Receive Shadows" 105 | self._message = "{0} mesh(es) don't receive shadows" 106 | 107 | self._attribute = ".receiveShadows" 108 | self._errorBool = False 109 | 110 | 111 | class SmoothShading(PrimaryVisibility): 112 | """ 113 | Meshes will be checked to see if they are smooth shaded. When fixing this 114 | error the smooth shading will be turned on. 115 | """ 116 | def __init__(self): 117 | PrimaryVisibility.__init__(self) 118 | 119 | self._name = "Smooth Shading" 120 | self._message = "{0} mesh(es) are not smooth shaded" 121 | 122 | self._attribute = ".smoothShading" 123 | self._errorBool = False 124 | 125 | 126 | class DoubleSided(PrimaryVisibility): 127 | """ 128 | Meshes will be checked to see if they are double sided. When fixing this 129 | error double sided will be turned on. 130 | """ 131 | def __init__(self): 132 | PrimaryVisibility.__init__(self) 133 | 134 | self._name = "Double Sided" 135 | self._message = "{0} mesh(es) are not double sided" 136 | 137 | self._attribute = ".doubleSided" 138 | self._errorBool = False 139 | 140 | 141 | class Opposite(PrimaryVisibility): 142 | """ 143 | Meshes will be checked to see if they are set to opposite. When fixing 144 | this error opposite state will be turned off. 145 | """ 146 | def __init__(self): 147 | PrimaryVisibility.__init__(self) 148 | 149 | self._name = "Opposite" 150 | self._message = "{0} mesh(es) are set to opposite" 151 | 152 | self._attribute = ".opposite" 153 | self._errorBool = True 154 | -------------------------------------------------------------------------------- /scripts/qualityAssurance/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Quality assurance framework for Maya. Focused on many parts of a production 3 | pipeline, collections are created for animators, modelers, riggers and 4 | look-dev. 5 | 6 | .. figure:: /_images/qualityAssuranceExample.png 7 | :align: center 8 | 9 | Installation 10 | ============ 11 | * Extract the content of the .rar file anywhere on disk. 12 | * Drag the qualityAssurance.mel file in Maya to permanently install the script. 13 | 14 | Usage 15 | ===== 16 | A button on the MiscTools shelf will be created that will allow easy access to 17 | the ui, this way the user doesn't need to worry about any of the code. If user 18 | wishes to not use the shelf button the following commands can be used. 19 | 20 | Display UI 21 | 22 | .. code-block:: python 23 | 24 | import qualityAssurance.ui 25 | qualityAssurance.ui.show("rigging") 26 | 27 | The show function takes in a collection argument, if you work within one of 28 | the specialties you can simply call the show function with the collection 29 | you want to see by default. To see all available collections run the following 30 | code. 31 | 32 | .. code-block:: python 33 | 34 | import qualityAssurance.collections 35 | print qualityAssurance.collections.getCollectionsCategories() 36 | 37 | Adding Quality Assurance Checks 38 | =============================== 39 | 40 | The quality assurance framework is setup so new quality assurance checks can 41 | easily be added. 42 | 43 | Location 44 | -------- 45 | All quality assurance checks are located in the **checks** folder. New checks 46 | can be written in one of the sub modules of the **checks** folder. Writing 47 | new checks in existing modules will automatically be picked up by the script. 48 | When adding a new sub module it is important to import the contents of the 49 | sub module into the **__init__.py** file in the **checks** folder. 50 | 51 | .. code-block:: python 52 | 53 | from .animation import * 54 | 55 | All checks are sorted in order of occurrence in the source code. This can be 56 | used to make sure certain checks are after others. 57 | 58 | Collections 59 | ----------- 60 | New collections can be added in the **COLLECTIONS** variable. Since this 61 | **COLLECTIONS** variable is an OrderedDict, it will keep the order. A 62 | collection can be defined by who will be using it. Currently it is 63 | divided by different specialties. Each specialty contains a list of 64 | categories that will be displayed. The category names link to the categories 65 | defined in the quality assurance checks themselves. 66 | 67 | Sub Classing 68 | ------------ 69 | New quality assurance checks can be created by sub classing the 70 | :class:`qualityAssurance.utils.qa.QualityAssurance` base class. It is important 71 | to extend the class with a **_find** and **_fix** function and update the meta 72 | data in the **__init__** function. 73 | 74 | .. code-block:: python 75 | 76 | from ..utils import QualityAssurance, reference 77 | 78 | class TestCheck(QualityAssurance): 79 | def __init__(self): 80 | QualityAssurance.__init__(self) 81 | 82 | self._name = "Unused Animation" 83 | self._message = "{0} animation curve(s) are unused" 84 | self._categories = ["Animation"] 85 | self._selectable = True 86 | 87 | # ------------------------------------------------------------------------ 88 | 89 | def _find(self): 90 | animCurves = self.ls(type="animCurve") 91 | animCurves = reference.removeReferenced(animCurves) 92 | 93 | for animCurve in animCurves: 94 | # check if the output plug is connected 95 | if cmds.listConnections("{0}.output".format(animCurve)): 96 | continue 97 | 98 | # yield error 99 | yield animCurve 100 | 101 | def _fix(self, animCurve): 102 | cmds.delete(animCurve) 103 | 104 | Meta Data 105 | ^^^^^^^^^ 106 | * **self._name:** Name of the quality assurance check 107 | * **self._urgency:** Urgency level, 1=orange, 2=red. 108 | * **self._message:** Format-able message when errors are found. 109 | * **self._categories:** List of categories the quality assurance check should part of. 110 | * **self._selectable:** Boolean value if the error list is selectable. 111 | 112 | Find and Fix Function 113 | ^^^^^^^^^^^^^^^^^^^^^ 114 | * The **_find** function is a generator that yields errors as they get found. 115 | * The **_fix** function fixes one of these errors at a time. In the example above we could find multiple animation curves, but the fix only deletes one animation curve at a time. 116 | 117 | Note 118 | ===== 119 | Inspired by Martin Orlowski's **Quality GuAard**, I've decided to write my own 120 | quality assurance framework and make it freely available. The project is 121 | available on `Git `_. 122 | Free for anybody that wishes to contribute to this tool and add additional 123 | quality assurance checks. 124 | """ 125 | __author__ = "Robert Joosten" 126 | __version__ = "0.1.0" 127 | __email__ = "rwm.joosten@gmail.com" 128 | -------------------------------------------------------------------------------- /docs/qualityAssurance.utils.reference.html: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | qualityAssurance.utils.reference module — qualityAssurance '' documentation 9 | 10 | 11 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 40 | 41 |
42 |
43 |
44 |
45 | 46 |
47 |

qualityAssurance.utils.reference module

48 |
49 |
50 | qualityAssurance.utils.reference.removeReferenced(nodes)
51 |

Remove all referenced nodes from list.

52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 |
Parameters:nodes (list) – List of strings
Returns:Filtered list without referenced nodes
Return type:generator
64 |
65 | 66 |
67 | 68 | 69 |
70 |
71 |
72 | 93 |
94 |
95 | 107 | 111 | 112 | -------------------------------------------------------------------------------- /scripts/qualityAssurance/checks/modelling.py: -------------------------------------------------------------------------------- 1 | from maya import cmds 2 | from ..utils import QualityAssurance, reference 3 | 4 | 5 | class FreezeTransforms(QualityAssurance): 6 | """ 7 | Transforms will be checked to see if they have unfrozen attributes When 8 | fixing this error transforms will be frozen. 9 | """ 10 | def __init__(self): 11 | QualityAssurance.__init__(self) 12 | 13 | self._name = "Freeze Transforms" 14 | self._message = "{0} transform(s) are not frozen" 15 | self._categories = ["Modelling"] 16 | self._selectable = True 17 | 18 | self._ignoreNodes = ["|persp", "|front", "|top", "|side"] 19 | 20 | self._attributes = [ 21 | ".tx", "ty", "tz", 22 | ".rx", "ry", "rz", 23 | ".sx", "sy", "sz" 24 | ] 25 | self._values = [ 26 | 0, 0, 0, 27 | 0, 0, 0, 28 | 1, 1, 1 29 | ] 30 | 31 | # ------------------------------------------------------------------------ 32 | 33 | @property 34 | def ignoreNodes(self): 35 | """ 36 | :return: Nodes to ignore 37 | :rtype: list 38 | """ 39 | return self._ignoreNodes 40 | 41 | @property 42 | def defaultState(self): 43 | """ 44 | :return: Default state of attributes with values 45 | :rtype: list 46 | """ 47 | return zip(self._attributes, self._values) 48 | 49 | # ------------------------------------------------------------------------ 50 | 51 | def _find(self): 52 | """ 53 | :return: Unfrozen transforms 54 | :rtype: generator 55 | """ 56 | transforms = self.ls(transforms=True, l=True) 57 | transforms = reference.removeReferenced(transforms) 58 | 59 | for transform in transforms: 60 | if transform in self.ignoreNodes: 61 | continue 62 | 63 | for attr, value in self.defaultState: 64 | if not cmds.objExists(transform + attr): 65 | continue 66 | 67 | if cmds.getAttr(transform + attr) != value: 68 | yield transform 69 | 70 | def _fix(self, transform): 71 | """ 72 | :param str transform: 73 | """ 74 | cmds.makeIdentity( 75 | transform, 76 | apply=True, 77 | translate=True, 78 | rotate=True, 79 | scale=True 80 | ) 81 | 82 | 83 | class DeleteHistory(QualityAssurance): 84 | """ 85 | Mesh shapes will be checked to see if they have history attached to them. 86 | When fixing this error the history will be deleted. 87 | """ 88 | def __init__(self): 89 | QualityAssurance.__init__(self) 90 | 91 | self._name = "History" 92 | self._message = "{0} mesh(es) contain history nodes" 93 | self._categories = ["Modelling"] 94 | self._selectable = True 95 | 96 | self._ignoreNodes = [ 97 | "tweak", "groupParts", "groupId", 98 | "shape", "shadingEngine", "mesh" 99 | ] 100 | 101 | 102 | # ------------------------------------------------------------------------ 103 | 104 | @property 105 | def ignoreNodes(self): 106 | """ 107 | :return: Nodes to ignore 108 | :rtype: list 109 | """ 110 | return self._ignoreNodes 111 | 112 | # ------------------------------------------------------------------------ 113 | 114 | def _find(self): 115 | """ 116 | :return: Meshes with history 117 | :rtype: generator 118 | """ 119 | 120 | meshes = self.ls(type="mesh", l=True) 121 | meshes = reference.removeReferenced(meshes) 122 | 123 | for mesh in meshes: 124 | history = cmds.listHistory(mesh) or [] 125 | types = [cmds.nodeType(h) for h in history] 126 | 127 | for t in types: 128 | if t in self.ignoreNodes: 129 | continue 130 | 131 | yield mesh 132 | break 133 | 134 | def _fix(self, mesh): 135 | """ 136 | :param str mesh: 137 | """ 138 | cmds.delete(mesh, ch=True) 139 | 140 | 141 | class DeleteAnimation(QualityAssurance): 142 | """ 143 | All animation curves will be added to the error list. When fixing this 144 | error the animation curves will be deleted 145 | """ 146 | def __init__(self): 147 | QualityAssurance.__init__(self) 148 | 149 | self._name = "Animation" 150 | self._message = "{0} animation curve(s) in the scene" 151 | self._categories = ["Modelling"] 152 | self._selectable = True 153 | 154 | # ------------------------------------------------------------------------ 155 | 156 | def _find(self): 157 | """ 158 | :return: Animation curves 159 | :rtype: generator 160 | """ 161 | 162 | animCurves = self.ls(type="animCurve") 163 | animCurves = reference.removeReferenced(animCurves) 164 | 165 | for animCurve in animCurves: 166 | yield animCurve 167 | 168 | def _fix(self, animCurve): 169 | """ 170 | :param str animCurve: 171 | """ 172 | cmds.delete(animCurve) 173 | -------------------------------------------------------------------------------- /docs/qualityAssurance.utils.animation.html: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | qualityAssurance.utils.animation module — qualityAssurance '' documentation 9 | 10 | 11 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 40 | 41 |
42 |
43 |
44 |
45 | 46 |
47 |

qualityAssurance.utils.animation module

48 |
49 |
50 | qualityAssurance.utils.animation.removeDrivenAnimCurves(animCurves)
51 |

Remove set driven animation curves from the list.

52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 |
Parameters:animCurves (list) – List of strings
Returns:Filtered list without set driven anim curves
Return type:generator
64 |
65 | 66 |
67 | 68 | 69 |
70 |
71 |
72 | 93 |
94 |
95 | 107 | 111 | 112 | -------------------------------------------------------------------------------- /docs/_static/classic.css: -------------------------------------------------------------------------------- 1 | /* 2 | * classic.css_t 3 | * ~~~~~~~~~~~~~ 4 | * 5 | * Sphinx stylesheet -- classic theme. 6 | * 7 | * :copyright: Copyright 2007-2017 by the Sphinx team, see AUTHORS. 8 | * :license: BSD, see LICENSE for details. 9 | * 10 | */ 11 | 12 | @import url("basic.css"); 13 | 14 | /* -- page layout ----------------------------------------------------------- */ 15 | 16 | body { 17 | font-family: sans-serif; 18 | font-size: 100%; 19 | background-color: #11303d; 20 | color: #000; 21 | margin: 0; 22 | padding: 0; 23 | } 24 | 25 | div.document { 26 | background-color: #1c4e63; 27 | } 28 | 29 | div.documentwrapper { 30 | float: left; 31 | width: 100%; 32 | } 33 | 34 | div.bodywrapper { 35 | margin: 0 0 0 230px; 36 | } 37 | 38 | div.body { 39 | background-color: #ffffff; 40 | color: #000000; 41 | padding: 0 20px 30px 20px; 42 | } 43 | 44 | div.footer { 45 | color: #ffffff; 46 | width: 100%; 47 | padding: 9px 0 9px 0; 48 | text-align: center; 49 | font-size: 75%; 50 | } 51 | 52 | div.footer a { 53 | color: #ffffff; 54 | text-decoration: underline; 55 | } 56 | 57 | div.related { 58 | background-color: #133f52; 59 | line-height: 30px; 60 | color: #ffffff; 61 | } 62 | 63 | div.related a { 64 | color: #ffffff; 65 | } 66 | 67 | div.sphinxsidebar { 68 | } 69 | 70 | div.sphinxsidebar h3 { 71 | font-family: 'Trebuchet MS', sans-serif; 72 | color: #ffffff; 73 | font-size: 1.4em; 74 | font-weight: normal; 75 | margin: 0; 76 | padding: 0; 77 | } 78 | 79 | div.sphinxsidebar h3 a { 80 | color: #ffffff; 81 | } 82 | 83 | div.sphinxsidebar h4 { 84 | font-family: 'Trebuchet MS', sans-serif; 85 | color: #ffffff; 86 | font-size: 1.3em; 87 | font-weight: normal; 88 | margin: 5px 0 0 0; 89 | padding: 0; 90 | } 91 | 92 | div.sphinxsidebar p { 93 | color: #ffffff; 94 | } 95 | 96 | div.sphinxsidebar p.topless { 97 | margin: 5px 10px 10px 10px; 98 | } 99 | 100 | div.sphinxsidebar ul { 101 | margin: 10px; 102 | padding: 0; 103 | color: #ffffff; 104 | } 105 | 106 | div.sphinxsidebar a { 107 | color: #98dbcc; 108 | } 109 | 110 | div.sphinxsidebar input { 111 | border: 1px solid #98dbcc; 112 | font-family: sans-serif; 113 | font-size: 1em; 114 | } 115 | 116 | 117 | 118 | /* -- hyperlink styles ------------------------------------------------------ */ 119 | 120 | a { 121 | color: #355f7c; 122 | text-decoration: none; 123 | } 124 | 125 | a:visited { 126 | color: #355f7c; 127 | text-decoration: none; 128 | } 129 | 130 | a:hover { 131 | text-decoration: underline; 132 | } 133 | 134 | 135 | 136 | /* -- body styles ----------------------------------------------------------- */ 137 | 138 | div.body h1, 139 | div.body h2, 140 | div.body h3, 141 | div.body h4, 142 | div.body h5, 143 | div.body h6 { 144 | font-family: 'Trebuchet MS', sans-serif; 145 | background-color: #f2f2f2; 146 | font-weight: normal; 147 | color: #20435c; 148 | border-bottom: 1px solid #ccc; 149 | margin: 20px -20px 10px -20px; 150 | padding: 3px 0 3px 10px; 151 | } 152 | 153 | div.body h1 { margin-top: 0; font-size: 200%; } 154 | div.body h2 { font-size: 160%; } 155 | div.body h3 { font-size: 140%; } 156 | div.body h4 { font-size: 120%; } 157 | div.body h5 { font-size: 110%; } 158 | div.body h6 { font-size: 100%; } 159 | 160 | a.headerlink { 161 | color: #c60f0f; 162 | font-size: 0.8em; 163 | padding: 0 4px 0 4px; 164 | text-decoration: none; 165 | } 166 | 167 | a.headerlink:hover { 168 | background-color: #c60f0f; 169 | color: white; 170 | } 171 | 172 | div.body p, div.body dd, div.body li, div.body blockquote { 173 | text-align: justify; 174 | line-height: 130%; 175 | } 176 | 177 | div.admonition p.admonition-title + p { 178 | display: inline; 179 | } 180 | 181 | div.admonition p { 182 | margin-bottom: 5px; 183 | } 184 | 185 | div.admonition pre { 186 | margin-bottom: 5px; 187 | } 188 | 189 | div.admonition ul, div.admonition ol { 190 | margin-bottom: 5px; 191 | } 192 | 193 | div.note { 194 | background-color: #eee; 195 | border: 1px solid #ccc; 196 | } 197 | 198 | div.seealso { 199 | background-color: #ffc; 200 | border: 1px solid #ff6; 201 | } 202 | 203 | div.topic { 204 | background-color: #eee; 205 | } 206 | 207 | div.warning { 208 | background-color: #ffe4e4; 209 | border: 1px solid #f66; 210 | } 211 | 212 | p.admonition-title { 213 | display: inline; 214 | } 215 | 216 | p.admonition-title:after { 217 | content: ":"; 218 | } 219 | 220 | pre { 221 | padding: 5px; 222 | background-color: #eeffcc; 223 | color: #333333; 224 | line-height: 120%; 225 | border: 1px solid #ac9; 226 | border-left: none; 227 | border-right: none; 228 | } 229 | 230 | code { 231 | background-color: #ecf0f3; 232 | padding: 0 1px 0 1px; 233 | font-size: 0.95em; 234 | } 235 | 236 | th { 237 | background-color: #ede; 238 | } 239 | 240 | .warning code { 241 | background: #efc2c2; 242 | } 243 | 244 | .note code { 245 | background: #d6d6d6; 246 | } 247 | 248 | .viewcode-back { 249 | font-family: sans-serif; 250 | } 251 | 252 | div.viewcode-block:target { 253 | background-color: #f4debf; 254 | border-top: 1px solid #ac9; 255 | border-bottom: 1px solid #ac9; 256 | } 257 | 258 | div.code-block-caption { 259 | color: #efefef; 260 | background-color: #1c4e63; 261 | } -------------------------------------------------------------------------------- /docs/qualityAssurance.ui.window.html: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | qualityAssurance.ui.window module — qualityAssurance '' documentation 9 | 10 | 11 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 40 | 41 |
42 |
43 |
44 |
45 | 46 |
47 |

qualityAssurance.ui.window module

48 |
49 |
50 | class qualityAssurance.ui.window.QualityAssuranceWindow(parent, collection)
51 |

Bases: PySide.QtGui.QWidget

52 |
53 |
54 | staticMetaObject = <PySide.QtCore.QMetaObject object>
55 |
56 | 57 |
58 | 59 |
60 |
61 | qualityAssurance.ui.window.show(collection='modelling')
62 |
63 | 64 |
65 | 66 | 67 |
68 |
69 |
70 | 91 |
92 |
93 | 105 | 109 | 110 | -------------------------------------------------------------------------------- /docs/_static/sidebar.js: -------------------------------------------------------------------------------- 1 | /* 2 | * sidebar.js 3 | * ~~~~~~~~~~ 4 | * 5 | * This script makes the Sphinx sidebar collapsible. 6 | * 7 | * .sphinxsidebar contains .sphinxsidebarwrapper. This script adds 8 | * in .sphixsidebar, after .sphinxsidebarwrapper, the #sidebarbutton 9 | * used to collapse and expand the sidebar. 10 | * 11 | * When the sidebar is collapsed the .sphinxsidebarwrapper is hidden 12 | * and the width of the sidebar and the margin-left of the document 13 | * are decreased. When the sidebar is expanded the opposite happens. 14 | * This script saves a per-browser/per-session cookie used to 15 | * remember the position of the sidebar among the pages. 16 | * Once the browser is closed the cookie is deleted and the position 17 | * reset to the default (expanded). 18 | * 19 | * :copyright: Copyright 2007-2017 by the Sphinx team, see AUTHORS. 20 | * :license: BSD, see LICENSE for details. 21 | * 22 | */ 23 | 24 | $(function() { 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | // global elements used by the functions. 34 | // the 'sidebarbutton' element is defined as global after its 35 | // creation, in the add_sidebar_button function 36 | var bodywrapper = $('.bodywrapper'); 37 | var sidebar = $('.sphinxsidebar'); 38 | var sidebarwrapper = $('.sphinxsidebarwrapper'); 39 | 40 | // for some reason, the document has no sidebar; do not run into errors 41 | if (!sidebar.length) return; 42 | 43 | // original margin-left of the bodywrapper and width of the sidebar 44 | // with the sidebar expanded 45 | var bw_margin_expanded = bodywrapper.css('margin-left'); 46 | var ssb_width_expanded = sidebar.width(); 47 | 48 | // margin-left of the bodywrapper and width of the sidebar 49 | // with the sidebar collapsed 50 | var bw_margin_collapsed = '.8em'; 51 | var ssb_width_collapsed = '.8em'; 52 | 53 | // colors used by the current theme 54 | var dark_color = $('.related').css('background-color'); 55 | var light_color = $('.document').css('background-color'); 56 | 57 | function sidebar_is_collapsed() { 58 | return sidebarwrapper.is(':not(:visible)'); 59 | } 60 | 61 | function toggle_sidebar() { 62 | if (sidebar_is_collapsed()) 63 | expand_sidebar(); 64 | else 65 | collapse_sidebar(); 66 | } 67 | 68 | function collapse_sidebar() { 69 | sidebarwrapper.hide(); 70 | sidebar.css('width', ssb_width_collapsed); 71 | bodywrapper.css('margin-left', bw_margin_collapsed); 72 | sidebarbutton.css({ 73 | 'margin-left': '0', 74 | 'height': bodywrapper.height() 75 | }); 76 | sidebarbutton.find('span').text('»'); 77 | sidebarbutton.attr('title', _('Expand sidebar')); 78 | document.cookie = 'sidebar=collapsed'; 79 | } 80 | 81 | function expand_sidebar() { 82 | bodywrapper.css('margin-left', bw_margin_expanded); 83 | sidebar.css('width', ssb_width_expanded); 84 | sidebarwrapper.show(); 85 | sidebarbutton.css({ 86 | 'margin-left': ssb_width_expanded-12, 87 | 'height': bodywrapper.height() 88 | }); 89 | sidebarbutton.find('span').text('«'); 90 | sidebarbutton.attr('title', _('Collapse sidebar')); 91 | document.cookie = 'sidebar=expanded'; 92 | } 93 | 94 | function add_sidebar_button() { 95 | sidebarwrapper.css({ 96 | 'float': 'left', 97 | 'margin-right': '0', 98 | 'width': ssb_width_expanded - 28 99 | }); 100 | // create the button 101 | sidebar.append( 102 | '
«
' 103 | ); 104 | var sidebarbutton = $('#sidebarbutton'); 105 | light_color = sidebarbutton.css('background-color'); 106 | // find the height of the viewport to center the '<<' in the page 107 | var viewport_height; 108 | if (window.innerHeight) 109 | viewport_height = window.innerHeight; 110 | else 111 | viewport_height = $(window).height(); 112 | sidebarbutton.find('span').css({ 113 | 'display': 'block', 114 | 'margin-top': (viewport_height - sidebar.position().top - 20) / 2 115 | }); 116 | 117 | sidebarbutton.click(toggle_sidebar); 118 | sidebarbutton.attr('title', _('Collapse sidebar')); 119 | sidebarbutton.css({ 120 | 'color': '#FFFFFF', 121 | 'border-left': '1px solid ' + dark_color, 122 | 'font-size': '1.2em', 123 | 'cursor': 'pointer', 124 | 'height': bodywrapper.height(), 125 | 'padding-top': '1px', 126 | 'margin-left': ssb_width_expanded - 12 127 | }); 128 | 129 | sidebarbutton.hover( 130 | function () { 131 | $(this).css('background-color', dark_color); 132 | }, 133 | function () { 134 | $(this).css('background-color', light_color); 135 | } 136 | ); 137 | } 138 | 139 | function set_position_from_cookie() { 140 | if (!document.cookie) 141 | return; 142 | var items = document.cookie.split(';'); 143 | for(var k=0; k 4 | 5 | 6 | 7 | 8 | qualityAssurance.utils package — qualityAssurance '' documentation 9 | 10 | 11 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 40 | 41 |
42 | 69 | 98 |
99 |
100 | 112 | 116 | 117 | -------------------------------------------------------------------------------- /docs/qualityAssurance.ui.utils.html: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | qualityAssurance.ui.utils module — qualityAssurance '' documentation 9 | 10 | 11 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 40 | 41 |
42 |
43 |
44 |
45 | 46 |
47 |

qualityAssurance.ui.utils module

48 |
49 |
50 | qualityAssurance.ui.utils.getIconPath(name)
51 |

Get an icon path based on file name. All paths in the XBMLANGPATH variable 52 | processed to see if the provided icon can be found.

53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 |
Parameters:name (str) –
Returns:Icon path
Return type:str/None
65 |
66 | 67 |
68 |
69 | qualityAssurance.ui.utils.mayaWindow()
70 |

Get Maya’s main window.

71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 |
Return type:QMainWindow
79 |
80 | 81 |
82 | 83 | 84 |
85 |
86 |
87 | 108 |
109 |
110 | 122 | 126 | 127 | -------------------------------------------------------------------------------- /docs/qualityAssurance.checks.uv.html: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | qualityAssurance.checks.uv module — qualityAssurance '' documentation 9 | 10 | 11 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 40 | 41 |
42 |
43 |
44 |
45 | 46 |
47 |

qualityAssurance.checks.uv module

48 |
49 |
50 | class qualityAssurance.checks.uv.EmptyUVSets
51 |

Bases: qualityAssurance.utils.qa.QualityAssurance

52 |

Meshes will be checked to see if they have empty uv sets. When fixing the 53 | uv set will be deleted.

54 |
55 | 56 |
57 |
58 | class qualityAssurance.checks.uv.UnusedUVSets
59 |

Bases: qualityAssurance.utils.qa.QualityAssurance

60 |

Meshes will be checked to see if they have unused uv sets. When fixing the 61 | uv set will be deleted.

62 |
63 |
64 | ignoreUvSets
65 |
66 | 67 |
68 | 69 |
70 | 71 | 72 |
73 |
74 |
75 | 96 |
97 |
98 | 110 | 114 | 115 | -------------------------------------------------------------------------------- /docs/qualityAssurance.checks.skinning.html: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | qualityAssurance.checks.skinning module — qualityAssurance '' documentation 9 | 10 | 11 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 40 | 41 |
42 |
43 |
44 |
45 | 46 |
47 |

qualityAssurance.checks.skinning module

48 |
49 |
50 | class qualityAssurance.checks.skinning.MaximumInfluences
51 |

Bases: qualityAssurance.utils.qa.QualityAssurance

52 |

Skin clusters will be checked to see if they contain vertices that exceed 53 | the maximum influences. When fixing this error new skin weights will be 54 | applied by removing and normalizing the lowest influences that are 55 | exceeding the maximum amount. 56 | cluster.

57 |
58 | 59 |
60 |
61 | class qualityAssurance.checks.skinning.UnusedInfluences
62 |

Bases: qualityAssurance.utils.qa.QualityAssurance

63 |

Skin clusters will be checked to see if they contain unused influences. 64 | When fixing this error the unused influences will be removed from the skin 65 | cluster.

66 |
67 | 68 |
69 | 70 | 71 |
72 |
73 |
74 | 95 |
96 |
97 | 109 | 113 | 114 | -------------------------------------------------------------------------------- /docs/qualityAssurance.utils.api.html: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | qualityAssurance.utils.api module — qualityAssurance '' documentation 9 | 10 | 11 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 40 | 41 |
42 |
43 |
44 |
45 | 46 |
47 |

qualityAssurance.utils.api module

48 |
49 |
50 | qualityAssurance.utils.api.toMDagPath(node)
51 |

Convert a node into a OpenMaya.MDagPath.

52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 |
Parameters:node (str) –
Returns:MDagPath of parsed node
Return type:OpenMaya.MDagPath
64 |
65 | 66 |
67 |
68 | qualityAssurance.utils.api.toMObject(node)
69 |

Convert a node into a OpenMaya.MObject.

70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 |
Parameters:node (str) –
Returns:MObject of parsed node
Return type:OpenMaya.MObject
82 |
83 | 84 |
85 | 86 | 87 |
88 |
89 |
90 | 111 |
112 |
113 | 125 | 129 | 130 | -------------------------------------------------------------------------------- /docs/qualityAssurance.collections.html: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | qualityAssurance.collections package — qualityAssurance '' documentation 9 | 10 | 11 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 40 | 41 |
42 |
43 |
44 |
45 | 46 |
47 |

qualityAssurance.collections package

48 |

New collections can be added in the COLLECTIONS variable. Since this 49 | COLLECTIONS variable is an OrderedDict, it will keep the order. A 50 | collection can be defined by who will be using it. Currently it is 51 | divided by different specialties. Each specialty contains a list of 52 | categories that will be displayed. The category names link to the categories 53 | defined in the quality assurance checks themselves.

54 |
55 |
56 | qualityAssurance.collections.getCollections()
57 |
58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 |
Returns:Collection overview
Return type:OrderedDict
67 |
68 | 69 |
70 |
71 | qualityAssurance.collections.getCollectionsCategories()
72 |
73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 |
Returns:List of all collection names
Return type:list
82 |
83 | 84 |
85 | 86 | 87 |
88 |
89 |
90 | 111 |
112 |
113 | 125 | 129 | 130 | -------------------------------------------------------------------------------- /docs/qualityAssurance.checks.rigging.html: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | qualityAssurance.checks.rigging module — qualityAssurance '' documentation 9 | 10 | 11 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 40 | 41 |
42 |
43 |
44 |
45 | 46 |
47 |

qualityAssurance.checks.rigging module

48 |
49 |
50 | class qualityAssurance.checks.rigging.DeleteNonDeformerHistory
51 |

Bases: qualityAssurance.utils.qa.QualityAssurance

52 |

Meshes will be checked to see if they contain non-deformer history. Be 53 | carefull with this ceck as it is not always nessecary to fix this. History 54 | is not always a bad thing. When fixing this error the partial history will 55 | be baked.

56 |
57 |
58 | ignoreNodeTypes
59 |
60 | 61 |
62 | 63 |
64 |
65 | class qualityAssurance.checks.rigging.DeleteNonSetDrivenAnimation
66 |

Bases: qualityAssurance.utils.qa.QualityAssurance

67 |

Animation curves will be checked to see if they are not set driven keys. 68 | Non set driven keys should not be present in the scene and will be deleted 69 | when fixing this error.

70 |
71 | 72 |
73 | 74 | 75 |
76 |
77 |
78 | 99 |
100 |
101 | 113 | 117 | 118 | -------------------------------------------------------------------------------- /docs/qualityAssurance.checks.shaders.html: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | qualityAssurance.checks.shaders module — qualityAssurance '' documentation 9 | 10 | 11 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 40 | 41 |
42 |
43 |
44 |
45 | 46 |
47 |

qualityAssurance.checks.shaders module

48 |
49 |
50 | class qualityAssurance.checks.shaders.FaceAssignedShading
51 |

Bases: qualityAssurance.utils.qa.QualityAssurance

52 |

Shading groups will be checked to see if they contain component 53 | connections. When fixing it will force a shape connections. If you 54 | intentially have component assigment, it is best to skip this check.

55 |
56 | 57 |
58 |
59 | class qualityAssurance.checks.shaders.InitialShadingGroup
60 |

Bases: qualityAssurance.utils.qa.QualityAssurance

61 |

Meshes will be checked to see if they have the default shading group 62 | attached.

63 |
64 | 65 |
66 |
67 | class qualityAssurance.checks.shaders.NoShadingGroup
68 |

Bases: qualityAssurance.utils.qa.QualityAssurance

69 |

Meshes will be checked to see if they have a shading group attached.

70 |
71 | 72 |
73 | 74 | 75 |
76 |
77 |
78 | 99 |
100 |
101 | 113 | 117 | 118 | -------------------------------------------------------------------------------- /docs/modules.html: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | qualityAssurance — qualityAssurance '' documentation 9 | 10 | 11 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 40 | 41 |
42 |
43 |
44 |
45 | 46 |
47 |

qualityAssurance

48 | 88 |
89 | 90 | 91 |
92 |
93 |
94 | 115 |
116 |
117 | 129 | 133 | 134 | -------------------------------------------------------------------------------- /docs/qualityAssurance.checks.renderLayers.html: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | qualityAssurance.checks.renderLayers module — qualityAssurance '' documentation 9 | 10 | 11 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 40 | 41 |
42 |
43 |
44 |
45 | 46 |
47 |

qualityAssurance.checks.renderLayers module

48 |
49 |
50 | class qualityAssurance.checks.renderLayers.DuplicateAdjustments
51 |

Bases: qualityAssurance.utils.qa.QualityAssurance

52 |

The scene will be processed for duplicate renderlayer adjustments. When 53 | fixing these errors the duplicate adjustments will be removed.

54 |
55 | 56 |
57 |
58 | class qualityAssurance.checks.renderLayers.MismatchedAdjustments
59 |

Bases: qualityAssurance.utils.qa.QualityAssurance

60 |

The scene will be processed for mismatched renderlayer adjustments. When 61 | fixing these errors the mismatched adjustments will be reconnected.

62 |
63 | 64 |
65 |
66 | class qualityAssurance.checks.renderLayers.MissingAdjustments
67 |

Bases: qualityAssurance.utils.qa.QualityAssurance

68 |

The scene will be processed for missing renderlayer adjustments. When 69 | fixing these errors the missing adjustments will be reconnected.

70 |
71 | 72 |
73 | 74 | 75 |
76 |
77 |
78 | 99 |
100 |
101 | 113 | 117 | 118 | -------------------------------------------------------------------------------- /scripts/qualityAssurance/checks/skinning.py: -------------------------------------------------------------------------------- 1 | from maya import cmds, OpenMaya, OpenMayaAnim 2 | from ..utils import QualityAssurance, reference, skin, api 3 | 4 | 5 | class UnusedInfluences(QualityAssurance): 6 | """ 7 | Skin clusters will be checked to see if they contain unused influences. 8 | When fixing this error the unused influences will be removed from the skin 9 | cluster. 10 | """ 11 | def __init__(self): 12 | QualityAssurance.__init__(self) 13 | 14 | self._name = "Unused Influences" 15 | self._urgency = 1 16 | self._message = "{0} skin cluster(s) contain unused influences" 17 | self._categories = ["Skinning"] 18 | self._selectable = True 19 | 20 | # ------------------------------------------------------------------------ 21 | 22 | def _find(self): 23 | """ 24 | :return: Skin clusters with unused influences 25 | :rtype: generator 26 | """ 27 | skinClusters = self.ls(type="skinCluster") 28 | skinClusters = reference.removeReferenced(skinClusters) 29 | 30 | for skinCluster in skinClusters: 31 | mesh = cmds.skinCluster(skinCluster, query=True, geometry=True) 32 | if not mesh: 33 | continue 34 | 35 | weighted = cmds.skinCluster( 36 | skinCluster, 37 | query=True, 38 | weightedInfluence=True 39 | ) 40 | influences = cmds.skinCluster( 41 | skinCluster, 42 | query=True, 43 | influence=True 44 | ) 45 | 46 | for i in influences: 47 | if i not in weighted: 48 | yield skinCluster 49 | break 50 | 51 | def _fix(self, skinCluster): 52 | """ 53 | :param str skinCluster: 54 | """ 55 | weighted = cmds.skinCluster( 56 | skinCluster, 57 | query=True, 58 | weightedInfluence=True 59 | ) 60 | 61 | influences = cmds.skinCluster( 62 | skinCluster, 63 | query=True, 64 | influence=True 65 | ) 66 | 67 | for i in influences: 68 | if i not in weighted: 69 | cmds.skinCluster(skinCluster, edit=True, removeInfluence=i) 70 | 71 | 72 | class MaximumInfluences(QualityAssurance): 73 | """ 74 | Skin clusters will be checked to see if they contain vertices that exceed 75 | the maximum influences. When fixing this error new skin weights will be 76 | applied by removing and normalizing the lowest influences that are 77 | exceeding the maximum amount. 78 | cluster. 79 | """ 80 | def __init__(self): 81 | QualityAssurance.__init__(self) 82 | 83 | self._name = "Maximum Influences" 84 | self._message = "{0} skin cluster(s) exceed the maximum influences" 85 | self._categories = ["Skinning"] 86 | self._selectable = True 87 | 88 | # ------------------------------------------------------------------------ 89 | 90 | def _find(self): 91 | """ 92 | :return: Skin clusters which exceed maximum influences. 93 | :rtype: generator 94 | """ 95 | # variables 96 | obj = OpenMaya.MObject() 97 | 98 | # get skin cluster iterator 99 | iterator = self.lsApi(nodeType=OpenMaya.MFn.kSkinClusterFilter) 100 | 101 | # iterate skin cluster 102 | while not iterator.isDone(): 103 | # variables 104 | iterator.getDependNode(obj) 105 | depNode = OpenMaya.MFnDependencyNode(obj) 106 | skinFn = OpenMayaAnim.MFnSkinCluster(obj) 107 | skinCluster = depNode.name() 108 | 109 | # skin cluster data 110 | maintain = "{0}.maintainMaxInfluences".format(skinCluster) 111 | maintain = cmds.getAttr(maintain) 112 | maxInfluences = "{0}.maxInfluences".format(skinCluster) 113 | maxInfluences = cmds.getAttr(maxInfluences) 114 | 115 | # skip if max influences doesn't have to be maintained 116 | if not maintain: 117 | iterator.next() 118 | continue 119 | 120 | # get influences 121 | infIds, infPaths = skin.getInfluencesApi(skinFn) 122 | 123 | # get weights 124 | for weight in skin.getWeightsApiGenerator(skinFn, infIds): 125 | if len([w for w in weight.values() if w]) > maxInfluences: 126 | yield skinCluster 127 | break 128 | 129 | iterator.next() 130 | 131 | def _fix(self, skinCluster): 132 | """ 133 | :param str skinCluster: 134 | """ 135 | obj = api.toMObject(skinCluster) 136 | skinFn = OpenMayaAnim.MFnSkinCluster(obj) 137 | 138 | # normalize 139 | normalizePath = "{0}.normalizeWeights".format(skinCluster) 140 | normalize = cmds.getAttr(normalizePath) 141 | 142 | # max influence data 143 | maxInfluencesPath = "{0}.maxInfluences".format(skinCluster) 144 | maxInfluences = cmds.getAttr(maxInfluencesPath) 145 | 146 | # get influences 147 | infIds, infPaths = skin.getInfluencesApi(skinFn) 148 | infIdsLocked = { 149 | i: cmds.getAttr("{0}.liw".format(infPaths[i])) 150 | for _, i in infIds.iteritems() 151 | } 152 | 153 | # get weights 154 | weights = skin.getWeightsApi(skinFn, infIds) 155 | 156 | for vId, vWeights in weights.iteritems(): 157 | # variable 158 | nWeights = vWeights.copy() 159 | 160 | # sort weights 161 | ordered = sorted(vWeights.items(), key=lambda x: -x[1]) 162 | 163 | keepIndices = [ 164 | index 165 | for i, (index, weight) in enumerate(ordered) 166 | if i <= maxInfluences-1 167 | ] 168 | removeIndices = [ 169 | index 170 | for i, (index, weight) in enumerate(ordered) 171 | if i > maxInfluences-1 172 | ] 173 | 174 | # remove weights 175 | for i in removeIndices: 176 | nWeights[i] = 0 177 | 178 | # normalize weights 179 | if normalize == 1: 180 | # get normalizable weights 181 | normalizeIndices = [ 182 | i 183 | for i in keepIndices 184 | if not infIdsLocked.get(i) 185 | ] 186 | 187 | # if no weights can be normalized, normalize all 188 | if not normalizeIndices: 189 | normalizeIndices = keepIndices 190 | 191 | # get normalizing multiplier 192 | total = sum([vWeights.get(i) for i in normalizeIndices]) 193 | multiplier = 1/total 194 | 195 | # normalize indices 196 | for i in normalizeIndices: 197 | nWeights[i] = vWeights.get(i) * multiplier 198 | 199 | # set weights 200 | for infId, infValue in nWeights.items(): 201 | infAttr = "{0}.weightList[{1}].weights[{2}]".format( 202 | skinCluster, 203 | vId, 204 | infId 205 | ) 206 | cmds.setAttr(infAttr, infValue) 207 | -------------------------------------------------------------------------------- /scripts/qualityAssurance/utils/qa.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import traceback 3 | from maya import cmds, OpenMaya 4 | from . import decorators, undo, path 5 | 6 | 7 | class QualityAssurance(object): 8 | """ 9 | This is the base class that can be used to generate your own quality 10 | assurance checks. Key variables can be overwritten and a _find and _fix 11 | function should be implemented to complete the check, otherwise the class 12 | will not register as findable or checkable. 13 | """ 14 | def __init__(self): 15 | # variables 16 | self._name = "" 17 | self._urgency = 2 18 | self._categories = [] 19 | 20 | self._message = "" 21 | self._information = self.__doc__ 22 | 23 | self._selectable = False 24 | self._onSelected = False 25 | 26 | self._errors = [] 27 | 28 | # ------------------------------------------------------------------------ 29 | 30 | @property 31 | def name(self): 32 | """ 33 | :return: Quality assurance name 34 | :rtype: str 35 | """ 36 | return self._name 37 | 38 | @property 39 | @decorators.ifNoErrorsReturn(0) 40 | def state(self): 41 | """ 42 | If no errors can be found the state of the check is neutral which is 43 | set to a value of 0. Different levels of urgency can be implemented 44 | with increasing values. 45 | 46 | :return: Current state of quality assurance 47 | :rtype: int 48 | """ 49 | return self._urgency 50 | 51 | @property 52 | def categories(self): 53 | """ 54 | :return: Categories error check should belong too 55 | :rtype: list 56 | """ 57 | return self._categories 58 | 59 | # ------------------------------------------------------------------------ 60 | 61 | @property 62 | @decorators.ifNoErrorsReturn("") 63 | def message(self): 64 | """ 65 | Message informing the user about the state of the check and the number 66 | of errors. If not errors are found and empty string is returned. 67 | 68 | :return: Quality assurance message 69 | :rtype: str 70 | """ 71 | return self._message.format(len(self.errors)) 72 | 73 | @property 74 | def information(self): 75 | """ 76 | :return: Quality assurance information 77 | :rtype: list 78 | """ 79 | return self._information 80 | 81 | # ------------------------------------------------------------------------ 82 | 83 | @property 84 | def errors(self): 85 | """ 86 | :return: Quality assurance error list 87 | :rtype: list 88 | """ 89 | return self._errors 90 | 91 | # ------------------------------------------------------------------------ 92 | 93 | def isFindable(self): 94 | """ 95 | A quality assurance check becomes findable if a _find method is 96 | implemented, 97 | 98 | :return: Findable state of quality assurance check 99 | :rtype: bool 100 | """ 101 | return True if "_find" in dir(self) else False 102 | 103 | def find(self): 104 | """ 105 | Runs the _find method and appends all of the errors found to the 106 | error list. 107 | """ 108 | # check if is fixable 109 | if not self.isFindable(): 110 | raise RuntimeError( 111 | "{0} doesn't allow for error finding!".format(self.name) 112 | ) 113 | 114 | # reset errors list 115 | self._errors = [] 116 | 117 | # find errors 118 | for error in self._find(): 119 | if error in self.errors: 120 | continue 121 | 122 | self.errors.append(error) 123 | 124 | # ------------------------------------------------------------------------ 125 | 126 | def isFixable(self): 127 | """ 128 | A quality assurance check becomes fixable if a _fix method is 129 | implemented, 130 | 131 | :return: Fixable state of quality assurance check 132 | :rtype: bool 133 | """ 134 | return True if "_fix" in dir(self) else False 135 | 136 | def fix(self): 137 | """ 138 | Loops over each of the errors found and runs the _fix method on each 139 | error. The entire function is wrapped in an undo block for easy 140 | undoing. Each fix is also wrapped in a try function, meaning that if 141 | an error cannot be fixed, it will remain in the error list but it 142 | error the application. The _fix method should only take in 1 argument. 143 | """ 144 | # check if is fixable 145 | if not self.isFixable(): 146 | raise RuntimeError( 147 | "{0} doesn't allow for error fixing!".format(self.name) 148 | ) 149 | 150 | # remove errors 151 | with undo.UndoContext(): 152 | for error in self.errors[:]: 153 | # remove objects that might have been deleted in other 154 | # quality assurance checks. 155 | 156 | if self.isSelectable() and ( 157 | type(error) in [str, unicode] 158 | and not cmds.objExists(error) 159 | ): 160 | self._errors.remove(error) 161 | continue 162 | 163 | try: 164 | self._fix(error) 165 | self._errors.remove(error) 166 | except: 167 | traceback.print_exc() 168 | 169 | # ------------------------------------------------------------------------ 170 | 171 | @property 172 | def onSelected(self): 173 | """ 174 | :return: Only run quality assurance check in selection 175 | :rtype: bool 176 | """ 177 | return self._onSelected 178 | 179 | @onSelected.setter 180 | def onSelected(self, value): 181 | """ 182 | :param bool value: Only run on selection state 183 | """ 184 | self._onSelected = value 185 | 186 | # ------------------------------------------------------------------------ 187 | 188 | def isSelectable(self): 189 | """ 190 | :return: If the error list is selectable 191 | :rtype: bool 192 | """ 193 | return self._selectable 194 | 195 | def select(self): 196 | """ 197 | Select all current entries of the error list. 198 | """ 199 | if self.isSelectable() and self.errors: 200 | cmds.select(path.asFlatList(self.errors)) 201 | 202 | # ------------------------------------------------------------------------ 203 | 204 | def ls(self, **kwargs): 205 | """ 206 | Subclass of Maya's ls command. This command should be used to get the 207 | nodes in a quality assurance check. This will automatically take into 208 | account of the check should only run on selected objects or not. 209 | 210 | :return: Object list 211 | :rtype: list 212 | """ 213 | return cmds.ls(sl=self.onSelected, **kwargs) or [] 214 | 215 | def lsApi(self, nodeType=OpenMaya.MFn.kTransform): 216 | """ 217 | Get a OpenMaya.MItSelectionList object. This command should be used to 218 | get the nodes in a quality assurance check. This will automatically 219 | take into account of the check should only run on selected objects or 220 | not. 221 | 222 | :return: Object list 223 | :rtype: OpenMaya.MItSelectionList 224 | """ 225 | selectionList = OpenMaya.MSelectionList() 226 | 227 | if self.onSelected: 228 | # populate selection list with active selection 229 | OpenMaya.MGlobal.getActiveSelectionList(selectionList) 230 | else: 231 | # populate selection list with all object of nodetype 232 | iterator = OpenMaya.MItDependencyNodes(nodeType) 233 | while not iterator.isDone(): 234 | obj = iterator.thisNode() 235 | selectionList.add(obj) 236 | iterator.next() 237 | 238 | return OpenMaya.MItSelectionList(selectionList, nodeType) 239 | -------------------------------------------------------------------------------- /docs/qualityAssurance.utils.skin.html: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | qualityAssurance.utils.skin module — qualityAssurance '' documentation 9 | 10 | 11 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 40 | 41 |
42 |
43 |
44 |
45 | 46 |
47 |

qualityAssurance.utils.skin module

48 |
49 |
50 | qualityAssurance.utils.skin.getInfluencesApi(skinFn)
51 |

Get influence data from a skin cluster 52 | Code written by Tyler Thornock: http://www.charactersetup.com/home.html

53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 |
Parameters:skinFn (OpenMayaAnim.MFnSkinCluster) –
Returns:Influences dictionary and list
Return type:tuple()
65 |
66 | 67 |
68 |
69 | qualityAssurance.utils.skin.getWeightsApi(skinFn, infIds)
70 |

Get skin weights from a skin cluster reading its attributes. 71 | Code written by Tyler Thornock: http://www.charactersetup.com/home.html

72 | 73 | 74 | 75 | 76 | 81 | 82 | 84 | 85 | 87 | 88 | 89 |
Parameters:
    77 |
  • skinFn (OpenMayaAnim.MFnSkinCluster) –
  • 78 |
  • infIds (dict) –
  • 79 |
80 |
Returns:

Dictionary of skin weights.

83 |
Return type:

dict

86 |
90 |
91 | 92 |
93 |
94 | qualityAssurance.utils.skin.getWeightsApiGenerator(skinFn, infIds)
95 |

Get skin weights from a skin cluster reading its attributes. 96 | Code written by Tyler Thornock: http://www.charactersetup.com/home.html

97 | 98 | 99 | 100 | 101 | 106 | 107 | 109 | 110 | 112 | 113 | 114 |
Parameters:
    102 |
  • skinFn (OpenMayaAnim.MFnSkinCluster) –
  • 103 |
  • infIds (dict) –
  • 104 |
105 |
Returns:

Skin weights per vertex

108 |
Return type:

generator

111 |
115 |
116 | 117 |
118 | 119 | 120 |
121 |
122 |
123 | 144 |
145 |
146 | 158 | 162 | 163 | -------------------------------------------------------------------------------- /docs/qualityAssurance.utils.path.html: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | qualityAssurance.utils.path module — qualityAssurance '' documentation 9 | 10 | 11 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 40 | 41 |
42 |
43 |
44 |
45 | 46 |
47 |

qualityAssurance.utils.path module

48 |
49 |
50 | qualityAssurance.utils.path.asFlatList(input)
51 |

Convert the input to a flat list.

52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 |
Parameters:input
Returns:Flattened list
Return type:list
64 |
65 | 66 |
67 |
68 | qualityAssurance.utils.path.baseName(name)
69 |

This function will strip the namespaces and grouping information of a name. 70 | Useful when working with fullPaths but needing the base for naming.

71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 |
Parameters:name (str) –
Returns:Base name of string
Return type:str
83 |
84 | 85 |
86 |
87 | qualityAssurance.utils.path.namespace(name)
88 |

This function will return the namespace if any of the object

89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 |
Parameters:name (str) –
Returns:namespace
Return type:str/None
101 |
102 | 103 |
104 |
105 | qualityAssurance.utils.path.rootName(name)
106 |

This function will strip the grouping information of a name. 107 | Useful when working with fullPaths but needing the base for naming.

108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 |
Parameters:name (str) –
Returns:Root name of string
Return type:str
120 |
121 | 122 |
123 | 124 | 125 |
126 |
127 |
128 | 149 |
150 |
151 | 163 | 167 | 168 | --------------------------------------------------------------------------------