From d1b39a1819421097f1e9f9a410eb37cb1ac6e679 Mon Sep 17 00:00:00 2001 From: Marcus Ottosson Date: Mon, 7 Jun 2021 07:33:41 +0100 Subject: [PATCH] Lots of usability updates --- cmdx.py | 130 ++++++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 116 insertions(+), 14 deletions(-) diff --git a/cmdx.py b/cmdx.py index 16f0857..d28e806 100644 --- a/cmdx.py +++ b/cmdx.py @@ -1084,6 +1084,36 @@ def dumps(self, indent=4, sort_keys=True, preserve_order=False): sort_keys=sort_keys ) + def index(self, plug): + """ Find index of `attr` in its owning node + _____________ + | | + | Friction o -> 126 + | Mass o -> 127 + | Color R o -> 128 + | Color G o -> 129 + | Color B o -> 130 + | | + |_____________| + + # TODO: This is really slow + + """ + + assert isinstance(plug, Plug), "%r was not a cmdx.Plug" % plug + + node = plug.node() + + for i in range(node._fn.attributeCount()): + attr = node._fn.attribute(i) + fn = om.MFnAttribute(attr) + + if fn.shortName == plug.name(long=False): + return i + + # Can happen if asking for an index of a plug from another node + raise ValueError("Index of '%s' not found" % plug.name()) + def type(self): """Return type name @@ -1680,7 +1710,7 @@ def mapTo(self, other, time=None): # Alias root = assembly - def parent(self, type=None): + def parent(self, type=None, filter=None): """Return parent of node Arguments: @@ -1694,6 +1724,13 @@ def parent(self, type=None): >>> not child.parent(type="camera") True >>> parent.parent() + >>> child.parent(filter=om.MFn.kTransform) == parent + True + >>> child.parent(filter=om.MFn.kJoint) is None + True + + Returns: + parent (Node): If any, else None """ @@ -1704,11 +1741,14 @@ def parent(self, type=None): cls = self.__class__ + if filter is not None and not mobject.hasFn(filter): + return None + if not type or type == self._fn.__class__(mobject).typeName: return cls(mobject) @protected - def lineage(self, type=None): + def lineage(self, type=None, filter=None): """Yield parents all the way up a hierarchy Example: @@ -1730,7 +1770,10 @@ def lineage(self, type=None): parent = self.parent(type) while parent is not None: yield parent - parent = parent.parent(type) + parent = parent.parent(type, filter) + + # Alias + parenthood = lineage @protected def children(self, @@ -2740,7 +2783,10 @@ def clone(self, name, shortName=None, niceName=None): if self.isCompound: raise TypeError("Compound plugs are unsupported") - niceName = niceName or self.niceName + if niceName is False: + niceName = None + else: + niceName = niceName or self.niceName # There is no way to tell whether the niceName of # a plug is automatically generated by Maya or if @@ -2864,6 +2910,10 @@ def nextAvailableIndex(self, startIndex=0): # No connections means the first index is available return 0 + def pull(self): + """Pull on a plug, without seriasing any value. For performance""" + self._mplug.asMObject() + def append(self, value, autofill=False): """Add `value` to end of self, which is an array @@ -3005,7 +3055,7 @@ def asTransformationMatrix(self, time=None): def asEulerRotation(self, order=kXYZ, time=None): value = self.read(time=time) - return om.MEulerRotation(value, order) + return Euler(om.MEulerRotation(value, order)) asEuler = asEulerRotation @@ -3018,6 +3068,10 @@ def asVector(self, time=None): assert self.isArray or self.isCompound, "'%s' not an array" % self return Vector(self.read(time=time)) + def asPoint(self, time=None): + assert self.isArray or self.isCompound, "'%s' not an array" % self + return Point(self.read(time=time)) + def asTime(self, time=None): attr = self._mplug.attribute() type = attr.apiType() @@ -3050,6 +3104,30 @@ def connected(self): return self.connection(destination=False) is not None + def animated(self, recursive=True): + """Return whether this attribute is connected to an animCurve + + Arguments: + recursive (bool, optional): Should I travel to connected + attributes in search of an animCurve, or only look to + the immediate connection? + + """ + + other = self.connection(destination=False, plug=True) + while other is not None: + + node = other.node() + if node.object().hasFn(om.MFn.kAnimCurve): + return True + + if not recursive: + break + + other = other.connection(destination=False, plug=True) + + return False + def lock(self): """Convenience function for plug.locked = True @@ -3326,6 +3404,12 @@ def fn(self): elif typ == om.MFn.kUnitAttribute: fn = om.MFnUnitAttribute(attr) + elif typ in (om.MFn.kDoubleLinearAttribute, + om.MFn.kFloatLinearAttribute, + om.MFn.kDoubleAngleAttribute, + om.MFn.kFloatAngleAttribute): + return om.MFnUnitAttribute(attr) + elif typ == om.MFn.kTypedAttribute: fn = om.MFnTypedAttribute(attr) @@ -3338,7 +3422,9 @@ def fn(self): else: raise TypeError( - "Couldn't figure out function set for %s" % self + "Couldn't figure out function set for '%s.%s'" % ( + self.path(), attr.apiTypeStr + ) ) return fn @@ -3805,6 +3891,7 @@ def node(self): as_euler = asEuler as_quaternion = asQuaternion as_vector = asVector + as_point = asPoint as_time = asTime channel_box = channelBox lock_and_hide = lockAndHide @@ -4148,7 +4235,7 @@ def __add__(self, value): self.z + value, ) - return super(Vector, self).__add__(value) + return Vector(super(Vector, self).__add__(value)) def __iadd__(self, value): if isinstance(value, (int, float)): @@ -4158,13 +4245,13 @@ def __iadd__(self, value): self.z + value, ) - return super(Vector, self).__iadd__(value) + return Vector(super(Vector, self).__iadd__(value)) def dot(self, value): - return super(Vector, self).__mul__(value) + return Vector(super(Vector, self).__mul__(value)) def cross(self, value): - return super(Vector, self).__xor__(value) + return Vector(super(Vector, self).__xor__(value)) # Alias, it can't take anything other than values @@ -4824,6 +4911,9 @@ def _python_to_mod(value, plug, mod): value = om.MAngle(value, om.MAngle.kRadians) _python_to_mod(value, plug[index], mod) + elif isinstance(value, om.MQuaternion): + _python_to_mod(value.asEulerRotation(), plug, mod) + else: raise TypeError( "Unsupported plug type for modifier: %s" % type(value) @@ -5209,7 +5299,6 @@ def setNiceName(self, plug, value=True): plug = Plug(Node(plug.node()), plug) assert isinstance(plug, Plug), "%s was not a plug" % plug - assert plug._mplug.isDynamic, "%s was not a dynamic attribute" % plug self._niceNames.append((plug, value)) @record_history @@ -6132,8 +6221,11 @@ def createNode(self, type, name=None, parent=None): try: mobj = self._modifier.createNode(type, parent) - except TypeError: - raise TypeError("'%s' is not a valid node type" % type) + except TypeError as e: + if e.message == "parent is not a transform type": + raise TypeError("'%s' is not a transform type," % parent) + else: + raise TypeError("'%s' is not a valid node type," % type) template = self._opts["template"] if name or template: @@ -6156,6 +6248,9 @@ def parent(self, node, parent=None): if SAFE_MODE: self._modifier.doIt() + reparent = parent + reparentNode = parent + if ENABLE_PEP8: create_node = createNode @@ -7083,8 +7178,12 @@ def __init__(self, label, **kwargs): kwargs.pop("fields", None) kwargs.pop("label", None) + # Account for spaces in label + # E.g. "Hard Pin" -> "hardPin" + name = label[0].lower() + label[1:].replace(" ", "") + super(Divider, self).__init__( - label, fields=(label,), label=" ", **kwargs + name, fields=(label,), label=" ", **kwargs ) @@ -7557,6 +7656,9 @@ def __init__(self, *args, **kwargs): super(_apiUndo, self).__init__(*args, **kwargs) _apiUndo._aliveCount += 1 + self.undoId = None + self.redoId = None + def __del__(self): _apiUndo._aliveCount -= 1