Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implements a different approach for __repr__ #78

Merged
merged 8 commits into from
Feb 27, 2024
127 changes: 114 additions & 13 deletions cmdx.py
Original file line number Diff line number Diff line change
Expand Up @@ -512,6 +512,8 @@ class Node(object):
>>> transform["tx"] = 5
>>> transform["worldMatrix"][0] >> decompose["inputMatrix"]
>>> decompose["outputTranslate"]
cmdx.Plug("decompose", "outputTranslate") == (5.0, 0.0, 0.0)
>>> decompose["outputTranslate"].read()
chelloiaco marked this conversation as resolved.
Show resolved Hide resolved
(5.0, 0.0, 0.0)

"""
Expand Down Expand Up @@ -548,7 +550,8 @@ def __str__(self):
return self.name(namespace=True)

def __repr__(self):
return self.name(namespace=True)
cls_name = '{}.{}'.format(__name__, self.__class__.__name__)
return '{}("{}")'.format(cls_name, self.name(namespace=True))

def __add__(self, other):
"""Support legacy + '.attr' behavior
Expand Down Expand Up @@ -576,9 +579,12 @@ def __getitem__(self, key):
optionally pass tuple to include unit.

Example:
>>> _new()
>>> node = createNode("transform")
>>> node["translate"] = (1, 1, 1)
>>> node["translate", Meters]
cmdx.Plug("transform1", "translate") == (0.01, 0.01, 0.01)
>>> node["translate", Meters].read()
(0.01, 0.01, 0.01)

"""
Expand Down Expand Up @@ -625,6 +631,8 @@ def __setitem__(self, key, value):
>>> node["rotateX", Degrees] = 1.0
>>> node["rotateX"] = Degrees(1)
>>> node["rotateX", Degrees]
cmdx.Plug("myNode", "rotateX") == 1.0
>>> node["rotateX", Degrees].read()
1.0
>>> node["myDist"] = Distance()
>>> node["myDist"] = node["translateX"]
Expand Down Expand Up @@ -978,9 +986,12 @@ def update(self, attrs):
attrs (dict): Key/value pairs of name and attribute

Examples:
>>> _new()
>>> node = createNode("transform")
>>> node.update({"tx": 5.0, ("ry", Degrees): 30.0})
>>> node["tx"]
cmdx.Plug("transform1", "translateX") == 5.0
>>> node["tx"].read()
5.0

"""
Expand All @@ -996,16 +1007,23 @@ def clear(self):
values, freeing up memory at the expense of performance.

Example:
>>> _new()
>>> node = createNode("transform")
>>> node["translateX"] = 5
>>> node["translateX"]
cmdx.Plug("transform1", "translateX") == 5.0
>>> node["translateX"].read()
5.0
>>> # Plug was reused
>>> node["translateX"]
cmdx.Plug("transform1", "translateX") == 5.0
>>> node["translateX"].read()
5.0
>>> # Value was reused
>>> node.clear()
>>> node["translateX"]
cmdx.Plug("transform1", "translateX") == 5.0
>>> node["translateX"].read()
5.0
>>> # Plug and value was recomputed

Expand Down Expand Up @@ -1513,22 +1531,33 @@ def __str__(self):
return self.path()

def __repr__(self):
return self.path()
cls_name = '{}.{}'.format(__name__, self.__class__.__name__)
return '{}("{}")'.format(cls_name, self.name())

def __or__(self, other):
"""Syntax sugar for finding a child

Examples:
>>> _new()
>>> parent = createNode("transform", "parent")
>>> child = createNode("transform", "child", parent)
>>> child = createNode("transform", "child", parent)
>>> parent | "child"
|parent|child
cmdx.DagNode("child")
>>> (parent | "child").path() in (
... '|parent|child',
... u'|parent|child'
... )
True

# Stackable too
>>> grand = createNode("transform", "grand", child)
>>> grand = createNode("transform", "grand", child)
>>> parent | "child" | "grand"
|parent|child|grand
cmdx.DagNode("grand")
>>> (parent | "child" | "grand").path() in (
... '|parent|child|grand',
... u'|parent|child|grand'
... )
True

"""

Expand Down Expand Up @@ -2302,7 +2331,13 @@ def flatten(self, type=None):
>>> parent = cmds.sets([cc, c], name="parent")
>>> mainset = encode(parent)
>>> sorted(mainset.flatten(), key=lambda n: n.name())
[|a, |b, |c]
[cmdx.DagNode("a"), cmdx.DagNode("b"), cmdx.DagNode("c")]
>>> result = sorted([n.name() for n in mainset.flatten()])
>>> result in (
... ['a', 'b', 'c'],
... [u'a', u'b', u'c']
... )
True

"""

Expand Down Expand Up @@ -2558,11 +2593,16 @@ def __add__(self, other):
than adding to longName, e.g. node["translate"] + "X"

Example:
>>> _new()
>>> node = createNode("transform")
>>> node["tx"] = 5
>>> node["translate"] + "X"
cmdx.Plug("transform1", "translateX") == 5.0
>>> (node["translate"] + "X").read()
5.0
>>> node["t"] + "x"
cmdx.Plug("transform1", "translateX") == 5.0
>>> (node["t"] + "x").read()
5.0
>>> try:
... node["t"] + node["r"]
Expand Down Expand Up @@ -2591,17 +2631,24 @@ def __iadd__(self, other):
"""Support += operator, for .append()

Example:
>>> _new()
>>> node = createNode("transform")
>>> node["myArray"] = Double(array=True)
>>> node["myArray"].append(1.0)
>>> node["myArray"].extend([2.0, 3.0])
>>> node["myArray"] += 5.1
>>> node["myArray"] += [1.1, 2.3, 999.0]
>>> node["myArray"][0]
cmdx.Plug("transform1", "myArray[0]") == 1.0
>>> node["myArray"][0].read()
1.0
>>> node["myArray"][6]
cmdx.Plug("transform1", "myArray[6]") == 999.0
>>> node["myArray"][6].read()
999.0
>>> node["myArray"][-1]
cmdx.Plug("transform1", "myArray[6]") == 999.0
>>> node["myArray"][-1].read()
999.0

"""
Expand All @@ -2627,7 +2674,20 @@ def __str__(self):
return str(self.read())

def __repr__(self):
return str(self.read())
cls_name = '{}.{}'.format(__name__, self.__class__.__name__)
if self._mplug.attribute().apiType() == om.MFn.kCompoundAttribute:
Copy link
Owner

@mottosso mottosso Feb 22, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would go the opposite route; include rather than exclude. There are lots of types, and if we aren't going to test them all then we should only include the ones we know work. I expect printing meshes and MObject types to also be problematic. Only the float, double, bool and string are interesting, possibly enum too.

Copy link
Contributor Author

@chelloiaco chelloiaco Feb 22, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of adding more checks into the repr, I thought, since self.typeClass() is doing all the heavy lifting, I should use that info instead.

I had to add a couple more types since a lot of these are types of some basic queries like node['transform'] or node['rotateX'] but this is what I came up with:

...
valid_types = (Angle, Boolean, Distance, Double, Double3, Enum, Float,
               Integer, Long, Matrix, String)
if self.typeClass() not in valid_types:
    return '{}("{}", "{}")'.format(cls_name,
                                    self.node().name(),
                                    self.name())
...

Which worked, with the exception of when the type has not been implemented, since self.typeClass() raises an error. This happens specifically with the following doctest on line 4180:

>>> _ = cmds.file(new=True, force=True)
>>> a = createNode("transform", name="A")
>>> b = createNode("multDoubleLinear", name="B")
>>> a["ihi"] << b["ihi"]
>>> a["ihi"].connection() == b
True
>>> b["ihi"].connection() == a
True
>>> a["ihi"]  # Error here

That is because isHistoricallyInteresting is a kNumericAttribute of kByte NumericData, which isn't supported in self.typeClass()

I could add a try except in the repr like so and then not read() the value:

...
typ_class = None
try:
    typ_class = self.typeClass()
except TypeError:
    pass

if typ_class not in valid_types:
...

But then the doctest above fails for a different reason:

# expected
cmdx.Plug("A", "isHistoricallyInteresting") == 2

# actual repr return
cmdx.Plug("A", "isHistoricallyInteresting")

Would it be ok to remove the value return from that test? Also, in regards to having a try except in the __repr__, I'm not so sure about that idea either, but inevitably there will be cases where the attribute isn’t implemented in typeClass, so it has to account for that.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm, true.. Choices choices.

In that case, I think we can just put whatever comes out of str(plug) after the ==. It can be a try/except; if it fails, we do not include the ==. It's a __repr__ after all, for debugging. Not performance sensitive or in need to be exact. Then we could potentially work, separately, on making more values print nicely via str(plug)

return '{}("{}", "{}")'.format(cls_name,
self.node().name(),
self.name())
read_val = self.read()
if isinstance(read_val, string_types):
# Add surrounding single quotes, indicating the value is a string
read_val = '"{}"'.format(read_val)

return '{}("{}", "{}") == {}'.format(cls_name,
self.node().name(),
self.name(),
read_val)
chelloiaco marked this conversation as resolved.
Show resolved Hide resolved

def __rshift__(self, other):
"""Support connecting attributes via A >> B"""
Expand Down Expand Up @@ -2796,9 +2856,12 @@ def __setitem__(self, index, value):
"""Write to child of array or compound plug

Example:
>>> _new()
>>> node = createNode("transform")
>>> node["translate"][0] = 5
>>> node["tx"]
cmdx.Plug("transform1", "translateX") == 5.0
>>> node["tx"].read()
5.0

"""
Expand Down Expand Up @@ -3170,12 +3233,17 @@ def extend(self, values):
If cmdx.Plug's, create a new entry and connect it.

Example:
>>> _new()
>>> node = createNode("transform")
>>> node["myArray"] = Double(array=True)
>>> node["myArray"].extend([1.0, 2.0, 3.0])
>>> node["myArray"][0]
cmdx.Plug("transform1", "myArray[0]") == 1.0
>>> node["myArray"][0].read()
1.0
>>> node["myArray"][-1]
cmdx.Plug("transform1", "myArray[2]") == 3.0
>>> node["myArray"][-1].read()
3.0

"""
Expand Down Expand Up @@ -4102,6 +4170,8 @@ def connections(self,
>>> b["ihi"].connection() == a
True
>>> a["ihi"]
cmdx.Plug("A", "isHistoricallyInteresting") == 2
>>> a["ihi"].read()
2
>>> b["arrayAttr"] = Long(array=True)
>>> b["arrayAttr"][0] >> a["ihi"]
Expand Down Expand Up @@ -6351,15 +6421,26 @@ def connect(self, src, dst, force=True):
... mod.connect(tm["sx"], tm["tx"])
...
>>> tm["tx"].connection()
|myTransform
cmdx.DagNode("myTransform")
>>> tm["tx"].connection().path() in (
... '|myTransform',
... u'|myTransform'
... )
True

>>> cmds.undo()
>>> tm["tx"].connection() is None
True

# Connect without undo
>>> tm["tx"] << tx["output"]
>>> tm["tx"].connection()
myAnimCurve
cmdx.AnimCurve("myAnimCurve")
>>> tm["tx"].connection().name() in (
... 'myAnimCurve',
... u'myAnimCurve'
... )
True

# Disconnect without undo
>>> tm["tx"] // tx["output"]
Expand Down Expand Up @@ -6445,11 +6526,21 @@ def connectAttr(self, srcPlug, dstNode, dstAttr):
... mod.connectAttr(newNode["newAttr"], otherNode, otherAttr)
...
>>> newNode["newAttr"].connection()
|otherNode
cmdx.DagNode("otherNode")
>>> newNode["newAttr"].connection().path() in (
... '|otherNode',
... u'|otherNode'
... )
True

>>> cmds.undo()
>>> newNode["newAttr"].connection()
|newNode
cmdx.DagNode("newNode")
>>> newNode["newAttr"].connection().path() in (
... '|newNode',
... u'|newNode'
... )
True

"""

Expand Down Expand Up @@ -6760,6 +6851,7 @@ class DagModifier(_BaseModifier):
"""Modifier for DAG nodes

Example:
>>> _new()
>>> with DagModifier() as mod:
... node1 = mod.createNode("transform")
... node2 = mod.createNode("transform", parent=node1)
Expand All @@ -6769,8 +6861,12 @@ class DagModifier(_BaseModifier):
>>> getAttr(node1 + ".translateX")
1.0
>>> node2["translate"][0]
cmdx.Plug("transform2", "translateX") == 1.0
>>> node2["translate"][0].read()
1.0
>>> node2["translate"][1]
cmdx.Plug("transform2", "translateY") == 2.0
>>> node2["translate"][1].read()
2.0
>>> with DagModifier() as mod:
... node1 = mod.createNode("transform")
Expand All @@ -6779,8 +6875,12 @@ class DagModifier(_BaseModifier):
... node1["translate"] >> node2["translate"]
...
>>> node2["translate"][0]
cmdx.Plug("transform4", "translateX") == 5.0
>>> node2["translate"][0].read()
5.0
>>> node2["translate"][1]
cmdx.Plug("transform4", "translateY") == 6.0
>>> node2["translate"][1].read()
6.0

Example, without context manager:
Expand Down Expand Up @@ -7721,7 +7821,8 @@ def __hash__(self):

def __repr__(self):
"""Avoid repr depicting the full contents of this dict"""
return self["name"]
cls_name = '{}.{}'.format(__name__, self.__class__.__name__)
return 'cmdx.{}("{}")'.format(cls_name, self["name"])

def __new__(cls, *args, **kwargs):
"""Support for using name of assignment
Expand Down
3 changes: 2 additions & 1 deletion tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -383,8 +383,9 @@ def test_nodeoperators():

node = cmdx.createNode(cmdx.tTransform, name="myNode")
assert_equals(node, "|myNode")
assert_equals(repr(node), 'cmdx.DagNode("myNode")')
assert_not_equals(node, "|NotEquals")
assert_equals(str(node), repr(node))
assert_not_equals(str(node), repr(node))


@with_setup(new_scene)
Expand Down
Loading