-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
36f844e
commit f971894
Showing
95 changed files
with
21,741 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
# Sphinx build info version 1 | ||
# This file hashes the configuration used when building these files. When it is not found, a full rebuild will be done. | ||
config: d6276c0f810dc3ed793ace52c573dc6a | ||
tags: 645f666f9bcd5a90fca523b33c5a78b7 |
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
****** | ||
Author | ||
****** | ||
|
||
`Kimon Matara <https://www.riggery.com>`_ is a Maya character rigger with a special interest in Python abstractions, | ||
automation and maths rigging. He particularly enjoys intuitive interaction design at all levels, from APIs to puppet | ||
controls. He also loves cats and electric guitars. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
Tutorial 2: Custom Constructors | ||
=============================== | ||
|
||
The :py:mod:`~paya.runtime` interface offers an elegant interface for custom | ||
instance constructors. For example, suppose you often call Maya commands with | ||
the same unwieldy argument lists to create objects, like this: | ||
|
||
.. code-block:: python | ||
skin = r.skinCluster( | ||
inf1, inf2, geo, | ||
bm=1, dr=4.5, mi=4, | ||
nw=1, omi=0, sm=0, | ||
tsb=1, wd=0 | ||
) | ||
A simpler method might look like this: | ||
|
||
.. code-block:: python | ||
skin = r.nodes.SkinCluster.create(influences, geo) | ||
This can be easily implemented using a regular Python class method: | ||
|
||
.. code-block:: python | ||
class SkinCluster: | ||
@classmethod | ||
def create(cls, influences, geo): | ||
args = list(influences) + [geo] | ||
kwargs = {'bm': 1, 'dr': 4.5, 'mi': 4.0, | ||
'nw': 1, 'omi': 0, 'sm': 0, | ||
'tsb': 1, 'wd': 0} | ||
return r.skinCluster(*args, **kwargs) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
************** | ||
Implementation | ||
************** | ||
|
||
There are two aspects to the **paya** patching mechanism. | ||
|
||
1. Class Construction | ||
--------------------- | ||
|
||
Custom **paya** classes are built in the following way: | ||
|
||
1. Where available, content is taken from user (or bundled) | ||
:ref:`template classes <template modules>`. | ||
|
||
2. The custom classes are then built using special metaclasses that | ||
manage inheritance via an overriden ``mro()``. | ||
|
||
Custom node, component and data types always shadow a PyMEL counterpart in the | ||
method resolution order. However, when it comes to **plug** (attribute) types, | ||
PyMEL only provides one class: :py:class:`pymel.core.general.Attribute`. For | ||
this reason, custom plug types are largely abstract, and constructed according | ||
to an inheritance tree defined inside ``paya/plugtree.json``. | ||
|
||
2. Instance Interception | ||
------------------------ | ||
|
||
``__new__()`` constructors on PyMEL classes inside :py:mod:`pymel.core.general`, :py:mod:`pymel.core.nodetypes` and | ||
:py:mod:`pymel.core.datatypes` are then dynamically replaced with wrappers that capture new PyNode instances and | ||
change their ``__class__`` assignment. | ||
|
||
Lookups are cached throughout. | ||
|
||
Advantages | ||
---------- | ||
|
||
- **Speed**: Instantiating PyNodes is notoriously slow, and therefore | ||
avoided entirely. | ||
|
||
- **Easy coupling / decoupling**: Unpatching PyMEL is merely a case of | ||
removing the ``__new__()`` wrappers. | ||
|
||
- **Compatibility**: PyMEL treats custom objects as its own. | ||
|
||
- **Simpler Customisation**: Inheritance is managed entirely during the | ||
rebuilding stage; users do not need to track it for class declarations. | ||
|
||
For more information, take a look inside :py:mod:`paya.pools` and :py:mod:`paya.patch`. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
****************************** | ||
Importing and Patch Management | ||
****************************** | ||
|
||
To get started with **paya**, run the following in the Script Editor: | ||
|
||
.. code-block:: python | ||
>>> import paya.runtime | ||
PyMEL has been patched successfully. | ||
At this point, PyMEL has been patched to return custom **paya** classes | ||
instead of its own. This can be confirmed thus: | ||
|
||
.. code-block:: python | ||
>>> import pymel.core as pm | ||
>>> cam = pm.PyNode('persp') | ||
>>> print(type(cam)) | ||
<class 'paya.nodetypes.transform.Transform'> | ||
You can remove the patch, and return PyMEL to its 'factory state', like this: | ||
|
||
.. code-block:: python | ||
>>> paya.runtime.stop() | ||
PyMEL has been unpatched successfully. | ||
>>> cam = pm.PyNode('persp') | ||
>>> print(type(cam)) | ||
<class 'pymel.core.nodetypes.Transform'> | ||
If you wish to re-apply the patch, **import** won't work a second time. | ||
Instead, you must call :py:meth:`~paya.runtime.Runtime.start`: | ||
|
||
.. code-block:: python | ||
>>> paya.runtime.start() | ||
PyMEL has been patched successfully. | ||
.. note:: | ||
|
||
Until they are customised, **paya** classes will merely replicate default | ||
PyMEL behaviours. This is because they always inherit from a PyMEL base | ||
class. As customisations increase, so does the potential to break | ||
pipelines that rely on unmodified PyMEL code. In such cases, care should | ||
be taken to 'bracket' patching with :py:meth:`~paya.runtime.Runtime.stop` | ||
and :py:meth:`~paya.runtime.Runtime.start`. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
Paya: PyMEL Unleashed | ||
===================== | ||
|
||
**Paya** is a `patch <https://en.wikipedia.org/wiki/Monkey_patch>`_ for | ||
`PyMEL <https://help.autodesk.com/view/MAYAUL/2022/ENU/?guid=__PyMel_index_html>`_ | ||
to overcome the limitations of the | ||
`virtual classes <https://github.com/LumaPictures/pymel/blob/master/examples/customClasses.py>`_ system. It unlocks | ||
customisation for component, attribute (including subtype) and data classes, implements true inheritance | ||
and offers a simpler authoring workflow with full support for techniques such as | ||
:doc:`operator overloading <op_overl_tutorial>`. | ||
|
||
|
||
Contents | ||
-------- | ||
|
||
- To install, see :doc:`setup`. | ||
- To start playing, browse the User Guide. | ||
- For a reference of every module in the package, see :doc:`Module Documentation <paya>`. | ||
- To peek under the hood, see :doc:`implementation`. | ||
- To report a bug, request a feature or just say hello, head over to :doc:`author`. | ||
|
||
.. toctree:: | ||
:hidden: | ||
|
||
setup | ||
|
||
.. toctree:: | ||
:caption: User Guide | ||
:hidden: | ||
|
||
Importing and Patch Management <importing_and_patch_management> | ||
Using the Runtime Interface <using_the_runtime_interface> | ||
Tutorial #1: Custom Methods <method_tutorial> | ||
Tutorial #2: Custom Constructors <cust_cstr_tutorial> | ||
Tutorial #3: Custom Operators <op_overl_tutorial> | ||
More Customisation <more_customisation> | ||
|
||
|
||
.. toctree:: | ||
:caption: Appendices | ||
:hidden: | ||
|
||
Module Documentation <paya> | ||
implementation | ||
author |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,136 @@ | ||
Tutorial #1: Custom Methods | ||
=========================== | ||
|
||
Suppose you want to create an object-oriented version of | ||
:func:`~pymel.core.modeling.pointPosition()` so that you can do this: | ||
|
||
.. code-block:: python | ||
mesh.vtx[6].getWorldPosition() | ||
Instead of this: | ||
|
||
.. code-block:: python | ||
p.pointPosition(mesh.vtx[6]) | ||
1. Identifying the Class to Customise | ||
------------------------------------- | ||
|
||
To find the class to target, you could start by inspecting, say, a vertex | ||
instance: | ||
|
||
.. code-block:: python | ||
>>> vertex = r.PyNode('pCube1').vtx[6] | ||
>>> cls = type(vertex) # vertex.__class__ also works | ||
>>> print(cls) | ||
<class 'paya.comptypes.meshVertex.MeshVertex'> | ||
This might suggest that the class you need to work on is | ||
:py:class:`~paya.comptypes.MeshVertex`. However, you probably want to add ``getWorldPosition()`` | ||
to every component type supported by :func:`~pymel.core.modeling.pointPosition()`. | ||
|
||
Since **paya** supports true inheritance, it would be better to target a more general class, one that | ||
:py:class:`~paya.comptypes.MeshVertex` as well as other types like | ||
:py:class:`~paya.comptypes.NurbsCurveCV` all inherit from. | ||
|
||
You can inspect the inheritance stack of :py:class:`~paya.comptypes.MeshVertex` | ||
via its ``__mro__`` class attribute, like this: | ||
|
||
.. code-block:: python | ||
>>> print(cls).__mro__ | ||
(<class 'paya.comptypes.meshVertex.MeshVertex'>, <class 'pymel.core.general.MeshVertex'>, <class 'paya.comptypes.mItComponent1D.MItComponent1D'>, <class 'pymel.core.general.MItComponent1D'>, <class 'paya.comptypes.mItComponent.MItComponent'>, <class 'pymel.core.general.MItComponent'>, <class 'paya.comptypes.component1D.Component1D'>, <class 'pymel.core.general.Component1D'>, <class 'paya.comptypes.discreteComponent.DiscreteComponent'>, <class 'pymel.core.general.DiscreteComponent'>, <class 'paya.comptypes.dimensionedComponent.DimensionedComponent'>, <class 'pymel.core.general.DimensionedComponent'>, <class 'paya.comptypes.component.Component'>, <class 'pymel.core.general.Component'>, <class 'pymel.core.general.PyNode'>, <class 'pymel.util.utilitytypes.proxyClass.<locals>.Proxy'>, <class 'object'>) | ||
Scanning through the members, :py:class:`~paya.comptypes.DiscreteComponent` | ||
looks like a better candidate for customisation: *discrete* suggests that | ||
point-like components will be included, but continuous components like NURBS | ||
isoparms will be excluded. | ||
|
||
2. Authoring the Class | ||
---------------------- | ||
|
||
Custom **paya** classes are defined in 'template' modules. Depending on type, | ||
these are stored inside ``paya/nodetypes``, ``paya/comptypes``, ``paya/plugtypes`` | ||
or ``paya/datatypes``. Each module name is always the uncapitalised name of the | ||
class it defines. | ||
|
||
Since we want to start a custom class for | ||
:py:class:`~paya.comptypes.DiscreteComponent`, we need to create the following | ||
file: ``paya/comptypes/discreteComponent.py``. | ||
|
||
Authoring the class is very easy. In this case, the module content amounts to this: | ||
|
||
.. code-block:: python | ||
import paya.runtime as r | ||
class DiscreteComponent: | ||
def getWorldPosition(self): | ||
return r.pointPosition(self, world=True) | ||
.. note:: | ||
|
||
Those familiar with object-oriented programming in Python might have | ||
expected to see this instead: | ||
|
||
.. code-block:: python | ||
import pymel.core.general | ||
class DiscreteComponent(pymel.core.general.DiscreteComponent): | ||
[...] | ||
This is not necessary, because template classes are rebuilt by **paya** | ||
to enforce correct inheritance. While this simplifies class authoring, | ||
it has a couple of drawbacks: | ||
|
||
- Template classes should never be imported directly (even by other | ||
template classes); instead, they must always be accessed via the | ||
:py:mod:`paya.runtime` interfaces. | ||
|
||
- If you want to call a superclass method, you can't use :obj:`super`; | ||
instead, you must correctly identify and source the parent class for | ||
the explicit form: | ||
|
||
.. code-block:: python | ||
from pymel.core.general import DiscreteComponent | ||
class MeshVertex: | ||
def getWorldPosition(self): | ||
return DiscreteComponent.getWorldPosition(self) | ||
3. Reloading and Using | ||
---------------------- | ||
|
||
Your new ``getWorldPosition()`` method won't be picked up until you clear the | ||
class pool caches. You can do this by calling | ||
:py:meth:`~paya.runtime.Runtime.rehash`: | ||
|
||
.. code-block:: python | ||
>>> r.rehash() | ||
Purged class pool paya.nodes. | ||
Purged class pool paya.plugs. | ||
Purged class pool paya.comps. | ||
Purged class pool paya.data. | ||
That's it! Your new class will now be reloaded the next time you create a | ||
component instance, and the ``getWorldPosition()`` method will be ready to use: | ||
|
||
.. code-block:: python | ||
>>> vertex = r.PyNode('pCube1').vtx[6] | ||
>>> print(vertex.getWorldPosition()) | ||
[10,10,10] | ||
# Confirm it works on other discrete components too: | ||
>>> cv = r.PyNode('curve1').cv[0] | ||
>>> print(cv.getWorldPosition()) | ||
[0.5,0.0,0.0] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
****************** | ||
More Customisation | ||
****************** | ||
|
||
======================= | ||
Base Paya Configuration | ||
======================= | ||
|
||
The following configuration flags are defined inside :py:mod:`paya.config`: | ||
|
||
.. list-table:: | ||
:widths: 25 25 50 | ||
:header-rows: 1 | ||
|
||
* - Flag | ||
- Default Value | ||
- Description | ||
* - ``patchOnLoad`` | ||
- ``True`` | ||
- Call :py:meth:`paya.runtime.Runtime.start` on import. | ||
|
||
More flags may be added in future releases. | ||
|
||
.. note:: | ||
|
||
Changes to :py:mod:`paya.config` won't be picked up until Maya is relaunched. | ||
|
||
========================= | ||
Runtime Interface Content | ||
========================= | ||
|
||
The runtime interface (:py:mod:`paya.runtime`) serves the entire content of | ||
:py:mod:`paya.cmds` which, in turn, imports the entire | ||
:py:mod:`pymel.core` namespace. Any functions or variables that you wish to | ||
make available directly on :py:mod:`paya.runtime` can therefore be added to | ||
:py:mod:`paya.cmds`. | ||
|
||
.. warning:: | ||
|
||
When adding content to :py:mod:`paya.cmds`, take care not to **shadow** | ||
factory PyMEL commands from :py:mod:`pymel.core`. Doing so may break code | ||
that accesses such commands via :py:mod:`paya.runtime`. | ||
|
||
.. note:: | ||
|
||
Changes to :py:mod:`paya.cmds` won't be picked up until | ||
:py:meth:`paya.runtime.Runtime.rehash` is called or Maya is relaunched. |
Oops, something went wrong.