プロジェクトアイテム型の作成

概要

Choreonoidのフレームワークにおける主要な構成要素のひとつであるプロジェクトアイテムについては、プラグインで独自のアイテム型を追加することも可能です。これにより、既存のアイテム型だけでは実現できない機能も自由に実現できるようになります。

本節では独自アイテム型の作成に関する基本事項を解説します。 そして本節以降の節にて、それらの具体例をサンプルも交えながら紹介していきます。

アイテム型作成の流れ

独自のアイテム型は、まず Itemクラス を継承したクラスとして定義します。そのクラスに対して、以下に示すようなアイテムの実質的な内容を実装します。

  • 独自アイテムが扱うオブジェクトやデータの保持/実装

  • オブジェクトやデータを操作する関数の定義/実装

  • オブジェクトやデータからの通知を行うシグナルの定義/送出

  • アイテムとしての振る舞いのカスタマイズ

「アイテムとしての振る舞いのカスタマイズ」は以下の手段で実現できます。

  • Itemクラスで定義されている各種virtual関数をオーバーライドする

  • 特定機能用のインタフェースクラスを継承して実装する

カスタマイズ可能な項目としては以下があります。

  • アイテムツリー上で追加・名前変更・移動・削除等の操作がされたときの挙動

  • プロジェクトへの保存とプロジェクトからの読み出し

  • プロパティの表示・編集

  • シーンビュー上での描画やインタラクション

  • その他の基盤的なビューとの連携

最後に、作成したアイテムクラスをItemManagerに登録します。 これによりChoreonoid上でプロジェクトアイテムとして使用できるようになります。 この際メニューからの新規作成やファイルの入出力についてもカスタマイズすることができます。

また、作成した型のアイテムについて、既存のインタフェースでは必要な表示や操作、編集等を実現できないかもしれません。 その場合は独自のツールバーやビューを作成することで対応します。 ツールバーの作成 については既に解説しましたが、独自のビューもプラグインで追加することが可能です。それについては本ガイドで後ほど解説します。

以下では上記の各項目の概要を解説します。

独自アイテム型の定義

独自のアイテム型は以下のようにItemクラスを継承して定義します。

class FooItem : public cnoid::Item
{
public:
    FooItem();
    FooItem(const FooItem& org);
    ~FooItem();

    virtual Item* doDuplicate() const override;

    ...
};

クラス名は "Item" で終わるようにします。 コンストラクタやデストラクタは必要に応じて定義します。 基本的にはデフォルトコンストラクタで生成できるようになっていればOKです。

アイテムは自身の複製を作れるようにdoDuplicate()関数をオーバーライドする必要があります。これは通常

Item* FooItem::doDuplicate() const
{
    return new FooItem(*this);
}

と実装することで実現できます。アイテムの複製はChoreonoidのフレームワーク内で必要になる機能ですので、自前のコードで複製を行わないとしても実装しておく必要があります。

注釈

アイテムはシングルトンとして作成することもできます。その場合は複製をできるようにする必要はありません。

オブジェクトやデータの保持/実装

アイテムが意味のあるものとなるためには、何らかの機能やデータを有している必要があります。 アイテムクラスには当然それを実装するわけですが、実装の形態としては大きく以下の2つが考えられます。

  1. 他のオブジェクトやデータを保持する

  2. アイテムクラスに直接実装する

1の例として、ロボットや環境のモデルに対応するBodyアイテムについては、アイテムとは独立して使用することが可能な Bodyクラス がまずあって、そのオブジェクトへのポインタを保有する形態で BodyItemクラス が定義されています。ここでBodyItemクラスはBodyオブジェクトをChoreonoidのGUI上で扱えるようにするためのラッパークラスとして機能しています。

このような例からも分かるように、独自のアイテム型は必ずしもその機能の全て一から実装する必要はありません。むしろまず既存のクラスやデータがあって、それをChoreonoid上で扱いたい場合に追加で実装するのがアイテムクラスであると考えることができます。その場合はアイテムクラスにまずメンバ変数やポインタの形態で対応するオブジェクトやデータを持たせて、それを外部からアクセスできるようにすることで、アイテムの機能の大部分を実現することができます。

あるいは、独自のアイテム型をその機能やデータの部分から新規に作成する場合でも、あえてその本質的な部分をアイテムのクラスとは分けて定義することで、ChoreonoidのGUIから独立してそれらを利用できるようにすることも考えられます。BodyItemクラスとBodyクラスの例で言えば、最初から全てをBodyItemとして実装できるかもしれないところを、あえてGUIに依存しない部分をBodyクラスに分けて実装したと考えることもできます。そしてそのようなGUIに依存しない部分はBodyライブラリという独立したライブラリにまとめており、Bodyプラグインからはモジュールを分けています。

独自アイテムの実装にあたっては、このことも覚えておくとより柔軟な対応ができるかと思います。 もちろんアイテムの種類によってはアイテムクラスにまとめて実装する2の形態がよい場合もありますし、状況に応じて適切な選択をしていただければと思います。

シグナルの定義/送出

Choreonoid SDKの重要な概念として シグナルの利用 を紹介しましたが、これは独自アイテムの実装においても活用できます。例えばアイテムが保有するデータが更新されたときに、データのどの部分がどう更新されたかを知らせるシグナルを定義し、データの更新時にこのシグナルを送出するようにすれば、そのデータと連携する機能の実装を実現しやすくなります。緩い結合で連携するというシグナルの特性をうまく使うことで、実装を簡潔にでき拡張性や保守性も高めることができます。

実際にChoreonoidの既存のアイテム型ではそれぞれ独自のシグナルが定義され活用されています。独自のアイテム型を作成する場合も、必要に応じてシグナルを活用できるとよいでしょう。アイテムクラスに外部から利用できるシグナルを定義する場合は、通常 SignalProxyクラス のオブジェクトを返すメンバ関数を通してアクセスできるようにします。

virtual関数のオーバーライド

Itemクラスは上記のdoDuplicate関数以外にも、オーバーライドされることを前提としたvirtual関数をいくつか備えています。 それらの関数を実際にオーバーライドすることで、アイテムとしての基本的な振る舞いをカスタマイズすることができます。 以下ではカテゴリごとに該当するvirtual関数を紹介します。

名前に関するもの

  • virtual bool setName(const std::string& name)

    ChoreonoidのGUI上でアイテムの名前が設定されるときに実行されます。アイテムクラスが保持しているオブジェクトに名前を設定する必要がある場合は、この関数で名前の更新を行います。

アイテムツリーに関するもの

アイテムツリーの変化に伴って実行されるvirtual関数がいくつかあります。ここではその中の主要なものについて紹介します。

  • virtual void onConnectedToRoot()

    ルートアイテムと直接的または間接的に接続したときに実行されます。アイテムはルートアイテムと接続されるとGUI上で操作できるようになるので、ここに初期化処理を実装することで、必要なタイミングで確実に初期化を行うことができます。

  • virtual void onTreePathChanged()

    アイテムツリーにおけるルートからアイテムまでのパスが変化したときに実行されます。ItemクラスのシグナルsigTreePathChangedと同じ条件になります。(本関数が実行された後にシグナルが送出されます。)ツリーにおける他のアイテムとの相対位置関係から自身の振る舞いを決定する場合は、この関数にその初期化処理を実装することで、関係の更新を確実に行うことができます。

  • virtual void onTreePositionChanged()

    アイテムツリーにおけるアイテムの位置が変化したときに実行されます。onTreePathChangedが実行される際にはこちらも実行されますが、さらにパスは同じでも兄弟アイテムにおける順番が変化した場合にも実行されます。ItemクラスのシグナルsigTreePositionChangedと同じ条件になります。(本関数が実行された後にシグナルが送出されます。)

  • virtual void onDisconnectedFromRoot()

    ルートアイテムとの接続が解除されたときに実行されます。接続時とは逆に、ここにアイテムの終了処理として、使用しているリソースの解放などを実装すると、それを適切なタイミングで実行できます。

プロジェクト保存・復帰に関するもの

アイテムをプロジェクト保存に対応させるための関数として以下が定義されています。

  • virtual bool store(Archive& archive)

    プロジェクト保存の際に各アイテムごとにこの関数が実行されます。ここにアイテムの状態やデータを保存する処理を実装することで、アイテムのプロジェクト保存を実現できます。引数archiveは構造化されたデータを格納するオブジェクトで、ここにアイテムの情報を書き込みます。

  • virtual bool restore(const Archive& archive)

    プロジェクト読み込みの際に各アイテムごとにこの関数が実行されます。ここにアイテムの状態やデータを復元させる処理を実装することで、プロジェクト読み込みにアイテムを対応させることができます。引数archiveには保存したときの情報が入っているので、そこから情報を取り出してアイテムの状態やデータを復元します。

これらの関数の実装方法の詳細は別途解説します。

プロパティに関するもの

アイテムには「プロパティ」という概念があり、Choreonodidの基本機能であるアイテムプロパティビュー上でプロパティの表示や編集ができます。以下はアイテムをこの機能に対応させるための巻数です。

  • virtual void doPutProperties(PutPropertyFunction& putProperty)

    アイテムのプロパティを出力します。引数putPropertyはプロパティ出力の機能を備えたオブジェクトで、これを介してプロパティを出力します。またそこで編集された値はputPropertyに渡したコールバック関数で取得することができ、プロパティの更新処理も実装できます。

この関数の実装方法の詳細は別途解説します。

基盤ビュー連携用インタフェースクラスの導入

アイテムはChoreonoidが備えているいくつかの基盤的なビューと連携することが可能です。 これを行うためには、連携を行うためのインタフェースクラスを継承し、そのvirtual関数をオーバーライドします。 このようなインタフェースクラスとして以下を利用可能です。

  • RenderableItem

    アイテムをシーンビューと連携させるためのインタフェースです。アイテムが描画可能なオブジェクトである場合、これを導入することでアイテムをシーンビュー上で表示・編集することが可能となります。

  • LocatableItem

    アイテムを配置(Location)ビューと連携させるためのインタフェースです。アイテムが三次元仮想空間に配置可能なオブジェクトである場合、これを導入することでオブジェクトの位置や姿勢を配置ビュー上で表示・編集することが可能となります。

  • ImagableItem

    アイテムを画像(Image)ビューと連携させるためのインタフェースです。アイテムが二次元画像データを含む場合、これを導入することでアイテムを画像ビュー上に表示することが可能となります。

ここではRenderableItemの定義や利用方法について簡単に紹介します。 このインタフェースクラスの主要部分を抜粋すると以下のように表されます。

class RenderableItem
{
public:
    virtual SgNode* getScene() = 0;
};

ここでSgNodeというのは三次元シーングラフのノードに対応するクラスで、このオブジェクトはシーンビュー上に描画可能です。

これを導入するアイテムクラスは以下のように定義します。

class FooItem : public cnoid::Item, public RenderableItem
{
public:
    ...

    virtual SgNode* getScene() override;

    ...
};

RenderableItemも継承し、getScene関数をオーバーライドするわけです。そしてこのgetScene関数でアイテムに対応するSgNodeオブジェクトを返すようにすれば、シーンビュー上でそれが表示されることになります。(実際にはアイテムツリービュー上でチェックを入れているときに描画されます。)

他のインタフェースについても基本的な導入方法は同じです。

RenderableItemとLocatableItemの導入方法の詳細は別途解説します。

アイテムクラスの登録

新たに定義したアイテムクラスはChoreonoidのフレームワークへの登録が必要です。 これを行って初めてChoreonoidのGUI上で使えるようになります。

登録はアイテム型に関わる各種管理を行う ItemManagerクラス を用いて行います。 登録に使用するItemManagerのインスタンスは、 Pluginクラス の親クラスである ExtensionManagerクラス で定義されている以下の関数で取得できます。

  • ItemManager& itemManager()

プラグインのinitialize関数からこの関数を呼び出してItemManagerのインスタンス(参照)を得ることが可能です。 インスタンスを取得したら、ItemManagerの以下のメンバ関数でアイテムクラスを登録できます。

template <class ItemType, class SuperItemType = Item>
ItemManager& registerClass(const std::string& className);

これはテンプレート関数となっていて、テンプレート引数に登録するアイテムのクラスを指定します。そして関数の引数にはクラス名を指定します。これを行うことで、対象となるアイテムクラスをChoreonoid上で使用するための各種初期化が行われます。

例えばFootItemクラスを登録する場合は以下のようにします。

itemManager().registerClass<FooItem>("FooItem");

登録するアイテムクラスが別のアイテムクラスを継承している場合は、registerClassの第二テンプレート引数にその型を指定します。例えばBarItemというアイテムクラスが

class BarItem : public FooItem
{
    ...
};

と定義されている場合は、

itemManager().registerClass<BarItem, FooItem>("BarItem");

として登録します。この場合はFooItemも予め登録されている必要があります。なお、FooItemが抽象クラスの場合は登録に以下の関数を使用します。

template <class ItemType, class SuperItemType = Item>
ItemManager& registerAbstractClass();

こちらの場合はアイテムのクラス名を指定する必要はありません。

抽象クラスであるアイテム型は直接生成して使用することはできませんが、内部処理において対象とするアイテムの型を指定する場合に利用できます。その際アイテムクラスが登録されていないと機能しない場合もあるので、抽象クラスであってもこちらの関数で予め登録するようにします。

シングルトンアイテムの登録

あるアイテム型がインスタンスをひとつだけ許容するものである場合は、「シングルトンアイテム」として登録しておきます。 これにはItemManagerの以下の関数を使用します。

template <class ItemType, class SuperItemType = Item>
ItemManager& registerClass(const std::string& className, ItemType* singletonInstance);

この場合は登録時に予め対象アイテム型のインスタンスを生成しておき、それをこの関数の第2引数に指定します。 シングルトンアイテムについては、ユーザの操作で生成できるのはひとつだけに限定され、常にここで指定されたインスタンスが使われることになります。

新規生成パネルの登録

新規生成パネルは、メインメニューの「ファイル」−「新規作成」でアイテムを生成できるようにするためのものです。 これはItemManagerの以下の関数で設定します。

template <class ItemType>
ItemManager& addCreationPanel(ItemCreationPanelBase* panel = nullptr);

この関数を用いて、例えば

itemManager().addCreationPanel<FooItem>();

とすると、FooItemが新規作成できるようになります。この場合「ファイル」−「新規作成」−「Foo」によってFooアイテムを生成するパネルが表示されるようになります。このパネルはデフォルトの機能をもつもので、アイテムの名前だけ設定できるというものです。使用されるパネルは引数panelによって指定されますが、これをデフォルト値のnullptrとすると、デフォルトのパネルが表示されます。

panel引数に独自のパネルオブジェクトを指定することで、名前以外の設定項目をもつパネルを実現することもできます。 その方法は プロジェクトアイテム生成/入出力のカスタマイズ - ItemCreationPanelによる生成ダイアログのカスタマイズ にて解説します。