Skip to content

Commit

Permalink
Merge pull request #56 from mottosso/improveUndo
Browse files Browse the repository at this point in the history
Improve undo/redo
  • Loading branch information
mottosso authored Apr 11, 2021
2 parents 4a6284c + 94b220b commit 503b064
Show file tree
Hide file tree
Showing 5 changed files with 836 additions and 436 deletions.
18 changes: 8 additions & 10 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,6 @@ jobs:

matrix:
include:
- maya: "2015sp6"
pip: "2.7/get-pip.py"
- maya: "2016sp1"
pip: "2.7/get-pip.py"
- maya: "2017"
pip: "2.7/get-pip.py"
- maya: "2018"
Expand All @@ -46,22 +42,24 @@ jobs:
- name: Checkout code
uses: actions/checkout@v1

# We'll lock each version to one that works with both Python 2.7 and 3.7
- name: pip install
run: |
wget https://bootstrap.pypa.io/pip/${{ matrix.pip }}
mayapy get-pip.py --user
mayapy -m pip install --user \
nose \
nose-exclude \
coverage \
flaky \
sphinx \
sphinxcontrib-napoleon
nose==1.3.7 \
nose-exclude==0.5.0 \
coverage==5.5 \
flaky==3.7.0 \
sphinx==1.8.5 \
sphinxcontrib-napoleon==0.7
# Since 2019, this sucker throws an unnecessary warning if not declared
- name: Environment
run: |
export XDG_RUNTIME_DIR=/var/tmp/runtime-root
export MAYA_DISABLE_ADP=1
- name: Unittests
run: |
Expand Down
155 changes: 112 additions & 43 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<a href=/cmdx/><p align=center><img height=140 src=https://user-images.githubusercontent.com/2152766/34321609-f134e0cc-e80a-11e7-8dad-d124fea80e77.png></p></a>

<p align=center>A fast subset of <a href=http://help.autodesk.com/cloudhelp/2018/ENU/Maya-Tech-Docs/CommandsPython/index.html><code>maya.cmds</code></a><br>For Maya 2015-2022</p>
<p align=center>A fast subset of <a href=http://help.autodesk.com/cloudhelp/2018/ENU/Maya-Tech-Docs/CommandsPython/index.html><code>maya.cmds</code></a><br>For Maya 2017-2022</p>

<br>

Expand All @@ -23,6 +23,7 @@ On average, `cmdx` is **140x faster** than [PyMEL](https:/LumaPictur

| Date | Version | Event
|:---------|:----------|:----------
| Apr 2020 | 0.6.0 | Stable Undo/Redo, dropped support for Maya 2015-2016
| Mar 2020 | 0.5.1 | Support for Maya 2022
| Mar 2020 | 0.5.0 | Stable release
| Aug 2019 | 0.4.0 | Public release
Expand All @@ -35,8 +36,6 @@ On average, `cmdx` is **140x faster** than [PyMEL](https:/LumaPictur

| Maya | Status
|:----------|:----
| 2015 | [![cmdx-test](https:/mottosso/cmdx/actions/workflows/main.yml/badge.svg)](https:/mottosso/cmdx/actions/workflows/main.yml)
| 2016 | [![cmdx-test](https:/mottosso/cmdx/actions/workflows/main.yml/badge.svg)](https:/mottosso/cmdx/actions/workflows/main.yml)
| 2017 | [![cmdx-test](https:/mottosso/cmdx/actions/workflows/main.yml/badge.svg)](https:/mottosso/cmdx/actions/workflows/main.yml)
| 2018 | [![cmdx-test](https:/mottosso/cmdx/actions/workflows/main.yml/badge.svg)](https:/mottosso/cmdx/actions/workflows/main.yml)
| 2019 | [![cmdx-test](https:/mottosso/cmdx/actions/workflows/main.yml/badge.svg)](https:/mottosso/cmdx/actions/workflows/main.yml)
Expand Down Expand Up @@ -76,7 +75,6 @@ With [so many options](#comparison) for interacting with Maya, when or why shoul
- [Node and attribute reuse](#query-reduction)
- [Transactions](#transactions)
- [Hashable References](#hashable-references)
- [Signals](#signals)
- [PEP8 Dual Syntax](#pep8-dual-syntax)

<br>
Expand Down Expand Up @@ -157,7 +155,7 @@ With [so many options](#comparison) for interacting with Maya, when or why shoul

### System Requirements

`cmdx` runs on Maya 2015 SP3 and above (SP2 does *not* work).
`cmdx` runs on Maya 2017 above.

It *may* run on older versions too, but those are not being tested. To bypass the version check, see [`CMDX_IGNORE_VERSION`](#cmdx_ignore_version).

Expand Down Expand Up @@ -597,14 +595,15 @@ For undo, you've got two options.
node = cmdx.createNode("transform")
```

This operation is undoable, because under the hood it calls `cmdx.DagModifier`.
This operation is not undoable and is intended for use with `cmdx.commit` and/or within a Python plug-in.

```py
node["translateX"] = 5
node["tx"] >> node["ty"]
cmdx.delete(node)
```

These operations however is *not* undoable.
These operations are also not undoable.

In order to edit attributes with support for undo, you must use either a modifier or call `commit`. This is how the Maya API normally works, for both Python and C++.

Expand Down Expand Up @@ -638,7 +637,10 @@ With this level of control, you are able to put Maya in a bad state.

```py
a = cmdx.encode("existingNode")
b = cmdx.createNode("transform", name="newNode")

with cmdx.DagModifier() as mod:
b = mod.createNode("transform", name="newNode")

b["ty"] >> a["tx"]
```

Expand Down Expand Up @@ -753,8 +755,6 @@ for member in objset:
print(member)
```

> NOTE: `MFnSet` was first introduced to the Maya Python API 2.0 in Maya 2016 and has been backported to work with `cmdx` in Maya 2015, leveraging the equivalent functionality found in API 1.0. It does however mean that there is a performance impact in Maya <2016 of roughly 0.01 ms/node.
<br>

### Attribute Query and Assignment
Expand Down Expand Up @@ -1163,6 +1163,8 @@ assert b.child(contains="nurbsCurve") != c

**Drawing a line**

<img width=200 src=https://user-images.githubusercontent.com/2152766/113600037-5ba77180-9637-11eb-8adc-cf2130131bb4.png>

```python
import cmdx

Expand All @@ -1177,6 +1179,8 @@ This creates a new `nurbsCurve` shape and fills it with points.

Append the `degree` argument for a smooth curve.

<img width=200 src=https://user-images.githubusercontent.com/2152766/113600082-69f58d80-9637-11eb-8a5a-0d72a1f2fbd4.png>

```python
import cmdx

Expand All @@ -1192,13 +1196,15 @@ shape["cached"] = cmdx.NurbsCurveData(

Append the `form` argument for closed loop.

<img width=200 src=https://user-images.githubusercontent.com/2152766/113600244-9d381c80-9637-11eb-8712-b5051df5c4b0.png>

```python
import cmdx

parent = cmdx.createNode("transform")
shape = cmdx.createNode("nurbsCurve", parent=parent)
shape["cached"] = cmdx.NurbsCurveData(
points=((0, 0, 0), (1, 1, 0), (0, 2, 0)),
points=((1, 1, 0), (-1, 1, 0), (-1, -1, 0), (1, -1, 0)),
degree=2,
form=cmdx.kClosed
)
Expand Down Expand Up @@ -1554,7 +1560,7 @@ It's not all roses; in order of severity:
Modifiers in `cmdx` extend the native modifiers with these extras.

1. **Automatically undoable** Like `cmds`
2. **Transactional** Changes are automatically rolled back on error, making every modifier atomic
2. **Atomic** Changes are automatically rolled back on error, making every modifier atomic
3. **Debuggable** Maya's native modifier throws an error without including what or where it happened. `cmdx` provides detailed diagnostics of what was supposed to happen, what happened, attempts to figure out why and what line number it occurred on.
4. **Name templates** Reduce character count by delegating a "theme" of names across many new nodes.

Expand All @@ -1572,24 +1578,14 @@ with cmdx.DagModifier() as mod:

Now when calling `undo`, the above lines will be undone as you'd expect.

If you prefer, modern syntax still works here.

```python
with cmdx.DagModifier() as mod:
parent = mod.createNode("transform", name="MyParent")
child = mod.createNode("transform", parent=parent)
parent["translate"] = (1, 2, 3)
parent["rotate"] >> child["rotate"]
```

And PEP8.
There is also a completely equivalent PEP8 syntax.

```python
with cmdx.DagModifier() as mod:
parent = mod.create_node("transform", name="MyParent")
child = mod.create_node("transform", parent=parent)
parent["translate"] = (1, 2, 3)
parent["rotate"] >> child["rotate"]
mod.set_attr(parent + ".translate", (1, 2, 3))
mod.connect(parent + ".rotate", child + ".rotate")
```

Name templates look like this.
Expand All @@ -1601,37 +1597,110 @@ with cmdx.DagModifier(template="myName_{type}") as mod:
assert node.name() == "myName_transform"
```

This makes it easy to move a block of code into a modifier without changing things around. Perhaps to test performance, or to figure out whether undo support is necessary.
##### Connect To Newly Created Attribute

##### Limitations
Creating a new attribute returns a "promise" of that attribute being created. You can pass that to `connectAttr` to both create and connect attributes in the same modifier.

The modifier is quite limited in what features it provides; in general, it can only *modify* the scenegraph, it cannot query it.
```py
with cmdx.DagModifier() as mod:
node = mod.createNode("transform")
attr = mod.createAttr(node, cmdx.Double("myNewAttr"))
mod.connectAttr(node["translateX"], attr)
```

1. It cannot read attributes
2. It cannot set complex attribute types, such as meshes or nurbs curves
3. It cannot query a future hierarchy, such as asking for the parent or children of a newly created node
You can even connect *two* previously unexisting attributes at the same time with `connectAttrs`.

Furthermore, there are a few limitations with regards to modern syntax.
```py

1. It cannot connect an existing attribute to one on a newly node, e.g. `existing["tx"] >> new["tx"]`
2. ...
with cmdx.DagModifier() as mod:
node = mod.createNode("transform")
attr1 = mod.createAttr(node, cmdx.Double("attr1"))
attr2 = mod.createAttr(node, cmdx.Double("attr2"))
mod.connectAttrs(node, attr1, node, attr2)
```

<br>
##### Convenience Historyically Interesting

### Signals
Sometimes you're creating a series of utility nodes that you don't want visible in the channel box. So you can either go..

Maya offers a large number of callbacks for responding to native events in your code. `cmdx` wraps some of these in an alternative interface akin to Qt Signals and Slots.
```py
with cmdx.DGModifier() as mod:
reverse = mod.createNode("reverse")
multMatrix = mod.createNode("multMatrix")
mod.set_attr(reverse["isHistoricallyInteresting"], False)
mod.set_attr(multMatrix["isHistoricallyInteresting"], False)
```

```python
import cmdx
..or use the convenience argument to make everything neat.

def onDestroyed():
pass
```py
with cmdx.DGModifier(interesting=False) as mod:
mod.createNode("reverse")
mod.createNode("multMatrix")
```

node = cmdx.createNode("transform")
node.onDestroyed.append(onDestroyed)
##### Convenience Try Set Attr

Sometimes you aren't too concerned whether setting an attribute actually succeeds or not. Perhaps you're writing a bulk-importer, and it'll become obvious to the end-user whether attributes were set or not, or you simply could not care less.

For that, you can either..

```py
with cmdx.DagModifier() as mod:
try:
mod.setAttr(node["attr1"], 5.0)
except cmdx.LockedError:
pass # This is OK
try:
mod.setAttr(node["attr2"], 5.0)
except cmdx.LockedError:
pass # This is OK
try:
mod.setAttr(node["attr3"], 5.0)
except cmdx.LockedError:
pass # This is OK
```

..or you can use the convenience `trySetAttr` to ease up on readability.

```py

with cmdx.DagModifier() as mod:
mod.trySetAttr(node["attr1"], 5.0)
mod.trySetAttr(node["attr2"], 5.0)
mod.trySetAttr(node["attr3"], 5.0)
```

##### Convenience Set Attr

Sometimes, the attribute you're setting is connected to by another attribute. Maybe driven by some controller on a character rig?

In such cases, the attribute cannot be set, and must set whichever attribute is feeding into it instead. So you could..

```py
with cmdx.DagModifier() as mod:
if node["myAttr"].connected:
other = node["myAttr"].connection(destination=False, plug=True)
mod.setAttr(other["myAttr"], 5.0)
else:
mod.setAttr(node["myAttr"], 5.0)
```

Or, you can use the `smart_set_attr` to automate this process.

```py
with cmdx.DagModifier() as mod:
mod.smartSetAttr(node["myAttr"], 5.0)
```

##### Limitations

The modifier is quite limited in what features it provides; in general, it can only *modify* the scenegraph, it cannot query it.

1. It cannot read attributes
2. It cannot set complex attribute types, such as meshes or nurbs curves
3. It cannot query a future hierarchy, such as asking for the parent or children of a newly created node unless you call `doIt()` first)

<br>

### PEP8 Dual Syntax
Expand Down
Loading

0 comments on commit 503b064

Please sign in to comment.