CodeLess Schemas について
USD AdventCalendar2022 19 日目は、CodeLessSchemas についてです。
CodeLessSchemas とは、通常のスキーマとは違い、その名の通り
コードを生成せずにランタイムスキーマに登録が必要な generatedSchema.usda と
plugInfo.json だけを使用して登録可能なスキーマのこと
です。
自分の記憶だとこんな機能あったっけ?という感じですが
たまたま Houdini の Karma の RenderSettings のアスキーファイルを眺めているときに
Python でコード書くときに、apiSchemas が指定されてるからこのあたりを使えばいいのか?
と思って Python の API で UsdKarma がないかを探す
が、UsdKarma は見つからない。
どうやってこのスキーマ定義してるん…?というのがきっかけです。
Houdini の usd_plugin はインストールフォルダの houdini/dso/usd_plugins にあり、
この中に usdKarma は存在しています。
のでプラグインではあります。
中にあるのは generatedSchema.usda と plugInfo.json のみ。
あれ?
で、調べていくと Codess Schemas の存在をしりました。
知らなかった。
存在を知ってしまったからにはちゃんと調べていきましょう。
Schema と CodelessSchema の違い
Schema とは Schema について ここに書かれている通り
ある UsdObject(UsdPrim)に対して共通の機能・特性を指定するものです。
USD は、Prim と呼ばれる Maya でいうところのノードのような「入れ物」が用意されていますが
通常だと、この入れ物が「どのようなものを入れられるか」は指定されていません。
この「何を入れるか」を定義するものが Schema です。
たとえば、3DCG で一般的に使われるデータ形式である Camera や Xform (Transform)
Mesh といった Schema が USD では用意されています。
この各 Schema は、データの構造(いわゆるクラス変数)と、
この Schema を操作するためのメソッドによって構成されています。
Cube を作成する UsdGeomCube であれば、Cube の大きさを定義する size 変数と
この size に値をセットしたり取得したりする GetSizeAttr 関数や
CreateSizeAttr 関数 といったものが用意されています。
このような Schema は New Schema Class を作る こちらの記事のように
カスタムスキーマを生成するための schema.usda に、スキーマ定義のアトリビュートを
定義し、
usdGenScehma コマンドを使用して schema.usda から cpp とヘッダファイルを生成します。
そこから、必要なファイル(Python 用のファイル等)を用意し、cmake を書いて、ビルドして
dll を生成することで Schema の登録ができます。
見てわかる通り、結構更新が手間で
変数のみで関数の実装が不要なもの、いわゆる C の構造体を定義するようなパターンだと
ここまでする必要はありません。
このように、スキーマを使用したり更新するための再コンパイルが不要で
「動的な性質」が、この CodelessSchemas の重要なコンセプトです。
手軽であることと引き換えに、CodelessSchemas は「コードを生成しない」ので
cube = UsdGeom.Cube.Define(stage,'/cube')
このような API は使用できず、一般的な USD の API を使用して
prim = stage.DefinePrim("/sample")
prim.SetTypeName("Smaple")
このようにして TypeName をセットしたりして Schema を定義する必要があるのに
注意が必要です。
定義してみる
まずは定義を作ります。
任意のフォルダ下に名前/resources/名前のフォルダを作り、その中に scehma.usda を
作ります。
#usda 1.0
(
subLayers = [
@usd/schema.usda@
]
)
over "GLOBAL" (
customData = {
string libraryName = "usdSchemaExamples"
string libraryPath = "./"
string libraryPrefix = "UsdSchemaExamples"
bool skipCodeGeneration = 1
}
) {
}
class SimplePrim "SimplePrim" (
doc = """An example of an untyped schema prim. Note that it does not
specify a typeName"""
# IsA schemas should derive from </Typed>, which is defined in the
# sublayer usd/schema.usda.
inherits = </Typed>
customData = {
# Provide a different class name for the C++ and python schema
# classes. This will be prefixed with libraryPrefix.
# In this case, the class name becomes UsdSchemaExamplesSimple.
string className = "Simple"
}
) {
int intAttr = 0 (
doc = "An integer attribute with fallback value of 0."
)
rel target (
doc = """A relationship called target that could point to another
prim or a property"""
)
}
schema.usda はこのようにします。
基本構造は New Schema Class を作る この時の中身と同じですが
GLOBAL に bool skipCodeGeneration = 1 を追加しています。
usdGenSchema schema.usda ..
コマンドプロンプトで schema.usda があるフォルダに移動して、 usdGenSchema を実行します。
これで無事必要なファイルが生成できました。
plugInfo.json
generatedSchema.usda ができたら、plugInfo.json をつくります。
plugInfo.json は、USD のプラグインを定義するためのファイルで、今回のスキーマも
どのような名前で、どのフォルダにファイルがあるのかなどを、この json で定義します。
# Portions of this file auto-generated by usdGenSchema.
# Edits will survive regeneration except for comments and
# changes to types with autoGenerated=true.
{
"Plugins": [
{
"Info": {
"Types": {
"UsdSchemaExamplesSimple": {
"alias": {
"UsdSchemaBase": "SimplePrim"
},
"autoGenerated": true,
"bases": [
"UsdTyped"
],
"schemaKind": "concreteTyped"
}
}
},
"LibraryPath": "@PLUG_INFO_LIBRARY_PATH@",
"Name": "usdSchemaExamples",
"ResourcePath": "@PLUG_INFO_RESOURCE_PATH@",
"Root": "@PLUG_INFO_ROOT@",
"Type": "resource"
}
]
}
サンプルからコピーして、UsdSchemaBase などを作成するプ ラグイン名に変更します。
CMakeLists.txt を作る
プラグインに必要なファイル「generatedSchema.usda」と「plugInfo.json」ができたら
この 2 つのファイルを USD のプラグインフォルダにリリースします。
set(PROJECT_NAME sampleSchema)
project(${PROJECT_NAME})
set(PLUG_INFO_LIBRARY_PATH "")
set(PLUG_INFO_RESOURCE_PATH "resources")
set(PLUG_INFO_ROOT "..")
configure_file(${PROJECT_SOURCE_DIR}/plugInfo.json
${PROJECT_BINARY_DIR}/plugInfo.json
@ONLY)
install(FILES
generatedSchema.usda
${PROJECT_BINARY_DIR}/plugInfo.json
DESTINATION
./${PROJECT_NAME}/resources
)
plugInfo.json の @~@となっていた部分は、Cmake 側で定義し、 configure_file を使用して置換します。
そしてリリース先は、 pluginNanme/resources 下に usda と json がコピーされるように指定します。
mkdir build
cd build
cmake ..
cmake --build .
cmake --isntall --prefix C:/USD/plugin/usd
あとは cmake を実行すれば完了です。
テストする
#usda 1.0
def SimplePrim "Sample"{}
準備ができたら、このような usda ファイルを作り usdview で開きます。
無事 CodelssSchemas プラグインがロードされ、 SimplePrim Schema が適応されて intAttr と target のプロパティが定義されました。
ほかの代用方法
最初に説明した通り、通常の Schema とは違い codelessSchemas はコンパイルを実行しない
代わりに動的に、構造体的なスキーマ定義が可能です。
この方法に似た手段として、
PXR_DEFAULT_SEARCH_PATH を通して、その下に Class 定義の usda を用意した うえで
subLayer + Inherits で、用意したクラス定義を継承する方法が可能です。
こうすると、PrimType は使用せずクラス継承によって構造体を定義が可能です。
このほうが、CodelessSchema より簡単で、PrimType を別の用途できるというメリット
があります。
CodelessSchemas の場合は、多少の手間はできるものの
Custom Attribute の定義を使用することで、ベースの構造とそれ以外のものとを
明確に区別したり
コンポジション(Inherits)を使用しなくても、attribute のフォールバックを
使用できるというメリットがあります。
メリット・デメリットがそれぞれあるので
ケースバイケースで使うのが良いのではないかと思います。