diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index 39b3c4e..c8a7b31 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -23,16 +23,23 @@ jobs:
fail-fast: false
matrix:
- containerImage: [
- "mottosso/maya:2015sp6",
- "mottosso/maya:2016sp1",
- "mottosso/maya:2017",
- "mottosso/maya:2018",
- "mottosso/maya:2019",
- "mottosso/maya:2020"
- ]
+ 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"
+ pip: "2.7/get-pip.py"
+ - maya: "2019"
+ pip: "2.7/get-pip.py"
+ - maya: "2020"
+ pip: "2.7/get-pip.py"
+ - maya: "2022"
+ pip: "get-pip.py"
- container: ${{ matrix.containerImage }}
+ container: mottosso/maya:${{ matrix.maya }}
# Steps represent a sequence of tasks that will be executed as part of the job
steps:
@@ -41,7 +48,7 @@ jobs:
- name: pip install
run: |
- wget https://bootstrap.pypa.io/pip/2.7/get-pip.py
+ wget https://bootstrap.pypa.io/pip/${{ matrix.pip }}
mayapy get-pip.py --user
mayapy -m pip install --user \
nose \
diff --git a/Dockerfile b/Dockerfile
index 85b92ca..7c834ac 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,6 +1,6 @@
-FROM mottosso/maya:2020
+FROM mottosso/maya:2022
-RUN wget https://bootstrap.pypa.io/pip/2.7/get-pip.py && \
+RUN wget https://bootstrap.pypa.io/pip/get-pip.py && \
mayapy get-pip.py --user && \
mayapy -m pip install --user \
nose \
diff --git a/README.md b/README.md
index 31332f4..7d407ae 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,6 @@
-
A fast subset of maya.cmds
+A fast subset of maya.cmds
For Maya 2015-2022
@@ -23,6 +23,7 @@ On average, `cmdx` is **140x faster** than [PyMEL](https://github.com/LumaPictur
| Date | Version | Event
|:---------|:----------|:----------
+| Mar 2020 | 0.5.1 | Support for Maya 2022
| Mar 2020 | 0.5.0 | Stable release
| Aug 2019 | 0.4.0 | Public release
| Feb 2018 | 0.1.0 | Extracted into its own repository
@@ -30,17 +31,17 @@ On average, `cmdx` is **140x faster** than [PyMEL](https://github.com/LumaPictur
##### Status
-[![](https://img.shields.io/pypi/v/cmdx?color=steelblue&label=PyPI)](https://github.com/mottosso/cmdx/)
-[![](https://img.shields.io/pypi/pyversions/cmdx?color=steelblue)](https://pypi.org/project/cmdx)
-
-| Maya | Status
-|:----------|:-----
-| 2015 | [![Build Status](https://mottosso.visualstudio.com/cmdx/_apis/build/status/mottosso.cmdx?branchName=master&jobName=Maya&configuration=Maya%20maya2015)](https://mottosso.visualstudio.com/cmdx/_build/latest?definitionId=7&branchName=master)
-| 2016 | [![Build Status](https://mottosso.visualstudio.com/cmdx/_apis/build/status/mottosso.cmdx?branchName=master&jobName=Maya&configuration=Maya%20maya2016)](https://mottosso.visualstudio.com/cmdx/_build/latest?definitionId=7&branchName=master)
-| 2017 | [![Build Status](https://mottosso.visualstudio.com/cmdx/_apis/build/status/mottosso.cmdx?branchName=master&jobName=Maya&configuration=Maya%20maya2017)](https://mottosso.visualstudio.com/cmdx/_build/latest?definitionId=7&branchName=master)
-| 2018 | [![Build Status](https://mottosso.visualstudio.com/cmdx/_apis/build/status/mottosso.cmdx?branchName=master&jobName=Maya&configuration=Maya%20maya2018)](https://mottosso.visualstudio.com/cmdx/_build/latest?definitionId=7&branchName=master)
-| 2019 | [![Build Status](https://mottosso.visualstudio.com/cmdx/_apis/build/status/mottosso.cmdx?branchName=master&jobName=Maya&configuration=Maya%20maya2019)](https://mottosso.visualstudio.com/cmdx/_build/latest?definitionId=7&branchName=master)
-| 2020 | [![Build Status](https://mottosso.visualstudio.com/cmdx/_apis/build/status/mottosso.cmdx?branchName=master&jobName=Maya&configuration=Maya%20maya2020)](https://mottosso.visualstudio.com/cmdx/_build/latest?definitionId=7&branchName=master)
+[![](https://img.shields.io/pypi/v/cmdx?color=steelblue&label=PyPI)](https://github.com/mottosso/cmdx/) [![](https://img.shields.io/pypi/pyversions/cmdx?color=steelblue)](https://pypi.org/project/cmdx)
+
+| Maya | Status
+|:----------|:----
+| 2015 | [![cmdx-test](https://github.com/mottosso/cmdx/actions/workflows/main.yml/badge.svg)](https://github.com/mottosso/cmdx/actions/workflows/main.yml)
+| 2016 | [![cmdx-test](https://github.com/mottosso/cmdx/actions/workflows/main.yml/badge.svg)](https://github.com/mottosso/cmdx/actions/workflows/main.yml)
+| 2017 | [![cmdx-test](https://github.com/mottosso/cmdx/actions/workflows/main.yml/badge.svg)](https://github.com/mottosso/cmdx/actions/workflows/main.yml)
+| 2018 | [![cmdx-test](https://github.com/mottosso/cmdx/actions/workflows/main.yml/badge.svg)](https://github.com/mottosso/cmdx/actions/workflows/main.yml)
+| 2019 | [![cmdx-test](https://github.com/mottosso/cmdx/actions/workflows/main.yml/badge.svg)](https://github.com/mottosso/cmdx/actions/workflows/main.yml)
+| 2020 | [![cmdx-test](https://github.com/mottosso/cmdx/actions/workflows/main.yml/badge.svg)](https://github.com/mottosso/cmdx/actions/workflows/main.yml)
+| 2022 | [![cmdx-test](https://github.com/mottosso/cmdx/actions/workflows/main.yml/badge.svg)](https://github.com/mottosso/cmdx/actions/workflows/main.yml)
##### Usecases
diff --git a/build_docs.py b/build_docs.py
index 57f959e..86e8444 100644
--- a/build_docs.py
+++ b/build_docs.py
@@ -1,4 +1,11 @@
-from sphinx.cmdline import main
+try:
+ # Python 2
+ from sphinx.cmdline import main
+
+except ImportError:
+ # Python 3
+ from sphinx.cmd.build import main
+
from maya import standalone
print("Initializing Maya..")
diff --git a/cmdx.py b/cmdx.py
index 7a503d9..cdced94 100644
--- a/cmdx.py
+++ b/cmdx.py
@@ -17,7 +17,7 @@
from maya.api import OpenMaya as om, OpenMayaAnim as oma, OpenMayaUI as omui
from maya import OpenMaya as om1, OpenMayaMPx as ompx1, OpenMayaUI as omui1
-__version__ = "0.5.0"
+__version__ = "0.5.1"
PY3 = sys.version_info[0] == 3
@@ -534,14 +534,20 @@ def __setitem__(self, key, value):
>>> node["myDist", Centimeters] = node["translateX", Meters]
>>> round(node["rotateX", Radians], 3)
0.017
- >>> node["myDist"] = Distance()
- Traceback (most recent call last):
- ...
- ExistError: myDist
- >>> node["notExist"] = 5
- Traceback (most recent call last):
- ...
- ExistError: |myNode.notExist
+ >>> try:
+ ... node["myDist"] = Distance()
+ ... except Exception as e:
+ ... assert isinstance(e, ExistError)
+ ... else:
+ ... assert False
+ >>>
+ >>> try:
+ ... node["notExist"] = 5
+ ... except Exception as f:
+ ... assert isinstance(f, ExistError)
+ ... else:
+ ... assert False
+ >>>
>>> delete(node)
"""
@@ -574,11 +580,7 @@ def __setitem__(self, key, value):
# where this exception is thrown. Stay catious.
raise ExistError(key)
- try:
- plug = self.findPlug(key)
- except RuntimeError:
- raise ExistError("%s.%s" % (self.path(), key))
-
+ plug = self.findPlug(key)
plug = Plug(self, plug, unit=unit)
if not getattr(self._modifier, "isDone", True):
@@ -595,6 +597,10 @@ def __setitem__(self, key, value):
# Else, write it immediately
plug.write(value)
+ def __hash__(self):
+ """Support storing in set() and as key in dict()"""
+ return hash(self.path())
+
def _onDestroyed(self, mobject):
self._destroyed = True
@@ -884,7 +890,13 @@ def findPlug(self, name, cached=False, safe=True):
if cached:
raise KeyError("'%s' not cached" % name)
- plug = self._fn.findPlug(name, False)
+ assert isinstance(name, string_types), "%s was not string" % name
+
+ try:
+ plug = self._fn.findPlug(name, True)
+ except RuntimeError:
+ raise ExistError("%s.%s" % (self.path(), name))
+
self._state["plugs"][name] = plug
return plug
@@ -944,8 +956,8 @@ def name(self, namespace=False):
Example:
>>> node = createNode("transform", name="myName")
- >>> node.name()
- u'myName'
+ >>> node.name() == 'myName'
+ True
"""
@@ -960,13 +972,13 @@ def namespace(self):
Example:
>>> _ = cmds.file(new=True, force=True)
>>> node = createNode("transform", name="myNode")
- >>> node.namespace()
- u''
+ >>> node.namespace() == ""
+ True
>>> _ = cmds.namespace(add=":A")
>>> _ = cmds.namespace(add=":A:B")
>>> node = createNode("transform", name=":A:B:myNode")
- >>> node.namespace()
- u'A:B'
+ >>> node.namespace() == 'A:B'
+ True
"""
@@ -1050,8 +1062,8 @@ def type(self):
Example:
>>> node = createNode("choice")
- >>> node.type()
- u'choice'
+ >>> node.type() == 'choice'
+ True
"""
@@ -1082,6 +1094,7 @@ def addAttr(self, attr):
if isinstance(mobj, _AbstractAttribute):
mobj = attr.create()
+ assert isinstance(mobj, om.MObject)
self._fn.addAttribute(mobj)
# These don't natively support defaults by Maya
@@ -1281,6 +1294,44 @@ class ContainerNode(Node):
This class wraps that interface to align with regular attribute access,
for an as-transparent-as-possible experience.
+ Examples:
+ >> from maya import cmds
+ >> _ = cmds.file(new=True, force=True)
+
+ # Establish a published plug
+ >> con = cmds.container(name="myContainer")
+ >> con = encode(con)
+ >> con.isA(kDagNode)
+ False
+ >> isinstance(con, ContainerNode)
+ True
+
+ >> from maya import cmds
+ >> _ = cmds.file(new=True, force=True)
+
+ # Establish a published plug
+ >> node = cmds.createNode("transform")
+ >> con = cmds.container(name="myContainer",
+ .. addNode=[node],
+ .. type="dagContainer")
+ >> plug = cmds.container(con,
+ .. edit=True,
+ .. publishName="inputTranslate")
+ >> binding = ("%s.tx" % node, plug)
+ >> _ = cmds.container(con, edit=True, bindAttr=binding)
+
+ # Query and connect that published plug
+ >> source = createNode("transform")
+ >> con = encode(con)
+ >> con.isA(kDagNode)
+ True
+ >> isinstance(con, ContainerNode)
+ True
+ >> source["tx"] >> con[plug]
+ >> source["tx"] = 5.0
+ >> con["inputTranslate"].read()
+ 5.0
+
"""
_Fn = om.MFnContainerNode
@@ -1357,10 +1408,10 @@ def path(self):
Example:
>>> parent = createNode("transform", "myParent")
>>> child = createNode("transform", "myChild", parent=parent)
- >>> child.name()
- u'myChild'
- >>> child.path()
- u'|myParent|myChild'
+ >>> child.name() == 'myChild'
+ True
+ >>> child.path() == '|myParent|myChild'
+ True
"""
@@ -1392,12 +1443,12 @@ def shortestPath(self):
>>> _ = cmds.file(new=True, force=True)
>>> parent = createNode("transform", name="myParent")
>>> child = createNode("transform", name="myChild", parent=parent)
- >>> child.shortestPath()
- u'myChild'
+ >>> child.shortestPath() == "myChild"
+ True
>>> child = createNode("transform", name="myChild")
>>> # Now `myChild` could refer to more than a single node
- >>> child.shortestPath()
- u'|myChild'
+ >>> child.shortestPath() == '|myChild'
+ True
"""
@@ -1687,7 +1738,18 @@ def children(self,
other = "typeId" if isinstance(type, om.MTypeId) else "typeName"
- for index in range(self._fn.childCount()):
+ assert self._fn.hasObj(self._mobject), "This is a Maya bug"
+
+ try:
+ count = int(self._fn.childCount())
+ except OverflowError:
+ # Maya does this sometimes and you'd be lucky if
+ # Python catches onto it. More likely it will
+ # fatal crash your Maya, as it likely accesses
+ # a bad memory address.
+ raise
+
+ for index in range(count):
try:
mobject = self._fn.child(index)
@@ -2131,10 +2193,10 @@ def key(self, time, value, interpolation=Linear):
self._fna.addKey(time, value, interpolation, interpolation)
def keys(self, times, values, interpolation=Linear):
- times = map(
+ times = list(map(
lambda t: Seconds(t) if isinstance(t, (float, int)) else t,
times
- )
+ ))
try:
self._fna.addKeys(times, values)
@@ -2191,6 +2253,9 @@ def __bool__(self):
# Python 3
__nonzero__ = __bool__
+ def __round__(self, digits=2):
+ return round(self.read(), digits)
+
def __float__(self):
"""Return plug as floating point value
@@ -2431,11 +2496,11 @@ def __iter__(self):
for value in values:
yield value
- def __getitem__(self, index):
+ def __getitem__(self, logicalIndex):
"""Read from child of array or compound plug
Arguments:
- index (int): Logical index of plug (NOT physical, make note)
+ logicalIndex (int): Logical index of plug (NOT physical, make note)
Example:
>>> _ = cmds.file(new=True, force=True)
@@ -2450,21 +2515,45 @@ def __getitem__(self, index):
>>> node["translate"][2].read()
5.1
+ # Elements are accessed by logical index, rather than physical
+ >>> tm = createNode("transform")
+ >>> mult = createNode("multMatrix")
+ >>> plug = mult["matrixIn"]
+ >>> plug[0] << tm["matrix"]
+ >>> plug[1] << tm["parentMatrix"][0]
+ >>> plug[2] << tm["worldMatrix"][0]
+
+ >>> plug[2].connection(plug=True) == tm["worldMatrix"]
+ True
+
+ # Notice how index 2 remains index 2 even on disconnect
+ # The physical index moves to 1.
+ >>> plug[1].disconnect()
+ >>> plug[2].connection(plug=True) == tm["worldMatrix"]
+ True
+
"""
cls = self.__class__
- if isinstance(index, int):
+ if isinstance(logicalIndex, int):
# Support backwards-indexing
- if index < 0:
- index = self.count() - abs(index)
+ if logicalIndex < 0:
+ logicalIndex = self.count() - abs(logicalIndex)
if self._mplug.isArray:
- item = self._mplug.elementByLogicalIndex(index)
+ # Preserve behavior from MEL
+ #
+ # NOTE: Physical index makes a lot more sense for
+ # programmatic use, but it isn't reliable when
+ # accessing native Maya array attributes like worldMatrix
+ # In that case, there is no index-0, even though a logical
+ # index 0 most definitely always (?) exists.
+ item = self._mplug.elementByLogicalIndex(logicalIndex)
return cls(self._node, item, self._unit)
elif self._mplug.isCompound:
- item = self._mplug.child(index)
+ item = self._mplug.child(logicalIndex)
return cls(self._node, item, self._unit)
else:
@@ -2472,7 +2561,7 @@ def __getitem__(self, index):
"%s does not support indexing" % self.path()
)
- elif isinstance(index, string_types):
+ elif isinstance(logicalIndex, string_types):
# Compound attributes have no equivalent
# to "MDependencyNode.findPlug()" and must
# be searched by hand.
@@ -2481,14 +2570,14 @@ def __getitem__(self, index):
child = self._mplug.child(child)
_, name = child.name().rsplit(".", 1)
- if index == name:
+ if logicalIndex == name:
return cls(self._node, child)
else:
raise TypeError("'%s' is not a compound attribute"
% self.path())
- raise ExistError("'%s' was not found" % index)
+ raise ExistError("'%s' was not found" % logicalIndex)
def __setitem__(self, index, value):
"""Write to child of array or compound plug
@@ -2527,30 +2616,84 @@ def __init__(self, node, mplug, unit=None, key=None, modifier=None):
self._modifier = modifier
def plug(self):
+ """Return the MPlug of this cmdx.Plug"""
return self._mplug
+ def attribute(self):
+ """Return the attribute MObject of this cmdx.Plug"""
+ return self._mplug.attribute()
+
@property
def isArray(self):
return self._mplug.isArray
@property
def arrayIndices(self):
- if not self.isArray:
+ if not self._mplug.isArray:
raise TypeError('{} is not an array'.format(self.path()))
- return self._mplug.getExistingArrayAttributeIndices()
+
+ # Convert from `p_OpenMaya_py2.rItemNot3Strs` to list
+ return list(self._mplug.getExistingArrayAttributeIndices())
@property
def isCompound(self):
return self._mplug.isCompound
- def next_available_index(self, start_index=0):
+ def nextAvailableIndex(self, startIndex=0):
+ """Find the next unconnected element in an array plug
+
+ Array plugs have both a "logical" and "physical" index.
+ Accessing any array plug via index means to access it's
+ logical element.
+
+ [0]<---- plug1
+ [.]
+ [.]
+ [3]<---- plug2
+ [4]<---- plug3
+ [5]<---- plug4
+ [.]
+ [7]<---- plug5
+
+ In the above scenario, 5 plugs are connected by 5 physical
+ indices, and yet the last index is 7. 7 is a logical index.
+
+ This function walks each element from the `startIndex` in search
+ of the first available index, in this case [1].
+
+ Examples:
+ >>> transform = createNode("transform")
+ >>> mult = createNode("multMatrix")
+ >>> mult["matrixIn"][0] << transform["matrix"]
+ >>> mult["matrixIn"][1] << transform["matrix"]
+ >>> mult["matrixIn"][2] << transform["matrix"]
+
+ # 3 logical indices are occupied
+ >>> mult["matrixIn"].count()
+ 3
+
+ # Disconnecting affects the physical count
+ >>> mult["matrixIn"][1].disconnect()
+ >>> mult["matrixIn"].count()
+ 2
+
+ # But the last logical index is still 2
+ >>> mult["matrixIn"].arrayIndices
+ [0, 2]
+
+ # Finally, let's find the next available index
+ >>> mult["matrixIn"].nextAvailableIndex()
+ 1
+
+ """
+
# Assume a max of 10 million connections
max_index = 1e7
- while start_index < max_index:
- if self[start_index].connection() is None:
- return start_index
- start_index += 1
+ while startIndex < max_index:
+ if self[startIndex].connection() is None:
+ return startIndex
+ startIndex += 1
# No connections means the first index is available
return 0
@@ -2598,7 +2741,7 @@ def append(self, value, autofill=False):
raise TypeError("\"%s\" was not an array attribute" % self.path())
if autofill:
- index = self.next_available_index()
+ index = self.nextAvailableIndex()
else:
index = self.count()
@@ -2681,7 +2824,7 @@ def asTransformationMatrix(self, time=None):
>>> node["translateY"] = 12
>>> node["rotate"] = 1
>>> tm = node["matrix"].asTransform()
- >>> map(round, tm.rotation())
+ >>> [round(v, 1) for v in tm.rotation()]
[1.0, 1.0, 1.0]
>>> list(tm.translation())
[0.0, 12.0, 0.0]
@@ -2935,10 +3078,6 @@ def lockAndHide(self):
self.lock()
self.hide()
- def attribute(self):
- """Return the attribute MObject of this plug"""
- return self._mplug.attribute()
-
@property
def niceName(self):
"""The nice name of this plug, visible in e.g. Channel Box
@@ -3185,7 +3324,7 @@ def animate(self, values, interpolation=None):
"""
- times, values = map(UiUnit(), values.keys()), values.values()
+ times, values = list(map(UiUnit(), values.keys())), values.values()
anim = createNode(_find_curve_type(self))
anim.keys(times, values, interpolation=Linear)
anim["output"] >> self
@@ -3380,6 +3519,7 @@ def node(self):
lock_and_hide = lockAndHide
array_indices = arrayIndices
type_class = typeClass
+ next_available_index = nextAvailableIndex
class TransformationMatrix(om.MTransformationMatrix):
@@ -5024,13 +5164,16 @@ def addAttr(self, node, attr):
False
# Protect against adding an attribute twice
- >>> with DagModifier() as mod:
- ... node = mod.createNode("transform")
- ... attr1 = mod.addAttr(node, Boolean("sameAttr"))
- ... attr2 = mod.addAttr(node, Double("sameAttr"))
- Traceback (most recent call last):
- ...
- ExistError: Same attribute added twice: .sameAttr
+ >>> try:
+ ... with DagModifier() as mod:
+ ... node = mod.createNode("transform")
+ ... attr1 = mod.addAttr(node, Boolean("sameAttr"))
+ ... attr2 = mod.addAttr(node, Double("sameAttr"))
+ ... except Exception as e:
+ ... assert isinstance(e, ExistError)
+ ... else:
+ ... assert False
+ >>>
# But allow the same attributes to be added to different nodes
>>> with DagModifier() as mod:
@@ -5294,12 +5437,14 @@ def tryConnect(self, src, dst):
Examples:
>>> node = createNode("transform")
>>> node["translateY"].lock()
- >>> with DGModifier() as mod:
- ... mod.connect(node["tx"], node["ty"])
- ...
- Traceback (most recent call last):
- ...
- LockedError: Channel locked, cannot connect 'translateY'
+ >>> mod = DGModifier()
+ >>> try:
+ ... mod.connect(node["tx"], node["ty"])
+ ... except Exception as e:
+ ... assert isinstance(e, LockedError)
+ ... else:
+ ... assert False
+ >>>
# Now let's tryConnect
>>> with DGModifier() as mod:
@@ -5528,8 +5673,8 @@ class DagModifier(_BaseModifier):
True
>>> "myChild" in cmds.ls()
True
- >>> parent.child().name()
- u'myChild'
+ >>> parent.child().name() == 'myChild'
+ True
>>> mod = DagModifier()
>>> _ = mod.delete(child)
>>> mod.doIt()
@@ -5641,11 +5786,11 @@ def __exit__(self, exc_type, exc_value, tb):
def ls(*args, **kwargs):
- return map(encode, cmds.ls(*args, **kwargs))
+ return list(map(encode, cmds.ls(*args, **kwargs)))
def selection(*args, **kwargs):
- return map(encode, cmds.ls(*args, selection=True, **kwargs))
+ return list(map(encode, cmds.ls(*args, selection=True, **kwargs)))
def createNode(type, name=None, parent=None):
@@ -5948,19 +6093,56 @@ def objExists(obj):
return True
-# PEP08
-sl = selection
-create_node = createNode
-get_attr = getAttr
-set_attr = setAttr
-add_attr = addAttr
-list_relatives = listRelatives
-list_connections = listConnections
-connect_attr = connectAttr
-obj_exists = objExists
-current_time = currentTime
+# Possible up-axes
+Y = "y"
+Z = "z"
+
+
+if __maya_version__ >= 2019:
+ def upAxis():
+ """Get the current up-axis as string
-# Speciality functions
+ Returns:
+ string: "y" for Y-up, "z" for Z-up
+
+ """
+
+ return om.MGlobal.upAxis()
+
+ def setUpAxis(axis=Y):
+ if axis == Y:
+ om.MGlobal.setYAxisUp()
+ else:
+ om.MGlobal.setZAxisUp()
+
+else:
+ def upAxis():
+ return cmds.optionVar(query="upAxisDirection")
+
+ def setUpAxis(axis=Y):
+ cmds.optionVar(stringValue=("upAxisDirection", axis))
+ cmds.warning(
+ "Changing up-axis via cmdx in Maya 2019 "
+ "or below requires a restart."
+ )
+
+
+if ENABLE_PEP8:
+ sl = selection
+ create_node = createNode
+ get_attr = getAttr
+ set_attr = setAttr
+ add_attr = addAttr
+ list_relatives = listRelatives
+ list_connections = listConnections
+ connect_attr = connectAttr
+ obj_exists = objExists
+ current_time = currentTime
+ up_axis = upAxis
+ set_up_axis = setUpAxis
+
+
+# Special-purpose functions
kOpen = om1.MFnNurbsCurve.kOpen
kClosed = om1.MFnNurbsCurve.kClosed
diff --git a/tests.py b/tests.py
index 69edcea..27adef2 100644
--- a/tests.py
+++ b/tests.py
@@ -142,21 +142,23 @@ def test_getattrtime():
edit=True,
time=(0, 24),
attribute="translateY",
+ inTangentType="linear",
outTangentType="linear")
# These floating point values can differ ever so slightly
- assert_almost_equals(transform["ty"].read(time=0), 0.0, places=5)
+ assert_almost_equals(transform["ty"].read(time=0.0), 0.0, places=5)
assert_almost_equals(transform["ty"].read(time=0.5), 5.0, places=5)
- assert_almost_equals(transform["ty"].read(time=1), 10.0, places=5)
+ assert_almost_equals(transform["ty"].read(time=1.0), 10.0, places=5)
# From the current context (Maya 2018 and above)
if hasattr(om.MDGContext, "makeCurrent"):
- with cmdx.DGContext(0):
+ with cmdx.DGContext(0.0):
assert_almost_equals(transform["ty"].read(), 0.0, places=5)
with cmdx.DGContext(0.5):
assert_almost_equals(transform["ty"].read(), 5.0, places=5)
- with cmdx.DGContext(1):
+ with cmdx.DGContext(1.0):
assert_almost_equals(transform["ty"].read(), 10.0, places=5)
+
# Custom units
with cmdx.DGContext(0, cmdx.TimeUiUnit()):
assert_almost_equals(transform["ty"].read(), 0.0, places=5)
@@ -164,6 +166,7 @@ def test_getattrtime():
assert_almost_equals(transform["ty"].read(), 5.0, places=5)
with cmdx.DGContext(24, cmdx.TimeUiUnit()):
assert_almost_equals(transform["ty"].read(), 10.0, places=5)
+
# Alternate syntax
with cmdx.DGContext(cmdx.TimeUiUnit()(0)):
assert_almost_equals(transform["ty"].read(), 0.0, places=5)
@@ -459,9 +462,9 @@ def test_modifier_history():
try:
mod.doIt()
except cmdx.ModifierError as e:
- pass
-
- tasks = [task[0] for task in e.history]
- assert_equals(tasks[0], "createNode")
- assert_equals(tasks[1], "connect")
- assert_equals(tasks[2], "setAttr")
+ tasks = [task[0] for task in e.history]
+ assert_equals(tasks[0], "createNode")
+ assert_equals(tasks[1], "connect")
+ assert_equals(tasks[2], "setAttr")
+ else:
+ assert False, "I should have failed"