選択中の ICETree ノードに対してごにょごにょするスクリプト、というのが最終的には目的なんですが、今回はその前提の段階となる、選択中のノードの取得について最近やったことを書きます。 あ、違った、更にもう一段階前の話です。 「選択中」のノードと言うけれど、じゃあ、いったいどこで選択中なのですか? という話です。
どこで選択中なのか? って、ICETree で選択中に決まってるじゃないですか。でも、ICETree って言うけれど、ICETree にはフローティングウインドウもあれば、ドックされた状態(レイアウトに埋め込んだ状態)もあるじゃないですか。それらを網羅して全部取得できないかしら、という実験の途中経過という感じです。
実はですね、この SI-Support の記事です。 いつもお世話になっているこのブログの、選択中の ICEノードをゲットするという記事を見て、やってみたんですよ。 そしたら、フローティングウインドウは問題ないけれど、ドックされたビューの中にある ICETree ビューには、その記事のスクリプトが効かないんですよ。 ドック状態のICETree ビューは無視されちゃうんです。 あ、ここで言うドック状態とは、フローティングではないという意味で言っています。 例えばビューポートの4つのビューのうち、ビューポートCを横に広げてビュー2つ分を占拠して使ったりするじゃないですか。そういう状態のことです。
その記事の中では、フローティングの ICETree ビューは、ビューの名前が ICE Tree だからいいけど、フローティングじゃないと名前が違ってしまうために、名前で選り分けることができない。 だから Type でフィルタしなさいという意味のことが書かれています。
しかしですよ、この記事のスクリプトを試してみると、選り分け対象にするために最初に全部取得するビューの中に、ドックされたビューが入っていないんです。 フローティングしか取得されないんです。 だから、Type でフィルタするとか以前の問題です。 ドックされた ICETree ビューは取得できておらず、取得できてない中から探しても当然見つからないわけです。意味ないです。
ですよね?
この記事の方法、フローティングウインドウにしか効きませんよね?
俺だけ?
誰かやった人は、教えてください。
そこで、こんなスクリプトを書いてみました。
JScript
------------------------------------------------------------------------
// どちらかをコメントアウト
TargetType = "ICE Tree";
//TargetType = "Render Tree";
Logmessage( "----------------------" );
// ファンクションに飛ばして目的のビュー(ICETree か RenderTree)を全部引っかき集める
var oMyFuckinViews = GetFuckinViewsByFuckinType( TargetType );
Logmessage( "ヒットしたView の数 :" + oMyFuckinViews.count + " (" + TargetType + ")" );
Logmessage( "----------------------" );
//選択されていたノードの格納容器
var oNodes = XSIFactory.CreateObject( "XSI.Collection" );
oNodes.Unique = true; //これがないと、1つのICETreeを複数ウインドウで開いてた場合、ノードがダブる
//親オブジェクトの格納容器
var oNodeParents = XSIFactory.CreateObject( "XSI.Collection" );
oNodeParents.Unique = true;//これがないと、選んでいたノードの数だけ親がいることになってしまう
// ファンクションに飛ばして引っかき集めた View 全部をループ
for ( var i=0; i<oMyFuckinViews.count; i++ )
{
var oMyFuckinView = oMyFuckinViews(i);
Logmessage( "ヒットしたView(" + (i+1) + ") - " + oMyFuckinView );
// 選択中のノードをカンマ区切りの文字列として取得
StringSelected = oMyFuckinView.GetAttributeValue( "selection" );
var aSelected = StringSelected.split( "," );// それをアレイに変換
for ( var j=0; j<aSelected.length; j++ )// アレイをループ
{
if ( aSelected[j] != "" )// 何もノードを選択してない場合、なぜか無文字の部屋ができてしまうので除外
{
var oNode = Dictionary.GetObject( aSelected[j], false );// 文字からオブジェクトとして取得
if ( oNode )
{
oNodes.Add( oNode );// オブジェクトとしてノードを取得できたら、格納
Logmessage( " そのViewで選択中のノード(" + (j+1) + ") - " + oNode.Name );
if ( TargetType == "ICE Tree" )// 親も格納 ICETree の場合はその親である3Dオブジェクト
{
var oParent = oNode.Parent3DObject;
}
else if ( TargetType == "Render Tree" )// 親も格納 RenderTree の場合は単純にノードの Parent
{
var oParent = oNode.Parent;
}
oNodeParents.Add( oParent );
Logmessage( " そのノードの親 : " + oParent.Name );
}
}
}
}
Logmessage( "----------------------" );
Logmessage( "選択されていたノードの数 : " + oNodes.count );
Logmessage( "選択されていたノードの親オブジェクトの数 : " + oNodeParents.count );
// View のタイプを文字列で渡すと View が格納されたコレクションを返すファンクション
function GetFuckinViewsByFuckinType( TargetViewType )
{
// 格納容器
var oTargetViews = XSIFactory.CreateObject( "XSI.Collection" );
// フローティングかどうかを問わず全View を ViewCollection として取得
var oViews = Application.Desktop.ActiveLayout.Views;
Logmessage( "全ビューの数 :" + oViews.count );
// 取得した全View をループ
for ( var i=0; i<oViews.count; i++ )
{
var oView = oViews(i);
Logmessage( oView + " : " + oView.type );//これを非コメント化で詳細ログ
// Type が指定のものと一致したら、格納(ここでヒットするのは、フローティングのもの)
if ( oView.type == TargetViewType ) oTargetViews.Add( oView );
// View.Views プロパティは、効かない View オブジェクトにはエラーが出るので、仕方なく try catch
// なんかいい方法ないですかね
try{
var oViewViews = oView.Views;
for ( var j=0; j<oViewViews.count; j++ )
{
var oViewView = oViewViews(j);
//Logmessage( " ---- " + oViewViews(j).type );//これを非コメント化で詳細ログ
// Type が指定のものと一致したら、格納(ここでヒットするのは、ドックされたもの)
if ( oViewView.type == TargetViewType ) oTargetViews.Add( oViewView );
}
}
catch(e){}
}
return oTargetViews;
}
------------------------------------------------------------------------
このスクリプトのダウンロード(右クリックで保存)
実行すると、このようにスクリプトエディタの中に結果がログされます。
このスクリーンショットでは、ドックされた ICETreeビュー(ビューポートC)と、フローティングの ICETree ビューの2つがありますが、ログを見るとその両方がヒットしています。
どうでしょか。フローティングウインドウも、ドックされたビューも、全部取得して、ICETree だけ抽出できているように見えるんですがね。 どうですか。
ちなみに RenderTree にも効くようです。スクリプト冒頭で、TargetType という変数の中身を "Render Tree" にするだけです。
大事なのはサブルーチン(ファンクション)の中身の方なので、そちらだけ重点的に書いておきます。 スクリプト後半にあるファンクション GetFuckinViewsByFuckinType の中身です。
マニュアルを調べたり、過去によくわからないまま書いた自作スクリプトの残骸の調査から、フローティングかドックされているかに関係なく現在作業中の XSI 上で開かれている全部の View を取得するには、以下のように書けばいいらしいと判りました。
// フローティングかどうかを問わず全View を ViewCollection として取得
var oViews = Application.Desktop.ActiveLayout.Views;
Logmessage( "全ビューの数 :" + oViews.count );
これで oViews にビューがコレクションとして入りました。例えば自前のカスタムシェルフなどを開いていた場合、それもここで取得できています。 その他、インターフェース上に見えている全てのものが取得できているように見えます。
で、フローティングウインドウの ICETree の場合は、ここで取得できた View オブジェクトというオブジェクトの Type を調べると、もう ICE Tree になっているんですよ。 なので、取得できた全ビューをループして、ひとつずつ Type を調べます。
// 取得した全View をループ
for ( var i=0; i<oViews.count; i++ )
{
var oView = oViews(i);
//Logmessage( oView + " : " + oView.type );//これを非コメント化で詳細ログ
// Type が指定のものと一致したら、格納(ここでヒットするのは、フローティングのもの)
if ( oView.type == TargetViewType ) oTargetViews.Add( oView );
Logmessage の行は、表示が長くなるのでコメントアウトしてますが、頭の // を削除して非コメント化すれば詳細がログされるので何が取得できているのかよくわかると思います。
上の SI-Support の記事のように Filter を使っても良かったんですが、なんとなくこちらの方が分かりやすかろうと思って、ループして Type をひとつずつ調べる方式にしています。
で、ここまでは本質的には SI-Support の記事と全く同じです。 違うのはここ以降です。
例えば、上に書いたようにビューポートCを ICETree にしていた場合にどうなるか。
4つのビューポート A,B,C,D って、実は View Manager(内部的な名前は vm ) というものの下に階層化されているんですよね。
なので、上記の Application.Desktop.ActiveLayout.Views で取得できるのは、この vm までなんです。 取得できた View の Type を調べると、"View Manager" って出てきます。Logmessage を非コメント化して詳細ログを見るとわかります。
Type を調べたら View Manager であって ICE Tree ではないのだから、当然上の Type 判定には引っかかりません。 よって、ビューポートCは ICETree にしてあるにもかかわらずスルーされてしまうのです。 これが、SI-Support のスクリプトの問題なような気がしたのです。
なので、取得できた View オブジェクトに対しもう1回 Views プロパティを使ってその下層にある View を取得します。
// View.Views プロパティは、効かない View オブジェクトにはエラーが出るので、仕方なく try catch
// なんかいい方法ないですかね
try{
var oViewViews = oView.Views;
for ( var j=0; j<oViewViews.count; j++ )
{
var oViewView = oViewViews(j);
//Logmessage( " ---- " + oViewViews(j).type );//これを非コメント化で詳細ログ
// Type が指定のものと一致したら、格納(ここでヒットするのは、ドックされたもの)
if ( oViewView.type == TargetViewType ) oTargetViews.Add( oViewView );
}
}
catch(e){}
これです。
ここでわかりにくいのは、Views プロパティって言っても実は2つあることなんですよね。
ひとつは、上の
var oViews = Application.Desktop.ActiveLayout.Views;
これですね。 Layout オブジェクトの Views プロパティです。 これで取得できるのは View オブジェクトコレクションです。 ひとつひとつを取り出せば、それは View オブジェクトです。
でも、そうやってひとつひとつ取り出された View オブジェクトにも、さらに Views プロパティがあるんですね。 今度は View オブジェクトの Views プロパティです。 これで取得できるのはやはり、View オブジェクトコレクションです。ひとつひとつを取り出せば、それは View オブジェクトです。
このように、View オブジェクトは Layout オブジェクトのすぐ下層にある場合もあるし、Layoutオブジェクトのすぐ下層にある View の更に下層にある場合もあるという、全くもって腹の立つ分かり難さです。このために Layout.Views と View.Views を両方試してあげないと、全部の View が取得できないことになります。
もっと厳密に言うと、View.Views で得られた View それぞれにも Views プロパティがあるわけで、これは再帰的無限地獄に突入する気がします。 なのでこのスクリプトでは1階層しか潜っていません。
とまあそんなわけで、Layout.Views から取れた View そのものの Type が ICE Tree ならそれはフローティングのはずだし、ドックされていれば vm などの子供になってるはずなので一階層潜って( vm の Views を調べて)更に Viewオブジェクトを取得し、そちらに対しても Type のテストをしてあげるということをやっているのが、このファンクションです。
ちなみに、vm 以外で同じ状態になる例を挙げると、自作のカスタムシェルフ、リレーショナルビュー等だと思います。 あと、XSI Explorer なんかもビューの子供にビューがあるタイプのビューなので、フローティングの XSI Explorer の中に ICETree が含まれる場合などは、同様に View.Views を調べてあげる(=フローティングの XSI Explorer (Viewオブジェクト)に対してさらに Views を調べてあげる) 必要があるかも知れません。
それともうひとつ、Layout.Views で取得できた View オブジェクトのうち、全ての View オブジェクトが、Views プロパティを持っているわけではない、という点も重要です。 上の vm の例のように、1階層潜ったところに ICE Tree が存在してないか調べようとして、取得できた全 View に対し Views プロパティをチェックしようとすると、エラーで止まってしまうのです。
vm やカスタムシェルフやリレーショナルビューや XSI Explorer など、下層に何かのビューを所有している View オブジェクトに対しては効くのですが、Desktop.Views で得られる全 View のうち下層に何も持たないやつも多いので、そいつらに対して Views プロパティの呪文を唱えると、エラーで止まってしまうんですよね。
これを回避する方法がわからず、仕方なく try catch を使っています。 try catch ってのは全てのエラーに対して効いてしまうので、例えばただのタイプミスで動かないスクリプトでも、そこで止めずに実行を続けてしまいます。なのでメンテナンス上非常に都合悪い。問題箇所の特定が難しくなりがちです。 なのでなるべく使いたくないのですが、他に方法が見つからない時はこうして仕方なく使ってます。 なんか方法ないでしょうかね?
ともかく、これでドックされたビューの中にある ICE Tree ビューもちゃんと取得できるようになったように見えるのですが、どうでしょうかね。落とし穴ありますかね。 どなたか、もし試してみたら、教えて下さいませ。
っていうかそもそも、もっと簡単に全 ICETree ビューを取得する方法、あるのでしょか? あったら知りたいです。
.
最近のコメント