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
134 changes: 121 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,27 @@ def __str__(self):
return str(self.read())

def __repr__(self):
return str(self.read())
try:
# Delegate the value reading to __str__
read_result = str(self)
Copy link
Owner

Choose a reason for hiding this comment

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

Hmmmm, now str() is a call I would never expect to fail, and if it does then I'd consider that a bug. We can expect it will never fail, and not bother with a valid = True.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

PS: I feel like I might've opened a can of worms, but running bs['inputTarget'] causes an API failure when it tries to get 'targetBindMatrix'. I might create a separate issue on it so we keep on topic here.

This is the reason why I added a try/except. running the following causes an API failure:

>>> _ = cmds.file(new=1, f=1)
>>> sphere_a = cmds.polySphere(ch=False)[0]
>>> sphere_b = cmds.polySphere(ch=False)[0]
>>> bs = cmds.blendShape(sphere_a, sphere_b)[0]
>>> bs = cmdx.ls(bs)[0]
>>> bs["inputTarget"].read()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "D:\repos\cmdx\cmdx.py", line 4044, in read

  File "D:\repos\cmdx\cmdx.py", line 5076, in _plug_to_python
    #
  File "D:\repos\cmdx\cmdx.py", line 5094, in _plug_to_python
    )
  File "D:\repos\cmdx\cmdx.py", line 5094, in <genexpr>
    )
  File "D:\repos\cmdx\cmdx.py", line 5076, in _plug_to_python
    #
  File "D:\repos\cmdx\cmdx.py", line 5094, in _plug_to_python
    )
  File "D:\repos\cmdx\cmdx.py", line 5094, in <genexpr>
    )
  File "D:\repos\cmdx\cmdx.py", line 5119, in _plug_to_python
    # E.g. transform["worldMatrix"][0]
RuntimeError: (kFailure): Unexpected Internal Failure

Might be an edge case, but that was the reason why I added tat in.

I did some digging and it could be because the attribute which it fails on (blendshape1.inputTarget[0].inputTargetGroup[0].targetBindMatrix) has its value being cached. It is just a theory at this point though, I haven't properly diagnosed this.

# Isolated API fail:
>>> bs['inputTarget'][0][0][0][4]._mplug.asMObject()             
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
RuntimeError: (kFailure): Unexpected Internal Failure

>>> bs['inputTarget'][0][0][0][4]._mplug.isCaching
True

We could remove the try/except and deal with this as a separate issue though, which is probably a cleaner strategy.

valid = True
except:
valid = False

cls_name = '{}.{}'.format(__name__, self.__class__.__name__)
msg = '{}("{}", "{}")'.format(cls_name,
self.node().name(),
self.name())
if valid:
try:
if self.typeClass() == String:
Copy link
Owner

Choose a reason for hiding this comment

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

Since we've established that typeClass isn't relevant for all types, like kByte, how about we use Maya's apiType here instead? Especially since we're only dealing with a single type.

It would save on the use of try/except too.

Did we also have a test for this case?

Copy link
Contributor Author

@chelloiaco chelloiaco Feb 23, 2024

Choose a reason for hiding this comment

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

Since we've established that typeClass isn't relevant for all types, like kByte, how about we use Maya's apiType here instead? Especially since we're only dealing with a single type.

Yeah, I thought of doing it that way as well, but thought it looked less elegant. But sounds good, let me implement that.

Did we also have a test for this case?

We do not, let me add it in.


On another note:

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 ==.

If this is not the case anymore, and we never expect str to fail, would it be ok to expect that it can return None? In the case of a Compound attribute for instance? That way I can add that check to include == or not.

This might be getting a little too much into read() functionality for this PR but, perhaps more useful, instead have it return "Compound" or something of the sort? e.g.:

>>> myNode["myCompoundAttr"]
cmdx.Plug("myNode", myCompoundAttr") == Compound

A third option, albeit more complex, could be to have it return a dictionary, with the child attribute names as keys for instance.

>>> myNode["myCompoundAttr"]
cmdx.Plug("myNode", myCompoundAttr") == {"childA": 1.0, "childB": "Some string value.", "childC": None}

Copy link
Owner

Choose a reason for hiding this comment

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

If this is not the case anymore, and we never expect str to fail, would it be ok to expect that it can return None? In the case of a Compound attribute for instance? That way I can add that check to include == or not.

Sure, let's try it.

A third option, albeit more complex, could be to have it return a dictionary, with the child attribute names as keys for instance.

I mean, this would be supremely useful. But I'm uncertain how stable we can make this.. Maybe let's start small, and add this later on?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sure, let's try it

Scratch that. We can't return something that isn't a string type from __str__. That raises a TypeError, silly me.

I think I was able to think of a decent solution within __repr__, so we're not messing with __str__ in this PR. pushing a commit now which includes this and the above

# Add surrounding quotes, indicating it is a string
read_result = '"{}"'.format(read_result)
except TypeError:
pass
msg += ' == {}'.format(read_result)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Had to add this in order to avoid cases where it shows a valid value, but of an empty string:

>>> myNode["myStr"]
cmdx.Plug("myNode", "myStr") == 


return msg

def __rshift__(self, other):
"""Support connecting attributes via A >> B"""
Expand Down Expand Up @@ -2796,9 +2863,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 +3240,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 +4177,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 +6428,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 +6533,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 +6858,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 +6868,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 +6882,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 +7828,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