""" Checker classes """ import re from abc import ABCMeta, abstractmethod from maya import cmds from maya.api import OpenMaya from PySide2 import QtWidgets if not cmds.pluginInfo("meshChecker", q=True, loaded=True): try: cmds.loadPlugin("meshChecker") except RuntimeError: cmds.warning("Failed to load meshChecker plugin") if not cmds.pluginInfo("uvChecker", q=True, loaded=True): try: cmds.loadPlugin("uvChecker") except RuntimeError: cmds.warning("Failed to load uvChecker plugin") if not cmds.pluginInfo("findUvOverlaps", q=True, loaded=True): try: cmds.loadPlugin("findUvOverlaps") except RuntimeError: cmds.warning("Failed to load uvOverlap checker plugin") class Error(QtWidgets.QListWidgetItem): """ Custom error object """ def __init__(self, fullPath, errors=None, parent=None): # type: (str, list) -> (None) super(Error, self).__init__(parent) self.components = errors self.longName = fullPath self.shortName = fullPath.split("|")[-1] self.setText(self.shortName) class BaseChecker: """ Base abstract class for each checker """ __metaclass__ = ABCMeta __category__ = "" __name__ = "" isWarning = False isEnabled = True isFixable = False def __init__(self): self.errors = [] def __eq__(self, other): return self.name == self.name def __ne__(self, other): return not (self == other) def __lt__(self, other): return (self.category < other.category) @abstractmethod def checkIt(self, objs, settings=None): """ Check method """ pass @abstractmethod def fixIt(self): """ Fix method """ pass @property def name(self): """ Label property """ return self.__name__ @property def category(self): return self.__category__ class TriangleChecker(BaseChecker): """ Triangle checker class """ __name__ = "Triangles" __category__ = "Topology" isWarning = True def checkIt(self, obj, settings=None): # type: (list) -> (list) errorsDict = {} self.errors = [] errs = cmds.checkMesh(obj, c=0) for e in errs: base, comp = e.split(".") if base in errorsDict: errorsDict[base].append(e) else: errorsDict[base] = [e] for err_key in errorsDict: components = errorsDict[err_key] errorObj = Error(err_key, errorsDict[err_key]) self.errors.append(errorObj) return self.errors def fixIt(self): pass class NgonChecker(BaseChecker): __name__ = "N-gons" __category__ = "Topology" def checkIt(self, obj, settings=None): # type: (list) -> (list) errorsDict = {} self.errors = [] errs = cmds.checkMesh(obj, c=1) for e in errs: base, comp = e.split(".") if base in errorsDict: errorsDict[base].append(e) else: errorsDict[base] = [e] for err_key in errorsDict: components = errorsDict[err_key] errorObj = Error(err_key, errorsDict[err_key]) self.errors.append(errorObj) return self.errors def fixIt(self): pass class NonmanifoldEdgeChecker(BaseChecker): __name__ = "Nonmanifold Edges" __category__ = "Topology" def checkIt(self, obj, settings=None): # type: (list) -> (list) errorsDict = {} self.errors = [] errs = cmds.checkMesh(obj, c=2) for e in errs: base, comp = e.split(".") if base in errorsDict: errorsDict[base].append(e) else: errorsDict[base] = [e] for err_key in errorsDict: components = errorsDict[err_key] errorObj = Error(err_key, errorsDict[err_key]) self.errors.append(errorObj) return self.errors def fixIt(self): pass class NonmanifoldVertexChecker(BaseChecker): __name__ = "Nonmanifold Vertices" __category__ = "Topology" def checkIt(self, obj, settings=None): # type: (list) -> (list) self.errors = [] children = cmds.listRelatives(obj, fullPath=True, ad=True, type="mesh") for obj in children: try: errs = cmds.polyInfo(obj, nmv=True) if errs: errorObj = Error(obj, errs) self.errors.append(errorObj) except RuntimeError: pass return self.errors def fixIt(self): pass class LaminaFaceChecker(BaseChecker): __name__ = "Lamina Faces" __category__ = "Topology" def checkIt(self, obj, settings=None): # type: (list) -> (list) errorsDict = {} self.errors = [] errs = cmds.checkMesh(obj, c=3) for e in errs: base, comp = e.split(".") if base in errorsDict: errorsDict[base].append(e) else: errorsDict[base] = [e] for err_key in errorsDict: components = errorsDict[err_key] errorObj = Error(err_key, errorsDict[err_key]) self.errors.append(errorObj) return self.errors def fixIt(self): pass class BiValentFaceChecker(BaseChecker): __name__ = "Bi-valent Faces" __category__ = "Topology" def checkIt(self, obj, settings=None): # type: (list) -> (list) errorsDict = {} self.errors = [] errs = cmds.checkMesh(obj, c=4) for e in errs: base, comp = e.split(".") if base in errorsDict: errorsDict[base].append(e) else: errorsDict[base] = [e] for err_key in errorsDict: components = errorsDict[err_key] errorObj = Error(err_key, errorsDict[err_key]) self.errors.append(errorObj) return self.errors def fixIt(self): pass class ZeroAreaFaceChecker(BaseChecker): __name__ = "Zero Area Faces" __category__ = "Topology" def checkIt(self, obj, settings): # type: (list) -> (list) mfa = settings.getSettings()['maxFaceArea'] errorsDict = {} self.errors = [] errs = cmds.checkMesh(obj, c=5, maxFaceArea=mfa) for e in errs: base, comp = e.split(".") if base in errorsDict: errorsDict[base].append(e) else: errorsDict[base] = [e] for err_key in errorsDict: components = errorsDict[err_key] errorObj = Error(err_key, errorsDict[err_key]) self.errors.append(errorObj) return self.errors def fixIt(self): pass class MeshBorderEdgeChecker(BaseChecker): __name__ = "Mesh Border Edges" isWarning = True __category__ = "Topology" def checkIt(self, obj, settings=None): # type: (list) -> (list) errorsDict = {} self.errors = [] errs = cmds.checkMesh(obj, c=6) for e in errs: base, comp = e.split(".") if base in errorsDict: errorsDict[base].append(e) else: errorsDict[base] = [e] for err_key in errorsDict: components = errorsDict[err_key] errorObj = Error(err_key, errorsDict[err_key]) self.errors.append(errorObj) return self.errors def fixIt(self): pass class CreaseEdgeChecker(BaseChecker): __name__ = "Crease Edges" __category__ = "Topology" def checkIt(self, obj, settings=None): # type: (list) -> (list) errorsDict = {} self.errors = [] errs = cmds.checkMesh(obj, c=7) for e in errs: base, comp = e.split(".") if base in errorsDict: errorsDict[base].append(e) else: errorsDict[base] = [e] for err_key in errorsDict: components = errorsDict[err_key] errorObj = Error(err_key, errorsDict[err_key]) self.errors.append(errorObj) return self.errors def fixIt(self): pass class ZeroLengthEdgeChecker(BaseChecker): __name__ = "Zero-length Edges" __category__ = "Topology" def checkIt(self, obj, settings=None): # type: (list) -> (list) errorsDict = {} self.errors = [] errs = cmds.checkMesh(obj, c=8) for e in errs: base, comp = e.split(".") if base in errorsDict: errorsDict[base].append(e) else: errorsDict[base] = [e] for err_key in errorsDict: components = errorsDict[err_key] errorObj = Error(err_key, errorsDict[err_key]) self.errors.append(errorObj) return self.errors def fixIt(self): pass class VertexPntsChecker(BaseChecker): __name__ = "Vertex Pnts Attribute" __category__ = "Attribute" isFixable = True def checkIt(self, obj, settings=None): # type: (list) -> (list) self.errors = [] errs = cmds.checkMesh(obj, c=9) for e in errs: errObj = Error(e) self.errors.append(errObj) return self.errors def fixIt(self): mSel = OpenMaya.MSelectionList() for n, e in enumerate(self.errors): if cmds.objExists(e.longName): obj = e.longName mSel.add(obj) try: cmds.polyMoveVertex( obj, lt=(0, 0, 0), nodeState=1, ch=False) except RuntimeError: pass class EmptyGeometryChecker(BaseChecker): __name__ = "Empty Geometry" __category__ = "Topology" isFixable = False def checkIt(self, obj, settings=None): # type: (list) -> (list) self.errors = [] errs = cmds.checkMesh(obj, c=10) for e in errs: errObj = Error(e) self.errors.append(errObj) return self.errors def fixIt(self): pass class NameChecker(BaseChecker): __name__ = "Name" __category__ = "Name" isEnabled = False def checkIt(self, objs, settings=None): # type: (list) -> (list) self.errors = [] for obj in objs: try: pass except RuntimeError: pass return self.errors def fixIt(self): pass class ShapeNameChecker(BaseChecker): __name__ = "ShapeName" __category__ = "Name" isFixable = True def checkIt(self, root, settings=None): # type: (list) -> (list) self.errors = [] objs = cmds.listRelatives(root, ad=True, fullPath=True, type="transform") or [] objs.insert(0, root) for obj in objs: shapes = cmds.listRelatives( obj, children=True, fullPath=True, shapes=True) or [] if shapes: for shape in shapes: isIntermediate = cmds.getAttr( shape + ".intermediateObject") if isIntermediate: continue shortName = obj.split("|")[-1] shapeShortName = shape.split("|")[-1] if shortName + "Shape" != shapeShortName: err = Error(shape) self.errors.append(err) return self.errors def fixIt(self): for e in self.errors: shape = e.longName parent = cmds.listRelatives(shape, parent=True, fullPath=False)[0] newShapeName = parent + "Shape" cmds.rename(shape, newShapeName) class HistoryChecker(BaseChecker): __name__ = "History" __category__ = "Node" isEnabled = True isFixable = True def checkIt(self, root, settings=None): # type: (list) -> (list) self.errors = [] objs = cmds.listRelatives(root, ad=True, fullPath=True, type="transform") or [] objs.insert(0, root) for obj in objs: mesh = cmds.listRelatives(obj, children=True, type="mesh") if mesh is not None: for m in mesh: inMesh = cmds.listConnections(m + ".inMesh", source=True) if inMesh is not None: err = Error(obj) self.errors.append(err) return self.errors def fixIt(self): for e in self.errors: cmds.delete(e.longName, ch=True) class TransformChecker(BaseChecker): __name__ = "Transform" __category__ = "Attribute" def checkIt(self, root, settings=None): # type: (list) -> (list) ignore = [] self.errors = [] identity = OpenMaya.MMatrix.kIdentity mSel = OpenMaya.MSelectionList() objs = cmds.listRelatives(root, ad=True, fullPath=True, type="transform") or [] objs.insert(0, root) for n, i in enumerate(objs): mSel.add(i) dagPath = mSel.getDagPath(n) groupName = dagPath.fullPathName().split("|")[-1] if groupName in ignore: continue dagNode = OpenMaya.MFnDagNode(dagPath) transform = dagNode.transformationMatrix() if not transform == identity: errorObj = Error(i) self.errors.append(errorObj) return self.errors def fixIt(self): pass class LockedTransformChecker(BaseChecker): __name__ = "Locked Transform" __category__ = "Attribute" isFixable = True def __init__(self): super(LockedTransformChecker, self).__init__() self.attrs = ["tx", "ty", "tz", "rx", "ry", "rz", "sx", "sy", "sz"] def checkIt(self, root, settings=None): # type: (list) -> (list) self.errors = [] objs = cmds.listRelatives(root, ad=True, fullPath=True, type="transform") or [] objs.insert(0, root) for obj in objs: try: for at in self.attrs: isLocked = cmds.getAttr(obj + ".{}".format(at), lock=True) if isLocked: err = Error(obj) self.errors.append(err) break except RuntimeError: pass return self.errors def fixIt(self): for e in self.errors: for at in self.attrs: cmds.setAttr(e.longName + ".{}".format(at), lock=False) class SmoothPreviewChecker(BaseChecker): __name__ = "Smooth Preview" __category__ = "Attribute" isFixable = True def checkIt(self, root, settings=None): # type: (list) -> (list) self.errors = [] meshes = cmds.listRelatives(root, ad=True, fullPath=True, type="mesh") or [] for mesh in meshes: isSmooth = cmds.getAttr(mesh + ".displaySmoothMesh") if isSmooth: err = Error(mesh) self.errors.append(err) return self.errors def fixIt(self): for e in self.errors: cmds.setAttr(e.longName + ".displaySmoothMesh", 0) class KeyframeChecker(BaseChecker): __name__ = "Keyframe" __category__ = "Attribute" isFixable = True def checkIt(self, root, settings=None): # type: (list) -> (list) self.errors = [] keyNodes = ["animCurveTU", "animCurveTA", "animCurveTL"] objs = cmds.listRelatives(root, ad=True, fullPath=True, type="transform") or [] objs.insert(0, root) for i in objs: conns = cmds.listConnections(i, source=True) keys = [] if conns is None: continue for c in conns: if cmds.objectType(c) in keyNodes: keys.append(c) if keys: err = Error(i, keys) self.errors.append(err) return self.errors def fixIt(self): for e in self.errors: cmds.delete(e.components) class UnusedVertexChecker(BaseChecker): """ Unused vertex checker class """ __name__ = "Unused Vertices" __category__ = "Topology" def __init__(self): super(UnusedVertexChecker, self).__init__() def checkIt(self, obj, settings=None): # type: (list) -> (list) errorsDict = {} self.errors = [] errs = cmds.checkMesh(obj, c=11) for e in errs: base, comp = e.split(".") if base in errorsDict: errorsDict[base].append(e) else: errorsDict[base] = [e] for err_key in errorsDict: components = errorsDict[err_key] errorObj = Error(err_key, errorsDict[err_key]) self.errors.append(errorObj) return self.errors def fixIt(self): """ Unused vertices ARE fixable """ pass class IntermediateObjectChecker(BaseChecker): __name__ = "Intermediate Object" __category__ = "Node" isFixable = True def checkIt(self, root, settings=None): # type: (list) -> (list) self.errors = [] meshes = cmds.listRelatives(root, ad=True, fullPath=True, type="mesh") for mesh in meshes: isIntermediate = cmds.getAttr(mesh + ".intermediateObject") if isIntermediate: err = Error(mesh) self.errors.append(err) return self.errors def fixIt(self): for e in self.errors: shape = e.longName if cmds.objExists(shape): parents = cmds.listRelatives( shape, fullPath=True, parent=True) or [] for i in parents: # Delete history for parents cmds.delete(i, ch=True) try: cmds.delete(shape) except ValueError: pass class InstanceShapeChecker(BaseChecker): __name__ = "Instance Shape" __category__ = "Node" def checkIt(self, obj, settings=None): # type: (list) -> (list) self.errors = [] errs = cmds.checkMesh(obj, c=12) for e in errs: errObj = Error(e) self.errors.append(errObj) return self.errors def fixIt(self): pass class ConnectionChecker(BaseChecker): __name__ = "Connections" __category__ = "Node" def checkIt(self, obj, settings=None): # type: (list) -> (list) self.errors = [] errs = cmds.checkMesh(obj, c=13) for e in errs: errObj = Error(e) self.errors.append(errObj) return self.errors def fixIt(self): pass class DisplayLayerCheck(BaseChecker): __name__ = "Display layers" __category__ = "other" isFixable = True def checkIt(self, root, settings=None): # type: (list) -> (list) self.errors = [] objs = cmds.listRelatives(root, ad=True, fullPath=True, type="transform") or [] objs.insert(0, root) for obj in objs: layers = cmds.listConnections(obj + ".drawOverride") or [] if layers: err = Error(obj, layers) self.errors.append(err) return self.errors def fixIt(self): for e in self.errors: layers = e.components node = e.longName for layer in layers: cmds.disconnectAttr( layer + ".drawInfo", node + ".drawOverride") class UnusedLayerChecker(BaseChecker): __name__ = "Unused layers" __category__ = "other" isFixable = True def checkIt(self, root, settings=None): # type: (list) -> (list) self.errors = [] layers = cmds.ls(type="displayLayer") layers.remove("defaultLayer") for layer in layers: contents = cmds.editDisplayLayerMembers( layer, q=True, fullNames=True) if contents is None: err = Error(layer, [layer]) self.errors.append(err) return self.errors def fixIt(self): for e in self.errors: try: cmds.delete(e.longName) except RuntimeError: pass class Map1Checker(BaseChecker): __name__ = "UVSet to map1" __category__ = "UV" isFixable = True def checkIt(self, root, settings=None): # type: (list) -> (list) self.errors = [] meshes = cmds.listRelatives(root, ad=True, fullPath=True, type="mesh") or [] for mesh in meshes: curUVSet = cmds.polyUVSet(mesh, q=True, currentUVSet=True)[0] if curUVSet != "map1": err = Error(mesh) self.errors.append(err) return self.errors def fixIt(self): for e in self.errors: cmds.polyUVSet(e.longName, uvSet="map1", currentUVSet=True) class NegativeUvChecker(BaseChecker): __name__ = "UVs in negative space" __category__ = "UV" def checkIt(self, obj, settings=None): self.errors = [] errorsDict = {} errs = cmds.checkUV(obj, c=4) for e in errs: base, comp = e.split(".") if base in errorsDict: errorsDict[base].append(e) else: errorsDict[base] = [e] for err_key in errorsDict: components = errorsDict[err_key] errorObj = Error(err_key, errorsDict[err_key]) self.errors.append(errorObj) return self.errors def fixIt(self): pass class UdimIntersectionChecker(BaseChecker): __name__ = "UDIM intersection" __category__ = "UV" def checkIt(self, obj, settings=None): # type: (list) -> (list) self.errors = [] errorsDict = {} errs = cmds.checkUV(obj, c=0) for e in errs: base, comp = e.split(".") if base in errorsDict: errorsDict[base].append(e) else: errorsDict[base] = [e] for err_key in errorsDict: components = errorsDict[err_key] errorObj = Error(err_key, errorsDict[err_key]) self.errors.append(errorObj) return self.errors def fixIt(self): pass class UnassignedUvChecker(BaseChecker): __name__ = "Unassigned UVs" __category__ = "UV" def checkIt(self, obj, settings=None): # type: (list) -> (list) self.errors = [] errorsDict = {} errs = cmds.checkUV(obj, c=3) for e in errs: base, comp = e.split(".") if base in errorsDict: errorsDict[base].append(e) else: errorsDict[base] = [e] for err_key in errorsDict: components = errorsDict[err_key] errorObj = Error(err_key, errorsDict[err_key]) self.errors.append(errorObj) return self.errors def fixIt(self): pass class UnmappedPolygonFaceChecker(BaseChecker): __name__ = "Unmapped polygon faces" __category__ = "UV" def checkIt(self, obj, settings=None): # type: (list) -> (list) self.errors = [] errorsDict = {} errs = cmds.checkUV(obj, c=1) for e in errs: base, comp = e.split(".") if base in errorsDict: errorsDict[base].append(e) else: errorsDict[base] = [e] for err_key in errorsDict: components = errorsDict[err_key] errorObj = Error(err_key, errorsDict[err_key]) self.errors.append(errorObj) return self.errors def fixIt(self): pass class ZeroAreaUVFaceChecker(BaseChecker): __name__ = "Zero area UV Faces" __category__ = "UV" def checkIt(self, obj, settings=None): # type: (list) -> (list) self.errors = [] errorsDict = {} errs = cmds.checkUV(obj, c=2) for e in errs: base, comp = e.split(".") if base in errorsDict: errorsDict[base].append(e) else: errorsDict[base] = [e] for err_key in errorsDict: components = errorsDict[err_key] errorObj = Error(err_key, errorsDict[err_key]) self.errors.append(errorObj) return self.errors def fixIt(self): pass class ConcaveUVChecker(BaseChecker): __name__ = "Concave UV Faces" __category__ = "UV" def checkIt(self, obj, settings=None): # type: (list) -> (list) self.errors = [] errorsDict = {} errs = cmds.checkUV(obj, c=5) for e in errs: base, comp = e.split(".") if base in errorsDict: errorsDict[base].append(e) else: errorsDict[base] = [e] for err_key in errorsDict: components = errorsDict[err_key] errorObj = Error(err_key, errorsDict[err_key]) self.errors.append(errorObj) return self.errors def fixIt(self): pass class ReversedUVChecker(BaseChecker): __name__ = "Reversed UV Faces" __category__ = "UV" isWarning = True def checkIt(self, obj, settings=None): # type: (list) -> (list) self.errors = [] errorsDict = {} errs = cmds.checkUV(obj, c=6) for e in errs: base, comp = e.split(".") if base in errorsDict: errorsDict[base].append(e) else: errorsDict[base] = [e] for err_key in errorsDict: components = errorsDict[err_key] errorObj = Error(err_key, errorsDict[err_key]) self.errors.append(errorObj) return self.errors def fixIt(self): pass class UvOverlapChecker(BaseChecker): __name__ = "UV Overlaps" __category__ = "UV" isEnabled = False def checkIt(self, root, settings=None): self.errors = [] objs = cmds.listRelatives(root, ad=True, fullPath=True, type="mesh") or [] mSel = OpenMaya.MSelectionList() for obj in objs: mSel.add(obj) for i in range(mSel.length()): dagPath = mSel.getDagPath(i) try: dagPath.extendToShape() except RuntimeError: # Not mesh. Do no nothing pass return self.errors def fixIt(self): pass class SelectionSetChecker(BaseChecker): __name__ = "Selection Sets" __category__ = "other" isFixable = True def getSets(self, path, typ): if typ == "transform": conns = cmds.listConnections(path + ".instObjGroups") or [] return [i for i in conns if cmds.objectType(i) == "objectSet"] elif typ == "shape": conns = cmds.listConnections( path + ".instObjGroups.objectGroups") or [] return [i for i in conns if cmds.objectType(i) == "objectSet"] else: pass return [] def checkIt(self, root, settings=None): # type: (list) -> (list) self.errors = [] objectSets = [] ignore = ["modelPanel[0-9]ViewSelectedSet"] objs = cmds.listRelatives(root, ad=True, fullPath=True, type="transform") or [] objs.insert(0, root) for obj in objs: shapes = cmds.listRelatives( obj, children=True, fullPath=True, shapes=True) or [] for shape in shapes: objectSets.extend(self.getSets(shape, "shape")) objectSets.extend(self.getSets(obj, "transform")) objectSets = list(set(objectSets)) for objSet in objectSets: for i in ignore: if re.match(i, objSet) is None: err = Error(objSet) self.errors.append(err) return self.errors def fixIt(self): for e in self.errors: try: cmds.delete(e.longName) except Exception: pass class ColorSetChecker(BaseChecker): __name__ = "Color Sets" __category__ = "other" isFixable = True def checkIt(self, root, settings=None): # type: (list) -> (list) # Reset result self.errors = [] objs = cmds.listRelatives(root, ad=True, fullPath=True, type="mesh") or [] for obj in objs: try: allColorSets = cmds.polyColorSet( obj, q=True, allColorSets=True) if allColorSets is None: continue else: err = Error(obj) self.errors.append(err) except RuntimeError: pass return self.errors def fixIt(self): for i in self.errors: allSets = cmds.polyColorSet( i.longName, q=True, allColorSets=True) or [] for s in allSets: cmds.polyColorSet(i.longName, delete=True, colorSet=s) CHECKERS = [ NameChecker, ShapeNameChecker, HistoryChecker, TransformChecker, LockedTransformChecker, SmoothPreviewChecker, KeyframeChecker, TriangleChecker, NgonChecker, NonmanifoldEdgeChecker, NonmanifoldVertexChecker, LaminaFaceChecker, BiValentFaceChecker, ZeroAreaFaceChecker, MeshBorderEdgeChecker, CreaseEdgeChecker, ZeroLengthEdgeChecker, VertexPntsChecker, EmptyGeometryChecker, UnusedVertexChecker, IntermediateObjectChecker, InstanceShapeChecker, ConnectionChecker, DisplayLayerCheck, UnusedLayerChecker, Map1Checker, NegativeUvChecker, UdimIntersectionChecker, UnassignedUvChecker, UnmappedPolygonFaceChecker, ZeroAreaUVFaceChecker, ConcaveUVChecker, ReversedUVChecker, UvOverlapChecker, SelectionSetChecker, ColorSetChecker]