コンテンツにスキップ

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 は「コードを生成しない」ので

1
cube = UsdGeom.Cube.Define(stage,'/cube')

このような API は使用できず、一般的な USD の API を使用して

1
2
prim = stage.DefinePrim("/sample")
prim.SetTypeName("Smaple")

このようにして TypeName をセットしたりして Schema を定義する必要があるのに
注意が必要です。

定義してみる

まずは定義を作ります。
任意のフォルダ下に名前/resources/名前のフォルダを作り、その中に scehma.usda を
作ります。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
#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 を追加しています。

1
usdGenSchema schema.usda ..

コマンドプロンプトで schema.usda があるフォルダに移動して、 usdGenSchema を実行します。

これで無事必要なファイルが生成できました。

plugInfo.json

generatedSchema.usda ができたら、plugInfo.json をつくります。
plugInfo.json は、USD のプラグインを定義するためのファイルで、今回のスキーマも
どのような名前で、どのフォルダにファイルがあるのかなどを、この json で定義します。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# 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 のプラグインフォルダにリリースします。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
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 がコピーされるように指定します。

1
2
3
4
5
mkdir build
cd build
cmake ..
cmake --build .
cmake --isntall --prefix C:/USD/plugin/usd

あとは cmake を実行すれば完了です。

テストする

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

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