■PMDエディタのプラグイン機能(PEPlugin β版)について (by 極北P)


○プラグインって何？

プラグイン機能を使うことにより、外部からPMDエディタに追加機能を実装することが可能になります。


○プラグインでできること

・PMDデータの取得／任意変更／更新
・エディタ本体の一部操作
・PMDView等各Viewの一部操作

※.NETの機能は基本的に全て使用可能なので、任意の操作フォームを起動し編集補助を行うなども可能となります。
※現在開発途中となっておりますので一部機能に制限があったり、予告なく仕様の変更がある場合があります。ご了承ください。


○作成に必要なもの

.NETフレームワーク4.0以上のクラスライブラリを作成できる環境
etc) Microsoft VisualStudio.NET 2010 / Microsoft VisualC# 2010 Express Edition 他

※CSScriptプラグインを使用することで、上記の開発環境がなくてもプラグイン機能を利用／作成することは可能です。



○作り方(C#2010の場合)

1. クラスライブラリとしてプロジェクトを作成
2. PMDエディタの"PEPlugin.dll"を参照設定へ追加
3. 作成するライブラリクラスに"PEPlugin.IPEPlugin"を実装、
　または"PEPlugin.PEPluginClass"から派生し"Run(IPERunArgs args)"メソッド等をオーバーライド

4. 必要な処理を記述しビルド
5．出来上がった"*.dll"ファイルをPMDエディタの"plugin"フォルダ内へコピー(配布時はDLL単体で配布してください)

※初期状態ではビルド時に"PEPlugin.dll"がDLLと同じ場所に生成(コピー)されるかもしれませんが、こちらは必要ありません。
※このプラグインDLLはPMDエディタのプラグイン作成にのみ使用してください。


○プラグインの起動方法

1. "_plugin"フォルダ内にプラグインDLLを保存後、PMDエディタを再起動
2. [編集]-[プラグイン] メニュー内にプラグインが登録されているので、任意に起動

※PMDエディタ起動時に同時に実行するプラグインも作成可能です(後述)
※メニューへの登録も任意に変更可能です



○プラグインの起動方法 - 外部DLLの直接実行

[編集]-[プラグインDLLの直接実行] から操作フォームが表示されるので、対象のDLLを選んで実行。

尚、一度実行したプラグインDLLはPMDエディタにロードされたままになるので(.NETの仕様)、
DLLの更新を行う場合は本体の再起動が必要な模様。



○外部ブリッジ機能について

PEExBridge.dll 内に含まれる機能で、外部の開発環境などから直接プラグインアセンブリを実行可能な機能。

ただし上記外部実行での注意点通り、
一度実行したアセンブリDLLをアンロードするのが難しいようなので(実現するには仕様の大幅変更が必要)
現状試作的な機能に留まります。

使用するにはまずPMDエディタを実行可能状態で起動する必要があります。
起動パラメータに"-ex"を付けて起動すると外部からの実行可能状態になります。

使い方としては、クライアント側で PEExBridge.dll を参照し、

1. PEExBridgeClient のインスタンス生成
2. Connect() で接続 - 接続できない場合は例外終了
※複数のPMDエディタを起動している場合は最初に(実行可能状態で)起動した本体が対象。
尚、一旦全部終了しない限り起動中の他の本体が有効になることはありません。

3. RunPlugin() から生成したプラグインDLLとプラグイン名を渡して実行

という流れで外部から任意にプラグインが実行可能です。

※PMDエディタが表情編集などモーダル実行中の場合はうまく動かないと思われます。

DLLアンロードの問題があるので、一度実行すると同一のDLLファイルはPMDエディタからロックされ更新ができなくなります。
開発用としての使用は難しいのでご注意ください。





○プラグイン作成TIPS

□プラグインの実行は？
void Run(IPERunArgs args) メソッド(以下、関数)に実行処理を記述してください。
ホスト(PMDエディタ)側へのアクセスは Run関数に渡される IPERunArgs インターフェイスを通して行うことができます。


□PMDデータの編集

プラグイン内では PMDエディタ側のPMDモデルデータを間接的に編集することが可能です。手順としては、
IPERunArgs.Host.Connector.Pmd 以下の各処理を用います。

1. 現在のPMDデータの複製取得 : IPEPmd IPEPmdConnector.GetCurrentState()
2. IPEPmd として PMDモデルデータが返されるのでそれを通して編集(PMDエディタ側は変更されない)
3. 編集されたIPEPmdデータでPMDエディタ側を更新 : void IPEPmdConnector.Update(****)  // いくつか種類があります


例) モデル名を変更する場合

// PEPluginClass派生のオーバーライド
public override void Run(IPERunArgs args)
{
    try{
        // 現在のPMDデータを複製取得
        PEPlugin.Pmd.IPEPmd pe = args.Host.Connector.Pmd.GetCurrentState();

        // モデル名変更
        pe.Header.ModelName = "変更しました";

        // 編集したモデル情報でPMDエディタ側を更新
        args.Host.Connector.Pmd.Update(pe);
        // ヘッダ部分のみ更新する場合(更新対象が限定されている方が負荷が軽くなります)
        //args.Host.Connector.Pmd.Update(pe, PEPlugin.Pmd.UpdateObject.Header, -1);

        // エディタ側の表示を更新する場合(一部を除いて表示更新の必要があります)
        args.Host.Connector.Form.UpdateList(PEPlugin.Pmd.UpdateObject.Header);
  }
  catch {
        // 例外キャッチ:適宜設定してください
  }
}



□ビルダについて

このプラグイン内では、取り扱うデータのほぼすべてがインターフェイス形式となっているので、
それらを直接インスタンス化して取り扱うことはできません。

プラグインPMDデータ(IPEPmd) などの編集で必要になる各インスタンスは、
ホストに渡されるビルダを用いて作成する必要があります。

IPERunArgs.Host.Builder 以下に各生成用関数があります。


例) IPEVector3 の作成 (Vector3データ)

×:生成不可能
IPEVector3 v = new IPEVector3();    // インターフェイスなのでnewできません。

○:生成可能
IPEVector3 v = args.Host.Builder.CreateVector3();   // このようにビルダを通して生成してください


尚、本プラグイン内で用意されているベクトル系データは一時保存用の機能しか実装しておりませんので、
実際の運用では各ライブラリで用意されているベクトル系データへ変換してご使用ください。

※SlimDXの値型との相互運用が可能なクラスオブジェクトが実装されています(詳しくは"CSScript.txt"参照)

※ショート名のビルダが IPERunArgs.Host.Builder.SC 以下に実装されています。
※PEStaticBuilder というホストを介さないビルダを使うことも可能です(参照先はホスト側と同じ)



□プラグインオプションの変更

IPEPluginOption 及び PEPluginOption を用いて設定します。以下のオプション項目が変更可能です。

・PMDエディタ起動時にプラグイン実行
・メニューへの登録の可否
・メニューへ登録されるテキスト(空の場合はプラグイン名が登録されます)


●IPEPluginOption  // オプション内容
bool Bootup { get; }  // 起動時実行
bool RegisterMenu { get; }  // プラグインメニューへの登録
string RegisterMenuText { get; }  // プラグインメニューへの登録テキスト(空の場合はプラグイン名を使用)

※名称変更につき過去作成されたプラグインは動作不可になる場合があります(要再ビルド)

プラグインクラスの IPEPlugin.Option から任意のオプション設定の値を返すように実装してください。
PEPluginOption 派生の場合は以下のようにコンストラクタにおいて m_option を再設定することでも対応可能です。


例) m_optionを再設定してオプション変更

// PEPluginClass派生クラス
public class NewPlugin : PEPluginClass
    // コンストラクタ
    public NewPlugin() : base()
    {
        // Bootup:false | RegistMenu:true | RegistMenuText:"新規プラグイン"
        // → メニューへ"新規プラグイン"で登録しメニューから実行する。起動時には実行しない。
        m_option = new PEPluginOption(false, true, "新規プラグイン");
    }
}



□独自クラスへのプラグインインターフェイスの実装(継承)

各プラグイン側で使用するクラスにPMDエディタのプラグインインターフェイスを実装した場合、
プラグインのverUP(インターフェイスへの任意の追加など)により
以降のバージョンでは使用することのできない公開プラグインとなってしまうことがあります。

PEPlugin.IPEPlugin 以外は極力実装(継承)を行わないことを推奨します。





□PMDデータ構造

おおまかにPMDエディタ本体の各タブ情報に対応したデータ構造になっています。

各データ間の対応はリストでのIndexを用いているので、
リストの順序変更や追加／削除などでは対応する全ての箇所を変更する必要があります
プラグイン処理内では自動的に対応しませんのでご注意ください(エディタ本体の操作では内部で整合性等を自動的に調整しております)


●IPEPmd プラグイン用PMDインターフェイス

・ヘッダ(モデル名／コメント／英名対応)情報
IPEHeader Header { get; }

・頂点リスト
IList<IPEVertex> Vertex { get; }

・面頂点リスト
IList<int> Face { get; }

・材質リスト
IList<IPEMaterial> Material { get; }

・ボーンリスト
IList<IPEBone> Bone { get; }

・IKリスト
IList<IPEIK> IK { get; }

・表情リスト
IList<IPEExpression> Expression { get; }

・表情枠リスト
IList<int> FrameExpression { get; }

・表示枠リスト
IList<IPEFrameBone> FrameBone { get; }

・Toon名一覧[10]
string[] ToonName { get; }

・剛体リスト
IList<IPEBody> Body { get; }

・Jointリスト
IList<IPEJoint> Joint { get; }


※各インターフェイス内の個別データについての説明は、
プラグインDLLをオブジェクトブラウザなどで確認するなどしてください(基本的にはエディタ本体と同じ構造になっています)

※0.0.0.2より表情の管理Indexについて、PMDエディタ側をプラグインに合わせて変更。差異はなくなりました



□try-catch のススメ

プラグイン処理をPMDエディタ側から起動した場合、プラグイン内部で発生した例外を捕捉することができないようです(要追加調査)

場合によっては、内部での例外発生によりPMDエディタ本体も強制終了してしまう場合もあるかもしれませんので、
実装したRun()関数内においては、必ず例外処理によって例外を捕捉してください(スレッド間処理についても同様)




□表示が更新されない！！

PMDデータの更新を行った後エディタ本体を確認しても、対応した箇所が変更されていない場合があります。
各Viewなどの操作でも同様にモデルの表示に変更がない場合があります。

これらは内部データの変更がリストやViewに反映されていない状況になります(なんらかの操作をすると更新される)

※以下のような関数を使うことで適宜更新されます。
IPEPMDViewConnector.UpdateModel()
IPEFormConnector.UpdateList()
IPEFormConnector.Selected****Index()
IPEPMDViewConnector.UpdateView()
他更新処理系



□Formを利用したプラグイン作成

任意のFormクラスを利用したプラグインを作成することも可能です。

一例として、クラスライブラリに任意のフォームを追加した場合は、
以下のような手順でプラグインから使うことが可能となります。

※実際はこの他に、追加したフォームにホスト側とやり取りできるような仕組みが必要になります。


例)
1. クラスライブラリを起動時実行にオプション設定(メニューにも登録)
2. クラスライブラリにForm追加
3. Run関数に起動時とメニュー呼び出しでの分岐した処理を記述

// 起動時
if (args.IsBootup) {
    // フォームの初期化
    m_form = new *****Form();
    // フォームへホストオブジェクトの通知など
    ....
}
// メニュー呼び出し時
else{
    // フォームの表示状態の変更
    m_form.Visible = !m_form.Visible;    // 表示状態の切り替え
}

4. フォームが閉じるボタンで破棄されないように変更(Form.FormClosingイベント追加)
if (e.CloseReason == CloseReason.UserClosing) {
    // クローズキャンセル
    e.Cancel = true;
    // フォームを非表示設定
    this.Visible = false;
}

5. Dispose()をオーバーライドし、フォームの後始末(Form.Close())


これらの機能を利用することで、PMDエディタ本体に
自作のエディタ機能を追加実装することも可能になります。





□PMDモデルの簡易データフロー

上流を変更した場合、下流の対象要素も変更する必要があります。
例)リスト途中のボーンを削除→リスト順番が変更になるので、下流のボーンIndexを全て再調整(削除番号以降全て-1)

----
ボーン
↓
→ 頂点(スキニング) 
↓
→ IK
↓
→ 表示枠
↓
→ 剛体 → ジョイント

----
材質 → 面(面頂点数)

----
頂点
↓
→ 面(面頂点Index)
↓
→ 表情

----
表情 → 表情枠





□拡張PMD(IPEXPmd)について - eXtend PMD

0.0.0.3以降、オブジェクト参照連結型の拡張PMDをプラグインに実装しました。
通常PMD(IPEPmd)と異なる点は以下のようになります。


----

・オブジェクト間の参照方法
通常PMD(IPEPmd)  : リストのIndex値
拡張PMD(IPEXPmd) : 各オブジェクトを直接参照

・面(面頂点Index)の扱い
通常PMD(IPEPmd)  : 頂点Indexの一括リスト構造、材質側の設定により区切り数が決定される
拡張PMD(IPEXPmd) : 各材質内に格納、3点1組の純ポリゴンとして管理(対象頂点はオブジェクト参照)

・リストへの要素追加／挿入時
通常PMD(IPEPmd)  : 関連する全てのIndexを要調整
拡張PMD(IPEXPmd) : 追加した要素の参照値のみ設定で十分

・リスト要素の順序変更
通常PMD(IPEPmd)  : 関連する全てのIndexを要調整
拡張PMD(IPEXPmd) : 追加変更の必要なし。但しボーンのように上位下位によって動作が異なる場合は注意が必要

・リスト要素の削除時
通常PMD(IPEPmd)  : 関連する全てのIndexを要調整
拡張PMD(IPEXPmd) : リスト上での参照は更新されない(null再設定推奨)。PMD変換時の正規化処理によって自動初期化

・リスト要素のオブジェクト入れ替え時
通常PMD(IPEPmd)  : Index調整の必要なし
拡張PMD(IPEXPmd) : 参照側を全て要変更。サポート処理は現在のところ未実装

・更新処理
通常PMD(IPEPmd)  : 個別に更新が可能
拡張PMD(IPEXPmd) : 全体を一度に更新する必要がある(個別更新不可能)

・PMDへの変換負荷
通常PMD(IPEPmd)  : 個別変換の場合は軽量
拡張PMD(IPEXPmd) : 必ず一括変換を行う必要があるので比較的重くなる

----



拡張PMDでの要素編集ではIndex調整の必要がないので、リスト途中への挿入や、
任意の列構造の追加／削除などが比較的容易に行うことが可能です。

反面、直接的にオブジェクトを参照するので、欠落や交換などによって不意に参照値が外れてしまう場合もあります。
未参照の場合はPMD変換時に自動的に削除される項目もあるのでご注意ください。

※正規化処理について
正規化手順
1.参照先頂点が存在しない場合 -> 面:削除 表情:頂点オフセット削除
2.参照先ボーンが存在しない場合 -> 頂点:0番固定 ボーン親:-1 ボーン子:0 ボーンIK親:0 IK_IK:IK削除 IK_Target:IK削除 IK_Link:Link削除 ボーン枠:削除 剛体:空
3.参照先表情が存在しない場合 -> 表情枠:削除
4.参照先剛体が存在しない場合 -> Joint:0番剛体






















□更新履歴

0.0.3.0 VMD／VME／外部実行機能他追加
0.0.1.0 色々拡張
0.0.0.4 β移行
0.0.0.3 拡張PMD追加。不具合修正他
0.0.0.2 表情／表示枠の取り扱い変更(PMDエディタ側の変更)
0.0.0.1 初期版公開(α版)







-------------------------------------------------------------------------

○戯言Q&A

Q. なんか色々足りないんですけど？
A. 現在鋭意作成中です(要望に応じて追加されていくと思われます)

Q. 説明に書いてあることと実行時に異なる挙動をするんだけど？
A. 不具合の可能性があります。報告して貰えると修正できると思います。

Q. プログラミングなんて無理！！
A. 頑張って勉強しましょう。またはできる人を捕まえましょう。

Q. 使ってたら落ちました。どうして？
A. プラグイン処理についてはPMDエディタ側からあまり制限しないような設計になっております。自己管理が基本です。

Q. プラグインのデバッグめんどくね？
A. 面倒ですね。(ExpressEdition(C#)ではアタッチ機能がないのでDLLのデバッグは不可能？)

Q. プラグインで使われてる名称／用語が(ry
A. 英語？ナニソレ・・・

Q. 自作のツールで使ってもいい？
A. 本プラグインはPMDエディタ用のプラグイン作成のためだけに使用してください(といっても普通に外部からは使えませんが)

Q. 自作のツールとやりとりするのは・・・？
A. プラグインを介して内部のPMDデータなどをやりとりするなどの利用法は特に制限しておりません。
　ただし自作ツールの方に本プラグインをそのまま流用するのはご法度です。

Q. 説明少なくね？
A. オブジェクトブラウザやPMDエディタのドキュメントを活用しませう・・・いや決して書くのが面倒というわけでは(ry

Q. 各イベント処理への対応は？
A. 検討中です。

Q. いっそのことFormオブジェクトなどへの直接アクセスもOKにしてみては？
A. 色々物騒なので勘弁してください。

Q. 表情エディタへアクセスできないよ？
A. 表情エディタはモーダル実行なので、現在の仕様ではプラグインを同時に実行することができません。

Q. TransformViewでオレもモデルを変形させたいんだけど？
A. 検討中・・・っていうかMMD使いましょうや。

Q. ダイアログを表示したら固まったよ？
A. PMDエディタが最前面表示になっている場合、モーダルダイアログの表示でそういう現象が発生するようです。
   対処法としては表示前にPMDエディタ側の"FormConnector.TopMost"を参照し、falseにしておく必要があります。

Q. ところで連絡先は？ 
A. にゃっぽんとかに生息してます。IRC(#MikuMikuDance)経由でも、もしかしたら連絡を取ることは可能かもしれません(本人はIRCにはおりません)




