« 選択された複数のオブジェクトを処理 | トップページ | 前々回のC4Dスクリプトのプラグイン化 »

前回のスクリプトのやり直し

前回のコードをあまり突っ込まないまま活動限界時間が来て畳み込んで終了させてしまった。残念なので前回をやり直し。何をやり直そう?複数選択?マトリクスの説明?繰り返しwhile()ループ?メニューの状態?あぁそういえば、COFFEEやり始めた頃、マトリクスを学習して、ある程度使えるようになって、「自分は何でも出来るちょ~天才」なんて勘違いをしていた時期があったなぁ。周りから見ればほんのちょっと出来るだけの状態だったのに。素人はこれだからタチが悪い。ま、いまだに自分はSDK首っ引きの素人なんだけど…と言うことで更に実験。

選択した全てのオブジェクトをグローバル座標の原点に。
「メニューの状態」から

var obj = doc->GetFirstObject();
if((obj->GetBit(BIT_AOBJ)) || (obj->SearchNext(BIT_AOBJ)))ENABLE = TRUE;
else ENABLE = FALSE;

と打った。
最初の打ち始めは、

var obj = doc->GetFirstObject();
if(obj->GetBit(BIT_AOBJ))ENABLE = TRUE;
else if(obj->SearchNext(BIT_AOBJ))ENABLE =......

と打ってみたけど、なんだかパッと見よく分からない状態に。
if(aaa)AAA else if(bbb)BBB else if(ccc)
aaaがTRUEでAAA、aaaがFALSEでbbbがTRUEでBBB、でbbbがFALSEでcccが…
最近頭の回転が鈍ってきたせいでひらめかないんですよねぇ。
じゃあ分かりやすく、「リスト最初のオブジェクトがアクティブオブジェクトか(or)、最初のオブジェクトのリスト下位にアクティブオブジェクトがあるか?」これでどっちかがTRUEだとENABLEをTRUEだ。(or)だから比較演算子が(||)。
最初に

var obj = doc->GetFirstObject();

って、宣言してリスト最初のオブジェクトで初期化したけど、一気にこんな具合でも良いわけだ。

if((doc->GetFirstObject()->GetBit(BIT_AOBJ)) || (doc->GetFirstObject()->SearchNext(BIT_AOBJ)))ENABLE = TRUE;
else ENABLE = FALSE;

ここで疑問。「メニューの状態」が実行(C4Dのイベント発生)されるたび変数が宣言されメモリを確保されるのは、負荷は掛からないのだろうか?変数1個だけだから気にすること無いのか?
比較ごとにGetFirstObject()でオブジェクト取得するほうが負荷が掛かるような?今の段階では自分には分からない。最良が分からないので、もやもや感ですね。ひとまずこれは放置。

GetBit(BIT_AOBJ)関数の実験。
適当にシーンにいくつかのオブジェクトを追加。オブジェクトの名前をダブらないように、同じ名前があれば変更。

Objectmanager_02

ユーザスクリプトを新規で作成。

/*
GetBit(BIT_AOBJ)テスト

08.05.26
*/

var obj = doc->GetFirstObject();//リスト最初のオブジェクトの取得

println("--- リスト ---"); //コンソールに表示
while(obj){               //objがNULLで無ければループを実行
  println(obj->GetName());//コンソールにオブジェクトの名前を表示
  obj = obj->GetNext();   //リストの次オブジェクトを取得(重要)
}

これでオブジェクトマネージャのルートの子オブジェクトの一覧の名前がコンソールに表示されます。最初のオブジェクトから、最後のオブジェクトまで表示されているのを確認。
ループ内、最後の文

obj = obj->GetNext();//リストの次オブジェクトを取得(重要)

(重要)とコメントしましたが、よくこの文を勢いでタイプして、その勢いで実行したが為C4Dを強制終了するはめに。実行する前に確認すれば良いだけなんですけどね。で、こんな具合に…

obj->GetNext();

objの次のオブジェクトをobjに格納しないで同じオブジェクトに対して次のオブジェクトを求める。最初のobjが存在して無くNILだったら良いのですが、存在していればループに突入。いつまでも最後が来ない「無限のループ」になるわけです。COFFEEのみの終了は出来ず、編集中のシーンファイルを含め努力も失うわけです。while()ループは注意だね。繰り返す数が決まっているときはfor()ループが安全。え、安全か?増減0にしたら?あ、ループの実験じゃなかった。GetBit(BIT_AOBJ)だった。
先ほどの実験スクリプトのループ内のprintln()関数を

println(obj->GetBit(BIT_AOBJ) , "  " , obj->GetName());

これで実行すると名前の前に1か0が表示されます。適当にオブジェクトを選択して実行してみてください。
選択されたオブジェクトの名前の前に1が表示されます。でSearchNext(BIT_AOBJ)を使うと現在変数が指しているオブジェクトのシーン内での次のGetBit(BIT_AOBJ) == 1を探してくれるわけです。これで取得したオブジェクトを次から次えとSearchNext(BIT_AOBJ)を実行していけばシーン内全てのアクティブオブジェクトを検索できるわけです。
このSeachNext()関数の存在に気づかなかった頃、自分で関数をつくりシーン内のオブジェクトを検索しポリゴンオブジェクトのポイントを処理するするものを作ったけど実行スピードの遅いCOFFEEなので実行させてから返ってくるまで時間がかかりモッタリした感じになっていたなぁ。まあオブジェクトの数が数百あったけど。
あ、先ほどのGetBit(BIT_AOBJ)実験スクリプトはルートの子オブジェクトのみだから、孫以下オブジェクトは対象外です。

で次は、マトリクス…。難しいから別の機会に。while()ループ?。while()は先ほど軽く触れたので良いですか?あ、そうそう、よくwhile(式)ループは「式がTRUEの間だけループする」って説明されるけど。「FALSE以外の間」ではないだろうか?軽い疑問なんだけど。coffeeのwhile(式)の式は「0かそれ以外」でループの条件になってるよね。だったら「FALSEかそれ以外」だよね。FALSEとTRUEの定義が「0がFALSEでそれ以外がTRUE」なら式は「TRUEかそれ以外」でいいのか?ん?coffeeだけ?C++はどうだっけ?まぁ良いか…。

while(1);//で無限ループ。

そういえばfor()の無限ループは、こんなです。

for(;;);//で無限ループ。

こんなコードを実行しないように…

あ、そうそう。今回は前回のやり直し。そう、やり直し。取り消し/やり直し。
前回のスクリプトは、実行後「取り消し」すると少し驚きの状態に。取り消しはC4Dで管理してくれれば良いのに。と、思う。COFFEEで変更したら、自分でUndoの管理をしないといけない。SDK9.6には分かるような分らないような…
BaseDocumentクラスのStartUndo()/AddUndo()/EndUndo()メンバ関数。
StartUndo()AddUndo()→...→EndUndo()と、C4DにUndoの記録の開始を伝えて必要に応じてUndoをどんどん追加してUndoの記録の終了を伝えてUndoの記録を完成させる。流れは簡単だ。ただAddUndo()のレベルだ。オブジェクト/マテリアル/タグ/キー/シーケンス/トラックと6種類。オブジェクトも更に、8種類

UNDO_OBJECT_REC オブジェクトと全ての子オブジェクトの完全なデータを保存する。
UNDO_OBJECT オブジェクトの完全なデータを保存する。
UNDO_OBJECT_BASEDATA_REC オブジェクトと全ての子オブジェクトの基本のデータだけを保存する。
UNDO_OBJECT_BASEDATA オブジェクトの基本のデータだけを保存する。
UNDO_OBJECT_NEW オブジェクトのUndosを作成。
UNDO_OBJECT_DEL オブジェクトのUndosを削除。
UNDO_OBJECT_ACTIVE オブジェクトのUndosをアクティベーション。
UNDO_OBJECT_SELECTION オブジェクトの現在の選択を保存する。

1、2個目は解る。完全なデータ。でもタグは別項目なんだね。3、4個目の「基本データ」どこまでが基本データだろう。BaseObjectレベルの事か?5個目のUndo作成って何だ?Undoのアクティベーション?まあ前回のスクリプトは座標の変更だから基本データかな?そうすると使用するのはUNDO_OBJECT_BASEDATAか?

main(doc , op)
{
  doc->StartUndo();
  doc->AddUndo(UNDO_OBJECT_BASEDATA , op);
  doc->EndUndo();

  //以下座標の変更処理
   .
   .
   .
}

基本はこんな感じ、では前回のスクリプトに組み込み。

/*
選択した全てのオブジェクトをグローバル座標の
原点(0.0 , 0.0 , 0.0)に配置

Copyright(c)2008/5/27 村人とC4D
*/

var gm;
var ob = doc->GetFirstObject();

//最初のアクティブオブジェクトを得る
if(!(ob->GetBit(BIT_AOBJ)))ob = ob->SearchNext(BIT_AOBJ);
if(!ob)return;

doc->StartUndo();//追加  アンドゥの記録開始
//アクティブオブジェクトの捜索しグローバル(0.0 , 0.0 , 0.0)へ

while(ob){
  doc->AddUndo(UNDO_OBJECT_BASEDATA , ob);//追加  移動させる前に現在の状態をアンドゥに記録
  gm = ob->GetMg();
  gm->SetV0(vector(0.0));
  ob->SetMg(gm);

  ob = ob->SearchNext(BIT_AOBJ);
}
doc->EndUndo();//追加  の記録の終了

これで取り消しが可能になる。
ではここでUndoの実験。
1回のUndoで同じオブジェクトを2回AddUndoを実行したら?

/*
Undoの実験
08.05.27
*/

doc->StartUndo();
doc->AddUndo(UNDO_OBJECT_BASEDATA , op);//現状況をUndoに保存
op->SetPosition(vector(100 , 200 , 300));//opのポジションをx:100,y:200,z:300へ移動

doc->AddUndo(UNDO_OBJECT_BASEDATA , op);//更にUndoに保存
op->SetPosition(vector(-100 , -200 , -300));//opを移動
doc->EndUndo();

オブジェクトをシーンに追加,、ポジション(0.0 , 0.0 , 0.0)に配置されてると思う。このオブジェクトを選択して、このスクリプトを実行するとオブジェクトはポジション(-100 , -200 , -300)へ移動。
では「取り消し」を実行。結果はポジション(0.0 , 0.0 , 0.0)へ戻りました。2回目のAddUndo()は追加はされなかったようです。
では2回目のAddUndo()の行の前に

doc->AddUndo(UNDO_OBJECT_DEL , op);//Undoの削除

を追加しオブジェクトをポジション(0.0 , 0.0 , 0.0)へ移動しスクリプトを実行。
結果は先ほどと同じポジション(-100 , -200 , -300)へ移動。では「取り消し」を実行。
「取り消し」の結果は、ポジション(100 , 200 , 300)へ移動。という事は…

追加済みUndoは上書きができない。
追加したUndoは削除が可能。

更に実験。
今のスクリプトは基本データのUndoが記録されるわけですね。
それでは、プリミティブオブジェクトの立方体をスクリプトでサイズを変更してUndoテスト。
予想としてはポジションのみ取り消し、サイズは取り消されない。
では、プリミティブオブジェクトの立方体をシーンに追加。デフォルトでサイズはXYZそれぞれ200.0になっているはず。スクリプトを入力。

/*
UNDO_OBJECT_BASEDATAテスト
08.05.27
*/

if(!instanceof(op , CubeObject))return;//立方体以外だと中断

doc->StartUndo();
doc->AddUndo(UNDO_OBJECT_BASEDATA , op);//Undoに保存
op->SetPosition(vector(-100 , -200 , -300));//opを移動
op#PRIM_CUBE_LEN = vector(300 , 500 , 400);//立方体オブジェクトのサイズを変更
doc->EndUndo();

立方体でテストする前提なので最初のif()はいらないけど、念のため。
実行すると座標もサイズも変更されました。では「取り消し」…
えっ?予想外な結果…サイズも取り消された。基本データとはどこまでのデータなんだぁぁぁ…
SDK9.6には詳しいことは載っていないし。
ポイントオブジェクトのポイントデータとか?まぁ色々試してみないといけないから、基本データはここまで。

UNDO_OBJECT_NEW「オブジェクトのUndosを作成」を実験。
最初のAddUndo()を
doc->AddUndo(UNDO_OBJECT_NEW , op);に変更
新規のシーンファイルに立方体を追加しスクリプトを実行。
立方体は移動する。そこで「取り消し」をすると立方体が消えた。
訳が分からなくなってきた?Undoは記録されている訳だが?

UNDO_OBJECT_ACTIVEで試してみる。
同じように新規のシーンファイルに立方体を追加しスクリプトを実行。立方体は移動。ここまでは同じ。
「取り消し」を実行すると。ポジションは変わらず選択が解除。
もう一度「取り消し」を実行すると、立方体は消え「取り消し」はこれ以上できない。
「取り消し」の回数は問題ないね。ポジションが記録されていないようだ。

UNDO_OBJECT_SELECTIONも試してみる。
同じように実行して「取り消し」を実行。ポジションは元に戻らず選択された状態。
もう一度「取り消し」すると消えてしまう。

方法を変えて何度試してもACTIVE/SELECTIONは把握できず…挫折…
BASEDATAがなんとなく使えるから次に進む。

ん?
C4Dを再起動させてプラグインメニューのユーザースクリプトを選択するとエラーが起きている。

[FAIL]Script'選択したオブジェクトをグローバルの原点へ'(Menu State)Line1:Incompatible values... NIL/OBJCT

(Menu State)ってメニューの状態?あっ…

if((doc->GetFirstObject()->GetBit(BIT_AOBJ)) || (doc->GetFirstObject()->SearchNext(BIT_AOBJ)))ENABLE = TRUE;
else ENABLE = FALSE;

シーンにオブジェクトが何にも無かったら
doc->GetFirstObject()はnilを返してnilはクラスじゃないから、GetBit()やSearchNext()とか、どのクラスのメンバ関数を使ってもエラーだよね…だったら…
最初に組んだコードに、更に追加…「最初のオブジェクトがあるか?」

var obj = doc->GetFirstObject();
if(obj){
  if((obj->GetBit(BIT_AOBJ)) || (obj->SearchNext(BIT_AOBJ)))ENABLE = TRUE;
  else ENABLE = FALSE;
}else ENABLE = FALSE;

これで完成かぁ。思い込みで組んでるからこんなミスをするんだよね。スクリプトは「Global_to_000.ZIP」です。

次は思い切って「選択したオブジェクトをグローバルの原点へ」スクリプトをメニュープラグインにしてみよう。多分そんなに難しくないと思う。「ダイアログも使ってないし。ほぼ完成に近い!!」。なんて言っておいて 途中で挫折したりして…あっそうそう、プラグインのソースを組むけど、正式にMAXONにIDはもらわないよ。あくまでも実験だし、ツールとしてはちっぽけだし、多分ネット検索したら似たようなプラグインが既にあるかも。
ただ皆に「もしかして頑張ったら自分にもプラグインが作れそうだ」と思ってくれたらいいなぁ。まぁこの程度ならスクリプトで充分だけどね。

|

« 選択された複数のオブジェクトを処理 | トップページ | 前々回のC4Dスクリプトのプラグイン化 »

コメント

この記事へのコメントは終了しました。