Skip to content

Commit 503b064

Browse files
authored
Merge pull request #56 from mottosso/improveUndo
Improve undo/redo
2 parents 4a6284c + 94b220b commit 503b064

File tree

5 files changed

+836
-436
lines changed

5 files changed

+836
-436
lines changed

.github/workflows/main.yml

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,6 @@ jobs:
2424

2525
matrix:
2626
include:
27-
- maya: "2015sp6"
28-
pip: "2.7/get-pip.py"
29-
- maya: "2016sp1"
30-
pip: "2.7/get-pip.py"
3127
- maya: "2017"
3228
pip: "2.7/get-pip.py"
3329
- maya: "2018"
@@ -46,22 +42,24 @@ jobs:
4642
- name: Checkout code
4743
uses: actions/checkout@v1
4844

45+
# We'll lock each version to one that works with both Python 2.7 and 3.7
4946
- name: pip install
5047
run: |
5148
wget https://bootstrap.pypa.io/pip/${{ matrix.pip }}
5249
mayapy get-pip.py --user
5350
mayapy -m pip install --user \
54-
nose \
55-
nose-exclude \
56-
coverage \
57-
flaky \
58-
sphinx \
59-
sphinxcontrib-napoleon
51+
nose==1.3.7 \
52+
nose-exclude==0.5.0 \
53+
coverage==5.5 \
54+
flaky==3.7.0 \
55+
sphinx==1.8.5 \
56+
sphinxcontrib-napoleon==0.7
6057
6158
# Since 2019, this sucker throws an unnecessary warning if not declared
6259
- name: Environment
6360
run: |
6461
export XDG_RUNTIME_DIR=/var/tmp/runtime-root
62+
export MAYA_DISABLE_ADP=1
6563
6664
- name: Unittests
6765
run: |

README.md

Lines changed: 112 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<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>
22

3-
<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>
3+
<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>
44

55
<br>
66

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

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

3637
| Maya | Status
3738
|:----------|:----
38-
| 2015 | [![cmdx-test](https://github.com/mottosso/cmdx/actions/workflows/main.yml/badge.svg)](https://github.com/mottosso/cmdx/actions/workflows/main.yml)
39-
| 2016 | [![cmdx-test](https://github.com/mottosso/cmdx/actions/workflows/main.yml/badge.svg)](https://github.com/mottosso/cmdx/actions/workflows/main.yml)
4039
| 2017 | [![cmdx-test](https://github.com/mottosso/cmdx/actions/workflows/main.yml/badge.svg)](https://github.com/mottosso/cmdx/actions/workflows/main.yml)
4140
| 2018 | [![cmdx-test](https://github.com/mottosso/cmdx/actions/workflows/main.yml/badge.svg)](https://github.com/mottosso/cmdx/actions/workflows/main.yml)
4241
| 2019 | [![cmdx-test](https://github.com/mottosso/cmdx/actions/workflows/main.yml/badge.svg)](https://github.com/mottosso/cmdx/actions/workflows/main.yml)
@@ -76,7 +75,6 @@ With [so many options](#comparison) for interacting with Maya, when or why shoul
7675
- [Node and attribute reuse](#query-reduction)
7776
- [Transactions](#transactions)
7877
- [Hashable References](#hashable-references)
79-
- [Signals](#signals)
8078
- [PEP8 Dual Syntax](#pep8-dual-syntax)
8179

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

158156
### System Requirements
159157

160-
`cmdx` runs on Maya 2015 SP3 and above (SP2 does *not* work).
158+
`cmdx` runs on Maya 2017 above.
161159

162160
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).
163161

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

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

602600
```py
603601
node["translateX"] = 5
604602
node["tx"] >> node["ty"]
603+
cmdx.delete(node)
605604
```
606605

607-
These operations however is *not* undoable.
606+
These operations are also not undoable.
608607

609608
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++.
610609

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

639638
```py
640639
a = cmdx.encode("existingNode")
641-
b = cmdx.createNode("transform", name="newNode")
640+
641+
with cmdx.DagModifier() as mod:
642+
b = mod.createNode("transform", name="newNode")
643+
642644
b["ty"] >> a["tx"]
643645
```
644646

@@ -753,8 +755,6 @@ for member in objset:
753755
print(member)
754756
```
755757

756-
> 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.
757-
758758
<br>
759759

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

11641164
**Drawing a line**
11651165

1166+
<img width=200 src=https://user-images.githubusercontent.com/2152766/113600037-5ba77180-9637-11eb-8adc-cf2130131bb4.png>
1167+
11661168
```python
11671169
import cmdx
11681170

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

11781180
Append the `degree` argument for a smooth curve.
11791181

1182+
<img width=200 src=https://user-images.githubusercontent.com/2152766/113600082-69f58d80-9637-11eb-8a5a-0d72a1f2fbd4.png>
1183+
11801184
```python
11811185
import cmdx
11821186

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

11931197
Append the `form` argument for closed loop.
11941198

1199+
<img width=200 src=https://user-images.githubusercontent.com/2152766/113600244-9d381c80-9637-11eb-8712-b5051df5c4b0.png>
1200+
11951201
```python
11961202
import cmdx
11971203

11981204
parent = cmdx.createNode("transform")
11991205
shape = cmdx.createNode("nurbsCurve", parent=parent)
12001206
shape["cached"] = cmdx.NurbsCurveData(
1201-
points=((0, 0, 0), (1, 1, 0), (0, 2, 0)),
1207+
points=((1, 1, 0), (-1, 1, 0), (-1, -1, 0), (1, -1, 0)),
12021208
degree=2,
12031209
form=cmdx.kClosed
12041210
)
@@ -1554,7 +1560,7 @@ It's not all roses; in order of severity:
15541560
Modifiers in `cmdx` extend the native modifiers with these extras.
15551561

15561562
1. **Automatically undoable** Like `cmds`
1557-
2. **Transactional** Changes are automatically rolled back on error, making every modifier atomic
1563+
2. **Atomic** Changes are automatically rolled back on error, making every modifier atomic
15581564
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.
15591565
4. **Name templates** Reduce character count by delegating a "theme" of names across many new nodes.
15601566

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

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

1575-
If you prefer, modern syntax still works here.
1576-
1577-
```python
1578-
with cmdx.DagModifier() as mod:
1579-
parent = mod.createNode("transform", name="MyParent")
1580-
child = mod.createNode("transform", parent=parent)
1581-
parent["translate"] = (1, 2, 3)
1582-
parent["rotate"] >> child["rotate"]
1583-
```
1584-
1585-
And PEP8.
1581+
There is also a completely equivalent PEP8 syntax.
15861582

15871583
```python
15881584
with cmdx.DagModifier() as mod:
15891585
parent = mod.create_node("transform", name="MyParent")
15901586
child = mod.create_node("transform", parent=parent)
1591-
parent["translate"] = (1, 2, 3)
1592-
parent["rotate"] >> child["rotate"]
1587+
mod.set_attr(parent + ".translate", (1, 2, 3))
1588+
mod.connect(parent + ".rotate", child + ".rotate")
15931589
```
15941590

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

1604-
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.
1600+
##### Connect To Newly Created Attribute
16051601

1606-
##### Limitations
1602+
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.
16071603

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

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

1614-
Furthermore, there are a few limitations with regards to modern syntax.
1613+
```py
16151614

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

1619-
<br>
1622+
##### Convenience Historyically Interesting
16201623

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

1623-
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.
1626+
```py
1627+
with cmdx.DGModifier() as mod:
1628+
reverse = mod.createNode("reverse")
1629+
multMatrix = mod.createNode("multMatrix")
1630+
mod.set_attr(reverse["isHistoricallyInteresting"], False)
1631+
mod.set_attr(multMatrix["isHistoricallyInteresting"], False)
1632+
```
16241633

1625-
```python
1626-
import cmdx
1634+
..or use the convenience argument to make everything neat.
16271635

1628-
def onDestroyed():
1629-
pass
1636+
```py
1637+
with cmdx.DGModifier(interesting=False) as mod:
1638+
mod.createNode("reverse")
1639+
mod.createNode("multMatrix")
1640+
```
16301641

1631-
node = cmdx.createNode("transform")
1632-
node.onDestroyed.append(onDestroyed)
1642+
##### Convenience Try Set Attr
1643+
1644+
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.
1645+
1646+
For that, you can either..
1647+
1648+
```py
1649+
with cmdx.DagModifier() as mod:
1650+
try:
1651+
mod.setAttr(node["attr1"], 5.0)
1652+
except cmdx.LockedError:
1653+
pass # This is OK
1654+
try:
1655+
mod.setAttr(node["attr2"], 5.0)
1656+
except cmdx.LockedError:
1657+
pass # This is OK
1658+
try:
1659+
mod.setAttr(node["attr3"], 5.0)
1660+
except cmdx.LockedError:
1661+
pass # This is OK
1662+
```
1663+
1664+
..or you can use the convenience `trySetAttr` to ease up on readability.
1665+
1666+
```py
1667+
1668+
with cmdx.DagModifier() as mod:
1669+
mod.trySetAttr(node["attr1"], 5.0)
1670+
mod.trySetAttr(node["attr2"], 5.0)
1671+
mod.trySetAttr(node["attr3"], 5.0)
1672+
```
1673+
1674+
##### Convenience Set Attr
1675+
1676+
Sometimes, the attribute you're setting is connected to by another attribute. Maybe driven by some controller on a character rig?
1677+
1678+
In such cases, the attribute cannot be set, and must set whichever attribute is feeding into it instead. So you could..
1679+
1680+
```py
1681+
with cmdx.DagModifier() as mod:
1682+
if node["myAttr"].connected:
1683+
other = node["myAttr"].connection(destination=False, plug=True)
1684+
mod.setAttr(other["myAttr"], 5.0)
1685+
else:
1686+
mod.setAttr(node["myAttr"], 5.0)
1687+
```
1688+
1689+
Or, you can use the `smart_set_attr` to automate this process.
1690+
1691+
```py
1692+
with cmdx.DagModifier() as mod:
1693+
mod.smartSetAttr(node["myAttr"], 5.0)
16331694
```
16341695

1696+
##### Limitations
1697+
1698+
The modifier is quite limited in what features it provides; in general, it can only *modify* the scenegraph, it cannot query it.
1699+
1700+
1. It cannot read attributes
1701+
2. It cannot set complex attribute types, such as meshes or nurbs curves
1702+
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)
1703+
16351704
<br>
16361705

16371706
### PEP8 Dual Syntax

0 commit comments

Comments
 (0)