C++ API のメタ データ クラスは、その他すべての Maya API クラスと同様に .NET API に移植されています。さらに、.NET のパワーを活用するため、いくつかの機能強化が行われています。
メタデータは、Maya API の非常に重要な部分であり、クライアントがカスタム データ(ストラクチャ、タイプ、要素ごとのデータにアクセスする方法など)を記述できるようになります。.NET 機能では、C++ の場合よりも処理を大幅に単純化し、強力にすることができます。
このページに記載した .NET 拡張機能を紹介する興味深い複数の例は、Maya に付属している例にも含まれています(「コンパイルの例」を参照)。Microsoft Visual Studio を使用して examples.sln ソリューション(Developer Kit をインストールした devkit\dotnet\examples フォルダ内、「ビルド環境を設定する: Windows 環境(64 ビット)」を参照)を開くと、これらのサンプルを MetaData フォルダ内で見つけることができます。いくつかの興味深い例を次に示します。
CreateMetadataFromTypeCmd.cs (CreateMetadataXMLCmd.mel を使用してテスト)
この例では、新しい StructureClass アセンブリ アトリビュートと .NET タイプ用の特殊なクラス StreamForType<T> (つまり、例の MyStructureClass)の使用方法を示します。
これにより、すべてが汎用クラス StreamForType<T> によって自動的に実行されるため、Structure クラスおよび Handle クラスが不要になります。
この例はまた、LINQ を使用して任意のコレクションでクエリを実行する方法も示します(例では、ストリームの要素を照会します)。
WriteMetadataToConsoleCmd.cs (MetaData フォルダ内のすべての MEL スクリプトでテスト)
この例では、Associations、Channel、Stream、および Structure クラスで .NET インタフェース IEnumerable がサポートされるおかげで、foreach 構文を使用した場合に列挙チャネル、ストリーム、ストラクチャ メンバーおよびストリーム ハンドルをどれだけシンプルにできるかを示します。
メタデータ API の主なクラスのリストとそれぞれの簡単な概要を、.NET に移植された C++ メソッドのリストとともに次に示します。
これは、MFnDependencyNode.metadata プロパティを取得することなどによって Maya オブジェクトからメタ データにアクセスするためのエントリ ポイントです。
基本的に、チャネルの単純なコンテナです。
C++ から移植された興味深いメソッドをいくつか示します。
Channel findChannel(string) は、名前が指定されたチャネルを検索します。void setChannel(Channel) は、チャネルを追加するか、同じ名前を持つ既存のチャネルを置き換えます。bool removeChannel(string) は、名前が指定されたチャネルを除去します。AssociationsIterator begin() は、C++/STL 形式のイテレータを返し、チャネルを反復します。これは、同じトポロジ(同じストリーム インデックス)を共有しているか、ユーザーが論理的に同じグループになるように決定したため(シミュレーション エフェクトのすべてのストリームが同じチャネルになり、エフェクトの名前であるチャネル名が付けられる場合など)にグループ化されたストリームのコレクションです。
チャネルに名前を付けることができますが、いくつかの名前は予約されています(たとえば、vertex はストリームが頂点ごとに 1 つの要素を持つことを示すために予約されています)。
C++ から移植された興味深いメソッドをいくつか示します。
Stream findDataStream(string) は、名前が指定されたストリームを検索します。Stream setDataStream(Stream) はストリームを追加します。void removeDataStream(string) は、名前が指定されたストリームを除去します。ChannelIterator begin() は、C++/STL 形式のイテレータを返し、ストリームを反復します。bool removeElement(Index elementIndex) は、チャネルで検出されたすべてのストリームから指定された要素を除去します。bool addElement(Index elementIndex) は、チャネルで検出されたすべてのストリームに指定された要素を追加します。これは、オブジェクト上のデータのコンテナです(たとえば、メッシュ上のチャネル vertex では、ストリームにある頂点と同じ数のデータ要素があります)。
C++ から移植された興味深いメソッドをいくつか示します。
Stream(Structure, string) は、指定されたストラクチャを使用して指定された名前の Stream オブジェクトを作成します。bool setElement(Index, Handle) は、指定されたインデックスにあるデータを別のデータに置き換えます。Handle element(Index) は、指定されたインデックスにあるデータのハンドルを取得します。StreamIterator begin() は、C++/STL 形式のイテレータを返し、ハンドルを反復します。このクラスは、ストリームの各要素で検出されたメタ データについて記述します。
このメソッド AddMember は、メタ データの各フィールドを定義するために使用されます。
たとえば、1 つの float と 1 つのブール値をすべての要素に格納する場合は、最初にストラクチャを作成し、AddMember(kFloat,1,”MyFloatField”) と AddMember(kBoolean,1,”MyBooleanField”) を呼び出します。
C++ から移植された興味深いメソッドをいくつか示します。
bool addMember( Member.eDataType, uint arraySize, string memberName ) は、指定されたタイプ、サイズ(一般的には 1)、および名前のメンバーをストラクチャに追加します。StructureIterator begin() は、C++/STL 形式のイテレータを返し、メンバーを反復します。このクラスを使用すると、ストリームの指定された要素のデータにアクセスすることができます。
ハンドルを使用すると、ストリーム要素のデータを取得または設定することができます。
C++ から移植された興味深いメソッドをいくつか示します。
bool setPositionByMemberIndex(uint) は、このハンドルが関連付けられているストラクチャのメンバーを示します。int[] asInt32Array は、(整数が 1 つのみの場合でも)整数データを整数の配列として取得または設定します。float[] asFloatArray は、(float が 1 つのみの場合でも) float データを float の配列として取得または設定します。.NET では、メタデータ API にいくつかの拡張機能が提供されます。ここでは、.NET の拡張機能がある API の主なクラスを示します。
このクラスは Channel オブジェクトのディクショナリとして機能するため、.NET インタフェース IDictionary<Channel> をサポートします。
このインタフェースを使用すると、.NET のディクショナリから予測される次の標準のメソッド/プロパティを使用することができます。
Channel channel;
if( myAssociations.TryGetValue( “vertex”, out channel ) { ... }このメソッドを使用すると名前が指定されたチャネルを検索することができ、同時にその有無がテストされます。
Channel channel = myAssociations[“vertex”]; このインデクサも名前が指定されたチャネルを検索しますが、この場合、チャネルは見つかる必要があります(見つからない場合は、KeyNotFoundException をスローします)。
if(myAssociations.ContainsKey( “vertex” ) { ... }これは単に、指定された名前のチャネルが存在しているかどうかをテストします。チャネルは返されません。
myAssociations.Add( “vertex”, myChannel ); これはチャネルを追加します。または同じ名前のチャネルが既に存在している場合は ArgumentException をスローします。
foreach( Channel chnl in myAssociations ) { ... }これを使用すると、複雑な C++/STL 構文の代わりに非常に単純な構文を使用してすべてのチャネルを列挙することができます。
for( AssociationsIterator iter = myAssociations.begin(); iter.notEqual( myAssociations.end() ); iter.next() ) {
Channel chnl = iter.__deref__();
...
}このクラスは Stream オブジェクトのディクショナリとして機能するため、.NET インタフェース IDictionary<Stream> をサポートします。
このインタフェースを使用すると、.NET のディクショナリから予測される次の標準のメソッド/プロパティを使用することができます。
Stream stream;
if( myChannel.TryGetValue( “myStream”, out stream ) { ... }このメソッドを使用すると名前が指定されたストリームを検索することができ、同時にその有無がテストされます。
Stream stream = myChannel[“myStream”]; このインデクサも名前が指定されたストリームを検索しますが、この場合、ストリームは見つかる必要があります(見つからない場合は、KeyNotFoundException をスローします)。
if( myChannel.ContainsKey( “myStream” ) { ... }これは単に、指定された名前のストリームが存在しているかどうかをテストします。ストリームは返されません。
myChannel.Add( “myStream”, myStream ); これはストリームを追加します。または同じ名前のストリームが既に存在している場合は ArgumentException をスローします。
foreach( Stream stream in myChannel ) { ... }これを使用すると、複雑な C++/STL 構文の代わりに非常に単純な構文を使用してすべてのストリームを列挙することができます。
for( ChannelIterator iter = myChannel.begin(); iter.notEqual( myChannel.end() ); iter.next() ) {
Stream stream = iter.__deref__();
...
}このクラスは Handle オブジェクトのディクショナリとして機能するため、.NET インタフェース IDictionary<Handle> をサポートします。
これを使用すると、.NET のディクショナリから予測される次の標準のメソッド/プロパティを使用することができます。
Handle handle;
if( myStream.TryGetValue( 18, out handle ) { ... }このメソッドを使用するとインデックスが指定されたハンドルを検索することができ、同時にその有無がテストされます。
Handle handle = myStream[18]; このインデクサもインデックスが指定されたハンドルを検索しますが、この場合、ハンドルは見つかる必要があります(見つからない場合は、KeyNotFoundException をスローします)。
if( myStream.ContainsKey(18) { ... }これは単に、指定されたインデックスのハンドルが存在しているかどうかをテストします。ハンドルは返されません。
myStream.Add( 18, myHandle ); これはハンドルを追加します。または同じインデックスのハンドルが既に存在している場合は ArgumentException をスローします。
foreach( Handle handle in myStream ) { ... }これを使用すると、複雑な C++/STL 構文の代わりに非常に単純な構文を使用してすべてのハンドルを列挙することができます。
for( StreamIterator iter = myStream.begin(); iter.notEqual( myStream.end() ); iter.next() ) {
Handle handle = iter.__deref__();
...
}この構文を有効にするために IEnumerable<Member> が追加されました。
foreach( Member member in myStructure ) { ... }これを使用すると、複雑な C++/STL 構文の代わりに非常に単純な構文を使用してすべてのメンバーを列挙することができます。
for( StructureIterator iter = myStream.begin(); iter.notEqual( myStream.end() ); iter.next() ) {
Member member = iter.__deref__();
...
}C++ API はポインタを使用する単一メソッド(例: int *asInt32())を使用するため、特定のタイプのデータにアクセスすることができます。単一の整数に対するポインタとして使用できます(例: myInteger = *asInt32())。Structure オブジェクトに対応するメンバーを作成するときに 1 より大きいサイズを指定した場合は、整数値の配列に対するポインタとして使用できます(例: asInt32()[6])。
.NET の場合、API でポインタを使用することはできなかったため、タイプごとに 2 つのプロパティが提供されています。
int[] asInt32Array は、整数の配列として整数データを取得または設定します。int asInt32 は、単一の整数を取得または設定します(対応するストラクチャ メンバーで検出されたサイズが 1 より大きい場合は ApplicationException をスローします)。float[] asFloatArray は、float の配列として float データを取得または設定します。float asFloat は、単一の float を取得または設定します(対応するストラクチャ メンバーで検出されたサイズが 1 より大きい場合は ApplicationException をスローします)。Structure クラスを使用して要素のデータを記述すると、データにアクセスする Handle クラスは次のようにかなり低レベルになり、C++ のようになります。
Structure.AddMember( kInt32, 1, “myIntMember”) は、int メンバーを追加します。Handle.setPositionByMemberIndex(uint) は、アクセスするハンドルのメンバーを指定します。Handle.asInt は、メンバーの整数データを取得または設定します。使用しているタイプの各メンバーが予想される Structure オブジェクトのメンバーとなっているデータを、クラスまたはストラクチャ内で記述している場合は、.NET によってプロセス全体が単純化されます。
プラグインで新しい StructureClass アセンブリ アトリビュートを使用し、メタデータのストラクチャを記述するタイプを指定している場合は、.NET によって Structure オブジェクトを自動的に作成することができます。
[assembly: StructureClass(typeof(MyNameSpace.MyStructureClass))]
namespace MyNameSpace
{
public class MyStructureClass
{
public int a;
public int b { get; set; }
public float[] c = new float[3];
}
}イントロスペクションのおかげで、.NET はクラス(またはストラクチャ)ですべてのパブリック フィールドおよびプロパティを検索し、Structure.registerStructure を使用して登録されている Structure オブジェクトを自動的に作成することができます。
ストラクチャの名前は、タイプのフル ネームです(MyNameSpace.MyStructureClass など)。各ストラクチャ メンバーの名前は、タイプの対応するメンバーの名前です。
.NET オブジェクト タイプの新しい StructureClass アセンブリ アトリビュート(前のセクションを参照)を使用する場合は、次に示す Handle クラスの新しいメソッドを使用することができます。
public void SetFromDotNetObject( Object dotNetObj, Structure structure ); これにより、メンバーは .NET オブジェクトから Handle オブジェクトに高レベルでコピーされます。
これは、次の疑似コードと同等です。
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); これにより、メンバーは Handle オブジェクトから .NET オブジェクトに高レベルでコピーされます。
これは、次の疑似コードと同等です。
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
ただし、次のセクションで説明する新しい汎用クラス StreamForType<T> を使用する場合、これらのメソッドは不要になります。
Stream は Structure を記述する .NET タイプを提供できない場合に使用するクラスですが、新しい StructureClass アセンブリ アトリビュートを使用してタイプを提供した場合は、クラス StreamForType<T> を使用する必要があります。
この汎用クラスの T パラメータに同じタイプを使用すると、タイプに合わせて完全にカスタマイズされた Stream のようなクラスが提供されます。
この汎用クラスは Stream オブジェクトのラッパーであり、2 つの方法で作成することができます。2 つのコンストラクタを次に示します。
StreamForType<T>(string streamName)
このコンストラクタは、タイプ T のフル ネームで呼び出された既存の Structure を検索します(StructureClass アセンブリ アトリビュートを使用した場合に検索する必要があります)。コンストラクタは、指定された名前と検出されたストラクチャを使用して新しい Stream オブジェクトを作成し、この新しい Stream オブジェクトをラップします。
StreamForType<T>(Stream baseStream)
このコンストラクタは、指定されたストリームをラップするだけでなく、このベース ストリームのストラクチャがタイプと互換性のあることを保証します(StructureClass アセンブリ アトリビュートを使用した場合にその必要があります)。
Stream クラスは Handle オブジェクトのディクショナリとみなすことができますが、StreamForType<T> 汎用クラスは T が .NET クラス(またはストラクチャ)である T オブジェクトのディクショナリとみなすことができます。これは、汎用クラスによってサポートされた IDictionary<T> を使用して実行されます。
いくつかの興味深いメソッドを次に示します。
bool TryGetValue(Index, out T value)
指定されたインデックスを持つ Handle オブジェクトを検索し、T の新しいインスタンスで handle.CopyToDotNetObject() を呼び出します。新しいインスタンスを返すか、ハンドルが見つからない場合は false を返します。
void Add(Index, T value)
指定したインデックスで新しいハンドルを作成し、指定された T の値を使用して handle.SetFromDotNetObject() を呼び出します。
T this[Index key]
これにより、次の構文が有効になります(MyStructureClass は T パラメータに使用される .NET タイプです)。
MyStructureClass myDotNetObj ;
myDotNetObj.myInt = 3;
myStream[18] = myDotNetObj;上記の例では、指定されたインデックスに .NET オブジェクトを追加するか、既にそのインデックスにオブジェクトが存在する場合は現在のオブジェクトを置き換えます。
MyStructureClass myDotNetObj = myStream[18]; 上記の例では、指定されたインデックスで検出されたオブジェクトが返されるか、オブジェクトがそのインデックスに存在しない場合は KeyNotFoundException をスローします。
次の構文はコンパイルを実行しますが、ベース ストリーム内の要素は変更されないため、予測どおりに動作しないことに注意してください。
myStream[18].myInt = 1.6; 上記の例では代わりにインデクサで返された「コピー」の myInt フィールドが変更されるため、まったく意味がありません。**
目的の結果を出力するには、次のコードを実行します。
MyStructureClass temp = myStream[18];
temp.myInt = 1.6;
myStream[18] = temp;
IEnumerator<T> GetEnumerator()
これにより、この単純な構文を使用してすべての要素が列挙されます。
foreach( MyStructureClass myDotNetObj in myStream ) { … }IEnumerable はほとんどのクラスでサポートされているため、データで非常に強力な照会を実行することができます。
たとえば、クラスに振幅というフィールドがあり、振幅が 0.5 よりも大きいメッシュの頂点のチャネルでメタデータを検索する場合、次のようにしてこれを簡単に行うことができます。
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 ) { … }頂点インデックスを照会するには、次のように実行できます。
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;
…
}このタイプの照会は、Associations、Channel、Stream、および Structure のいずれかのクラスで実行できます。これらはすべて、LINQ が必要とする唯一のインタフェースである IEnumerable を実装しているためです。