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:
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)
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:
This 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:
This 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:
This 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:
This 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:
.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 ) { ... }
Stream stream = myChannel[“myStream”];
if( myChannel.ContainsKey( “myStream” ) { ... }
myChannel.Add( “myStream”, myStream );
foreach( Stream stream in myChannel ) { ... }
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 ) { ... }
Handle handle = myStream[18];
if( myStream.ContainsKey(18) { ... }
myStream.Add( 18, myHandle );
foreach( Handle handle in myStream ) { ... }
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:
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:
.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:
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.
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:
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.
Creates a new handle at the specified index, then calls handle.SetFromDotNetObject() using the specified T value.
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;
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.