Pattern Matching¶
The API of Maya has a well-defined syntax to describe DG names and DAG paths
but the solution offered to match wildcard patterns, through the use of
methods such as maya.OpenMaya.MGlobal.getSelectionListByName()
, can
sometimes lead to unexpected results.
As an example, Maya defines the pattern |*
as matching only the DAG nodes
of depth 1, that is the nodes directly parented under the world. Therefore,
when using a similar pattern applied to the underworld, for instance
node|shape->|*
, one would intuitively expect that only the nodes located
directly beneath the underworld are to be matched. Instead, Maya’s
implementation leads to match all the nodes at any depth below the underworld,
which is inconsistent.
To alleviate this lack of predictability and to add a whole new set of
possibilities loosely borrowed from Python’s re
module, a new specification
dedicated to matching name and path patterns is being used across the Bana
extensions, mostly through the bnFind*()
and bnGet*()
methods (see
Retrieving Nodes).
This pattern matching specification introduces:
- a new syntax built upon Maya’s DG names and DAG paths syntaxes, with support for four wildcard operators
*
,+
,?
, and.
.- a well-defined set of matching rules describing the expected behaviour when using these wildcards in each possible scenario.
Syntax¶
The standard syntax defined by Maya, and recognized by Bana, describes DG names
and DAG paths as they are expected to be returned by methods like
maya.OpenMaya.MFnDependencyNode.name()
and
maya.OpenMaya.MFnDagNode.fullPathName()
:
alpha ::= "a"..."z" | "A"..."Z" | "_" character ::=alpha
| "0"..."9" name ::=alpha
character
* full_name ::= (name
":")*name
path ::= ("|"full_name
)+ full_path ::=path
("->"path
)* "->"?
The library Bana extends the standard syntax by adding support for the four wildcard operators:
wcard ::= "*" | "+" | "?" | "." wcard_name ::= (alpha
|wcard
+) (character
|wcard
+)* wcard_full_name ::= (":"wcard
+ |wcard_name
) (":"wcard_name
)* wcard_path ::= ("|"wcard_full_name
|wcard
+)+ wcard_full_path ::=wcard_path
("->"wcard_path
)* "->"?
Note
The syntax groups are listed in ascending precedence order. In other words: character < name < full name < path < full path. This is useful for determining the context.
In English¶
Names
can identify DG nodes, excluding the ones carrying any
namespace or hierarchy information. They are made of character
elements, that is alphanumeric characters, underscores, and wildcards.
Full names
can fully identify any DG node. They are
composed by one or more name
elements, each separated by the namespace
delimiter :
.
Paths
can identify DAG nodes, excluding the ones carrying any
underworld information. They are composed by one or more
full name
elements, each starting with the hierarchy
delimiter |
.
Full paths
can fully identify any DAG node. They are
composed by one or more path
elements, each separated by the
underworld delimiter ->
.
Patterns can be checked against any of these syntax groups using the
corresponding bana.OpenMaya.MGlobal.bnIsValid*()
method:
>>> import bana
>>> bana.initialize()
>>> from maya import OpenMaya
>>> OpenMaya.MGlobal.bnIsValidName('node')
True
>>> OpenMaya.MGlobal.bnIsValidName('node_*', allowWildcards=True)
True
>>> OpenMaya.MGlobal.bnIsValidName('ns:node')
False
>>> OpenMaya.MGlobal.bnIsValidFullName('ns:node')
True
>>> OpenMaya.MGlobal.bnIsValidFullName('*:node', allowWildcards=True)
True
>>> OpenMaya.MGlobal.bnIsValidFullName('|node')
False
>>> OpenMaya.MGlobal.bnIsValidPath('|node')
True
>>> OpenMaya.MGlobal.bnIsValidPath('*|node', allowWildcards=True)
True
>>> OpenMaya.MGlobal.bnIsValidPath('|root->|node')
False
>>> OpenMaya.MGlobal.bnIsValidFullPath('|root->|node')
True
>>> OpenMaya.MGlobal.bnIsValidFullPath('*->|node', allowWildcards=True)
True
TL;DR¶
The composition of names, full names, paths, and full paths, can approximately be summed up as follows:
Matching Rules¶
Depending on where a wildcard operator is located within a pattern, it might
end up matching a certain number of occurrences of either one of the
character
, name
, full name
, or
path
syntax groups. For example the wildcard in the pattern
|node_*
matches a name formed by any number of characters, but the same
wildcard in the pattern *|node
matches a path composed by any number of
full names (e.g.: |root|parent|node
).
In order to understand what a wildcard, or a combination of wildcards, will precisely match, there are two aspects to take into consideration:
- the context in which the wildcards are defined.
- the number of occurrences that the wildcards describe.
Context¶
The context represents the syntax group to be matched. It can be determined by looking at the delimiters surrounding the wildcards, picking the one with the highest precedence, and retrieving the syntax group associated with it as defined in this table sorted in descending precedence order:
delimiter | syntax group |
---|---|
character | name |
: |
full name |
| |
path |
-> |
full path |
For example, the wildcard in the pattern |ns:*|leaf
is surrounded by the
delimiters :
and |
, respectively representing the full name and
path syntax groups, hence the context is full name since it has a higher
precedence than path.
When the wildcards are located at the beginning or the end of a string, then
the only delimiter found is used to define the context. For example, the
context for the wildcard in the pattern *->|leaf
is full path, as per the
->
delimiter.
If one of the delimiters is a character, then the context is bound to be
name. The pattern |node*->leaf
is an example of such a case.
Finally, if a pattern is only composed of wildcards, then the global context
defined by the matching method called is used. For example the method
MGlobal.bnMatchFullPath()
defines the global context full path.
Number of Occurrences¶
Remember how, according to the rules of syntax composition, a syntax group might be made of one or more elements of another syntax group. With this in mind, the number of occurences specifies how many elements of a context needs to be matched.
The special characters *
, +
, ?
, and .
all carry the same
purpose of matching a context element but a different number of times. The
quantity being described by these wildcards is the same as their regular
expression language counterparts, meaning that:
*
matches 0 or more occurrences of a context element.+
matches 1 or more occurrences of a context element.?
matches 0 or 1 occurrences of a context element..
matches 1 occurrence of a context element.
As an example, if the context is full name
, then the
quantifier defines how many name
elements needs to be matched: the
wildcard in the pattern |ns:+|leaf
will match 1 or more names separated
by the :
delimiter, thus forming in the end a full name.
Matching Nothing¶
It sometimes makes sense to allow a wildcard to match zero occurrences.
This is especially useful when performing recursive searches where the pattern
*|leaf
can match any node named leaf
, including the one directly
parented under the world, and where the pattern |ns:*:leaf
can match nodes
such as |ns:ns2:ns3:leaf
and |ns:leaf
.
In some other cases, this doesn’t make too much sense. For example the pattern
|ns:*
cannot match any node named |ns:
because this isn’t a valid
pattern.
To check if a wildcard is allowed to match zero occurrences or not, see the TL;DR table.
TL;DR¶
The table below regroups all the possible valid uses of wildcard operators located between two adjacent delimiters.
Reminder
If the occurrence of wildcard is not listed in this table, it is bound to belong to the name context.
pattern | example | context | can match nothing |
---|---|---|---|
^@$ |
@ |
same as the global context | yes |
^@: |
@:leaf |
full name | yes |
^@| |
@|leaf |
path | yes |
^@-> |
@->|leaf |
full path | yes |
:@$ |
|ns:@ |
full name | no |
:@: |
|ns:@:leaf |
full name | yes |
:@| |
|ns:@|leaf |
full name | no |
:@-> |
|ns:@->|leaf |
full name | no |
|@$ |
|root|@ |
path | no |
|@: |
|root|@:leaf |
full name | yes |
|@| |
|root|@|leaf |
path | yes |
|@-> |
|root|@->|leaf |
path | no |
->@$ |
|root->@ |
full path | yes |
->@| |
|root->@|leaf |
path | yes |
->@-> |
|root->@->|leaf |
full path | yes |
Note
The characters ^
and $
used in the table respectively refer to the
start and the end of a string. As for the character @
, it is to be
replaced by one or more wildcards.
Combining Wildcards¶
If needed, it is possible to come up with some fancy patterns by successively
writing multiple wildcard operators that will combine to define a specific
number of occurrences. For example, the pattern ...
matches 3 occurrences
of the context element, while .+
matches at least 2 occurrences, and
..??
matches from 2 to 4 occurrences.
The number of occurrences to match resulting from such a combination is easy to
figure out. Let’s consider the regular expression notation {m,n}
describing
from m to n occurrences
, and {m,}
that specifies at least m
occurrences
. Rewriting the four wildcard operators following this notation
gives:
*
->{0,}
+
->{1,}
?
->{0,1}
.
->{1,1}
Combining wildcard operators is equivalent to adding their range of
occurrences. From the previous example, the pattern .+
equals to
{1,1} + {1,}
, that is {2,}
, and the pattern ..??
equals to
{1,1} + {1,1} + {0,1} + {0,1}
, that is {2,4}
.
Namespace Construct¶
The pattern |root|.
allows matching any node which has root
as direct
parent but it is not enough if filtering namespaces is also required. This
is why, as per the syntax rules, a special construct has
been added to allow a full name to start with the :
delimiter if it is
followed by one or more wildcards. With this addition, the pattern |root|:.
makes it possible to match any node directly parented under root
that does
not belong to any namespce.
Examples¶
Matching DG Nodes¶
>>> import bana
>>> bana.initialize()
>>> from maya import OpenMaya
>>> # Match the nodes named 'leaf' belonging to any namespace.
>>> OpenMaya.MGlobal.bnMatchFullName('*:leaf', 'leaf')
True
>>> OpenMaya.MGlobal.bnMatchFullName('*:leaf', 'ns:leaf')
True
>>> OpenMaya.MGlobal.bnMatchFullName('*:leaf', 'nsa:nsb:leaf')
True
>>> # Match the nodes directly nested under a namespace 'ns'.
>>> OpenMaya.MGlobal.bnMatchFullName('ns:.', 'ns:leaf')
True
>>> OpenMaya.MGlobal.bnMatchFullName('ns:.', 'ns:nsa:leaf')
False
>>> # Match the nodes recursively nested under a namespace 'ns'.
>>> OpenMaya.MGlobal.bnMatchFullName('ns:+', 'ns:leaf')
True
>>> OpenMaya.MGlobal.bnMatchFullName('ns:+', 'ns:nsa:leaf')
True
>>> OpenMaya.MGlobal.bnMatchFullName('ns:+', 'ns:nsa:nsb:leaf')
True
Matching DAG Nodes¶
>>> import bana
>>> bana.initialize()
>>> from maya import OpenMaya
>>> # Match the nodes directly parented under the world.
>>> OpenMaya.MGlobal.bnMatchPath('|.', '|leaf')
True
>>> OpenMaya.MGlobal.bnMatchPath('|.', '|ns:leaf')
True
>>> OpenMaya.MGlobal.bnMatchPath('|.', '|root|leaf')
False
>>> # Match the nodes directly parented under the world but not belonging to
... # any namespace.
>>> OpenMaya.MGlobal.bnMatchPath('|:.', '|leaf')
True
>>> OpenMaya.MGlobal.bnMatchPath('|:.', '|ns:leaf')
False
>>> OpenMaya.MGlobal.bnMatchPath('|:.', '|root|leaf')
False
>>> # Match the nodes containing 'Shape' anywhere in the hierarchy but not
... # belonging to any namespace.
>>> OpenMaya.MGlobal.bnMatchPath('+|*Shape*', '|cube|cubeShape')
True
>>> OpenMaya.MGlobal.bnMatchPath('+|*Shape*', '|root|sphere|sphereShape1')
True
>>> OpenMaya.MGlobal.bnMatchPath('+|*Shape*', '|cube|ns:cubeShape')
False
>>> # Match the nodes containing 'Shape' anywhere in the hierarchy.
>>> OpenMaya.MGlobal.bnMatchPath('+|*:*Shape*', '|cube|cubeShape')
True
>>> OpenMaya.MGlobal.bnMatchPath('+|*:*Shape*', '|root|sphere|sphereShape1')
True
>>> OpenMaya.MGlobal.bnMatchPath('+|*:*Shape*', '|cube|ns:cubeShape')
True