Skip to content

Commit

Permalink
Added docs
Browse files Browse the repository at this point in the history
  • Loading branch information
kimonmatara committed Apr 19, 2022
1 parent 36f844e commit f971894
Show file tree
Hide file tree
Showing 95 changed files with 21,741 additions and 0 deletions.
4 changes: 4 additions & 0 deletions docs/.buildinfo
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 added docs/.nojekyll
Empty file.
7 changes: 7 additions & 0 deletions docs/_sources/author.rst.txt
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.
37 changes: 37 additions & 0 deletions docs/_sources/cust_cstr_tutorial.rst.txt
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)
47 changes: 47 additions & 0 deletions docs/_sources/implementation.rst.txt
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`.
49 changes: 49 additions & 0 deletions docs/_sources/importing_and_patch_management.rst.txt
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`.
45 changes: 45 additions & 0 deletions docs/_sources/index.rst.txt
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
136 changes: 136 additions & 0 deletions docs/_sources/method_tutorial.rst.txt
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]
47 changes: 47 additions & 0 deletions docs/_sources/more_customisation.rst.txt
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.
Loading

0 comments on commit f971894

Please sign in to comment.