The meta data classes of the C++ API are ported to the .NET API just like all other Maya API classes. In addition, several enhancements have been made to take advantage of the power of .NET.
Metadata is a very important part of the Maya API, and enables clients to describe their custom data (its structure, the types, how to access the data per element, and so forth). .NET features can make the process much simpler and more powerful than it is in C++.
Several interesting examples that demonstrate the .NET enhancements described on this page are included with the examples that are shipped with Maya (see Compiling examples). Open the examples.sln
solution (in the devkit\dotnet\examples
folder of your Developer Kit installation; see Setting up your build environment: Windows environment (64-bit)) using Microsoft Visual Studio, and you can find these examples in the MetaData
folder. The following are some interesting examples:
CreateMetadataFromTypeCmd.cs
(tested with CreateMetadataXMLCmd.mel
)
This example shows how you can use the new StructureClass
assembly attribute and the special class StreamForType<T>
for a .NET type (that is, MyStructureClass in the example).
This eliminates the need for the Structure
and Handle
classes since everything is done automatically by the generic class StreamForType<T>
.
The example also shows how to use LINQ to perform queries on any of the collections (that is, querying elements of a stream in the example)
WriteMetadataToConsoleCmd.cs
(tested with every MEL script in the MetaData
folder)
This example shows how simple enumerating channels, streams, structure members and stream handles can be when using the foreach
syntax, thanks to the support of the .NET interface IEnumerable
in the Associations, Channel, Stream and Structure classes.
The following is a list of the main classes of the metadata API, and a quick summary of each along with a list of C++ methods that have been ported to .NET:
This is an entry point to access the meta data from a Maya object (for example, by getting the MFnDependencyNode.metadata
property).
It’s basically a simple container of channels.
A few interesting methods ported from C++ are:
Channel findChannel(string)
searches for a channel given its namevoid setChannel(Channel)
adds a channel, or replaces an existing channel that has the same namebool removeChannel(string)
removes a channel given its nameAssociationsIterator begin()
returns a C++/STL-style iterator to iterate over the channelsThis is a collection of streams that are grouped together, either because they share the same topology (the same stream indices) or because the user decides they should logically be in the same group (for example, all the streams for a simulation effect would be in the same channel and given a channel name that is the name of the effect).
A channel can be named, but some names are reserved (for example, vertex
is reserved to indicate that its streams have one element per vertex).
A few interesting methods ported from C++ are:
Stream findDataStream(string)
looks for a stream given its nameStream setDataStream(Stream)
adds a streamvoid removeDataStream(string)
removes a stream given its nameChannelIterator begin()
returns a C++/STL-style iterator to iterate over the streamsbool removeElement(Index elementIndex)
removes the specified element from every stream found in the channelbool addElement(Index elementIndex)
adds the specified element to every stream found in the channelThis is a container of data on the object (for example, on a mesh, in the channel vertex
, you have as many data elements as you have vertices in the stream).
A few interesting methods ported from C++ are:
Stream(Structure, string)
creates a Stream
object using the specified structure and given the specified namebool setElement(Index, Handle)
replaces the data at a given index with that of anotherHandle element(Index)
obtains a handle of the data at a given indexStreamIterator begin()
returns a C++/STL-style iterator to iterate over the handlesThis class describes the meta data found at each element of the stream.
Its method AddMember
is used to define each field of the meta data.
For instance, if you want one float and one boolean to be stored at every element, first create a structure and call AddMember(kFloat,1,”MyFloatField”)
and AddMember(kBoolean,1,”MyBooleanField”)
.
A few interesting methods ported from C++ are:
bool addMember( Member.eDataType, uint arraySize, string memberName )
adds a member with the specified type, size (typically 1), and name to the structureStructureIterator begin()
returns a C++/STL-style iterator to iterate over the membersThis class allows you to access the data for a specified element of the stream.
With a handle, you can get or set the data of the stream element.
A few interesting methods ported from C++ are:
bool setPositionByMemberIndex(uint)
indicates the member of the structure that this handle is associated withint[] asInt32Array
gets or sets the integer data as an array of integers (even if there is only one integer)float[] asFloatArray
gets or sets the float data as an array of floats (even if there is only one float).NET provides several enhancements to the metadata API. Here are the main classes of the API with the .NET enhancements:
Because it acts as a dictionary of Channel
objects, the class supports the .NET interface IDictionary<Channel>
.
This interface enables you to use the following standard methods/properties that are expected from a dictionary in .NET:
Channel channel;
if( myAssociations.TryGetValue( “vertex”, out channel ) { ... }
This method allows you to look for a channel given its name and tests for its existence at the same time.
Channel channel = myAssociations[“vertex”];
This indexer also looks for a channel given its name, but in this case, the channel is expected to be found (throws KeyNotFoundException
otherwise).
if(myAssociations.ContainsKey( “vertex” ) { ... }
This simply tests if a channel with the specified name exists. The channel is not returned.
myAssociations.Add( “vertex”, myChannel );
This adds the channel, or throws ArgumentException
if a channel with the same name already exists.
foreach( Channel chnl in myAssociations ) { ... }
This lets you enumerate all the channels using this very simple syntax instead of the complicated C++/STL syntax:
for( AssociationsIterator iter = myAssociations.begin(); iter.notEqual( myAssociations.end() ); iter.next() ) {
Channel chnl = iter.__deref__();
...
}
Because it acts as a dictionary of Stream
objects, the class supports the .NET interface IDictionary<Stream>
.
This interface enables you to use the following standard methods/properties that are expected from a dictionary in .NET:
Stream stream;
if( myChannel.TryGetValue( “myStream”, out stream ) { ... }
This method allows you to look for a stream given its name and tests for its existence at the same time.
Stream stream = myChannel[“myStream”];
This indexer also looks for a stream given its name, but in this case, the stream is expected to be found (throws KeyNotFoundException
otherwise).
if( myChannel.ContainsKey( “myStream” ) { ... }
This simply tests if a stream with the specified name exists. The stream is not returned.
myChannel.Add( “myStream”, myStream );
This adds the stream or throws ArgumentException
if a stream with the same name already exists.
foreach( Stream stream in myChannel ) { ... }
This lets you enumerate all the streams using this very simple syntax instead of the complicated C++/STL syntax:
for( ChannelIterator iter = myChannel.begin(); iter.notEqual( myChannel.end() ); iter.next() ) {
Stream stream = iter.__deref__();
...
}
Because it acts as a dictionary of Handle
objects, the class supports the .NET interface IDictionary<Handle>
.
This enables you to use the following standard methods/properties that are expected from a dictionary in .NET:
Handle handle;
if( myStream.TryGetValue( 18, out handle ) { ... }
This method allows you to look for a handle given its index and tests for its existence at the same time.
Handle handle = myStream[18];
This indexer also looks for a handle given its index, but in this case, the handle is expected to be found (throws KeyNotFoundException
otherwise).
if( myStream.ContainsKey(18) { ... }
This simply tests if a handle with the specified index exists. The handle is not returned.
myStream.Add( 18, myHandle );
This adds the handle or throws ArgumentException
if a handle with the same index already exists.
foreach( Handle handle in myStream ) { ... }
This lets you enumerate all the handles using this very simple syntax instead of the complicated C++/STL syntax:
for( StreamIterator iter = myStream.begin(); iter.notEqual( myStream.end() ); iter.next() ) {
Handle handle = iter.__deref__();
...
}
IEnumerable<Member>
was added in order to enable this syntax:
foreach( Member member in myStructure ) { ... }
This lets you enumerate all the members using this very simple syntax instead of the complicated C++/STL syntax:
for( StructureIterator iter = myStream.begin(); iter.notEqual( myStream.end() ); iter.next() ) {
Member member = iter.__deref__();
...
}
The C++ API uses a single method using pointers (for example, int *asInt32()
) to let you access the data for a given type. You can use it as a pointer to a single integer (for example, myInteger = *asInt32()
) or an array of integer values (for example, asInt32()[6]
) if you had specified a size of greater than 1 when you created the corresponding member in the Structure
object.
For .NET, using pointers in the API was not feasible so two properties are provided per type:
int[] asInt32Array
to get or set the integer data as an array of integersint asInt32
to get or set the single integer (will throw an ApplicationException
if the size found in the corresponding structure member is greater than 1)float[] asFloatArray
to get or set the float data as an array of floatsfloat asFloat
to get or set the single float (will throw an ApplicationException
if the size found in the corresponding structure member is greater than 1)Using the Structure
class to describe the data of an element and then the Handle
class to access the data is quite low-level and C++ like, for example:
Structure.AddMember( kInt32, 1, “myIntMember”)
to add an int member,Handle.setPositionByMemberIndex(uint)
to specify the member of the handle that you want to accessHandle.asInt
to get or set the integer data of the member.NET simplifies the whole process, as long as you have a class or structure that describes the data where each member of your type is a member in the expected Structure
object.
In your plug-in, if you use the new StructureClass
assembly attribute and provide your type that describes the structure of your metadata, .NET can automatically create the Structure object for you:
[assembly: StructureClass(typeof(MyNameSpace.MyStructureClass))]
namespace MyNameSpace
{
public class MyStructureClass
{
public int a;
public int b { get; set; }
public float[] c = new float[3];
}
}
Thanks to introspection, .NET can find all the public fields and properties in your class (or struct) and automatically create a Structure
object that is registered using Structure.registerStructure
.
The name of the Structure is the full name of your type (for example, MyNameSpace.MyStructureClass
). The name of each Structure member is the name of the corresponding member in your type.
If you use the new StructureClass assembly attribute (see previous section) with the type of a .NET object, you can use the new following methods of the Handle
class:
public void SetFromDotNetObject( Object dotNetObj, Structure structure );
This does a member wise copy from your .NET object to the Handle
object.
This is the equivalent of the following pseudo-code:
for each public member in the type of dotNetObj
find corresponding member in Structure object
call this.setPositionByMemberIndex( index of member found )
if type is int32
this.asInt = get value of member in dotNetObj
else if type is float
this.asFloat = get value of member in dotNetObj
…
next
public void CopyToDotNetObject(Object dotNetObj, Structure structure);
This does a member wise copy from the Handle
object to your .NET object.
This is the equivalent of the following pseudo-code:
for each public member in the type of dotNetObj
find corresponding member in Structure object
call this.setPositionByMemberIndex( index of member found )
if type is int32
dotNetObj.SetValue( this.asInt )
else if type is float
dotNetObj.SetValue( this.asFloat )
…
next
These methods become unnecessary, however, when you use the new generic class StreamForType<T>
explained in the next section.
While Stream is the class to use when you can’t provide a .NET type that describes your Structure, the class StreamForType<T>
should be used when you have provided your type with the new StructureClass assembly attribute.
Use the same type for the T parameter of this generic class and you are given a Stream-like class that is completely tailored for your type.
This generic class is a wrapper to a Stream
object so you can create it in two ways. The following are the two constructors:
StreamForType<T>(string streamName)
This constructor looks for an existing Structure called with the full name of your type T (it should find it if you used the StructureClass assembly attribute). It then creates a new Stream
object using the specified name and the structure found and wraps this new Stream
object.
StreamForType<T>(Stream baseStream)
This constructor wraps the specified stream but also ensures that the structure of this base stream is compatible with your type (it should be if you used the StructureClass assembly attribute).
While the Stream
class can be seen as a dictionary of Handle
objects, the StreamForType<T>
generic class can be seen as a dictionary of T objects where T is your .NET class (or struct). This is done with the IDictionary<T>
supported by the generic class.
The following are a few interesting methods:
bool TryGetValue(Index, out T value)
Finds the Handle
object with the specified index, then calls handle.CopyToDotNetObject()
on a new instance of T. It returns the new instance; or, if the handle is not found, it returns false.
void Add(Index, T value)
Creates a new handle at the specified index, then calls handle.SetFromDotNetObject()
using the specified T value.
T this[Index key]
This enables the following syntax (MyStructureClass
is the .NET type used for the T parameter):
MyStructureClass myDotNetObj ;
myDotNetObj.myInt = 3;
myStream[18] = myDotNetObj;
The example above adds the .NET object at the specified index or replaces the current object if one already exists at that index.
MyStructureClass myDotNetObj = myStream[18];
The example above returns the object found at the specified index or throws a KeyNotFoundException
if an object does not exist at that index.
Be careful though, the following syntax compiles but does not work as expected as it does not change the element in the base stream:
myStream[18].myInt = 1.6;
Instead, the example above changes the field myInt
of the copy returned by the indexer, so it is completely useless.
For the desired result, do as follows:
MyStructureClass temp = myStream[18];
temp.myInt = 1.6;
myStream[18] = temp;
IEnumerator<T> GetEnumerator()
This enumerates all your elements with this simple syntax:
foreach( MyStructureClass myDotNetObj in myStream ) { … }
Since IEnumerable
is supported on most classes, you can perform very powerful queries with your data.
For instance, if your class has a field called amplitude, and you want to find the metadata in the vertex channel of a mesh where the amplitude is greater than 0.5, you can do this easily as follows:
Channel chn = myAssociations["vertex"];
Stream chnStream = chn[“myStream”];
var strm = new StreamForType<MyStructureClass>(chnStream);
var list = strm.Where( ( MyStructureClass obj ) =>obj.amplitude >0.5 );
foreach( MyStructureClass obj in list ) { … }
To query the vertex index, you can do as follows:
var list = strm.Where( ( KeyValuePair<Index,MyStructureClass> keyvalue ) => keyvalue.Value.amplitude > 0.5 );
foreach (var keyvalue in list ) {
Index index = keyvalue.Key;
MyStructureClass obj = keyvalue.Value;
…
}
This type of query can be done on any of the following classes: Associations, Channel, Stream and Structure because they all implement IEnumerable
, which is the only interface LINQ requires.