メインコンテンツまでスキップ

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 のフォールバックを
使用できるというメリットがあります。

メリット・デメリットがそれぞれあるので
ケースバイケースで使うのが良いのではないかと思います。