GetValue および SetValue の代わりにオブジェクト モデルを使用する

 
 
 

一般的に、Softimage でスクリプトを記述するには、Softimage ネイティブ スクリプト コマンドと Softimage オブジェクト モデルという 2 つの方法を使用できます。

スクリプト コマンドは、全般的にユーザ インターフェイスの多くのユーザ アクションに対応する Softimage 固有の関数のセットです。たとえば、3D オブジェクトのシーン グラフへの追加(CreatePrim)、シェーダの接続ポイントへの接続(SIConnectShaderToCnxPoint)、任意のプロパティ セットのパラメータ値の変更(SetValue)などです。

オブジェクト モデルでは、3D の世界をまねることで、はるかに深いレベルでアクセスできます。ここで、オブジェクトに対応するクラスのインスタンスを作成します。たとえば、CameraNurbsSurfaceMeshMaterialOperatorControlPointParameterPass などのオブジェクトです。 これらのクラスには、そのオブジェクトに関する何かにアクセスできる関数(メソッドとプロパティ)が用意されています。たとえば、球の回転、頂点の色、カメラの視野、オペレータの接続ポイントなどです。

注:

スクリプト コマンドとオブジェクト モデルの相違点に関する詳細は、「Softimage SDK アーキテクチャについて」を参照してください。

パフォーマンス: コマンドとオブジェクト モデル

オブジェクト モデルで記述されたスクリプトの方が、コマンドで記述されたスクリプトより高速に実行できるのには、次の 2 つの理由があります。

  • コマンドを実行すると、デフォルトで Script Editor にコマンド エコーがロギングされます。 コマンドのロギングには時間がかかるため、ログ ウィンドウが開くとスクリプトの実行が遅くなり、さらに開く回数が増えるほどスクリプトの実行が遅くなります。

    ロギングを一時的にオフにすることができますが、以下の理由でコマンドの実行速度は変わりません。

  • オブジェクト モデルは、ロー レベルで Softimage のコア エンジンにアクセスします。つまり、コマンドでは、タスクを実行するために折衝しなければならないコードのレイヤがオブジェクト モデルより多くなります。 オブジェクト モデルは、基本的に Softimage のコア エンジンと直接折衝する COM(ActiveX)レイヤを使用して実装されています。

SetValue および GetValue から Parameter オブジェクトに移行する

GetValue および SetValue コマンドは、基本的には、Softimage シーン オブジェクトに関するさまざまな情報が格納されているプロパティ セットに対して動作します。 スクリプト コマンドからオブジェクト モデルに切り替えたい場合は、コマンドで実行していた処理をオブジェクト モデルに移行する方法を習得する必要があります。

GetValue および SetValue コマンドに相当するオブジェクト モデルは ParameterParameterCollection オブジェクトです。このオブジェクトで、ほぼ同じ機能を実行できます。 たとえば、以下の JScript コード例で SetValue を使用する方法を示します。

	/*
		The following example uses SetValue to set various parameters 
		on some scene elements
	*/

	NewScene( null, false );

	// Create a sphere and change the wireframe color to red
	// This demonstrates we can use SetValue for object properties.
	CreatePrim( "Sphere", "MeshSurface" );
	DeselectAll();
	MakeLocal( "sphere.display", siNodePropagation );
	SetValue( "sphere.display.wirecol", 15 );

	CreatePrim( "Circle", "NurbsCurve" );
	CreatePrim( "Circle", "NurbsCurve" );
	CreatePrim( "Circle", "NurbsCurve" );
	CreatePrim( "Circle", "NurbsCurve" );

	// Set the radius of all circles to 1
	// This demonstrates we can use SetValue in a bulk-edit fashion.
	SetValue( "circle*.circle.radius", 1 );

	// Set the radius of "circle" to 2, and the radius of "circle1" to 3
	// This demonstrates we can use SetValue with an array of values.
	SetValue( "circle.circle.radius,circle1.circle.radius", new Array(2,3) );

	// Hide the grid in all views, for all cameras (except scene camera)
	// This demonstrates we can use SetValue for cameras, views, etc.
	SetValue( "Views.*.*.camvis.gridvis", false );

	// Change current frame to 30 
	// This demonstrates we can use SetValue for playback settings
	SetValue( "PlayControl.Current", 30.000 );

...同じ内容をほぼすべてオブジェクト モデルで記述したスクリプトは次のようになります(MakeLocal コマンドは例外です)。

	/*
		The following example uses SetValue to set various parameters
		on some scene elements
	*/

	NewScene( null, false );
	var root = ActiveSceneRoot;

	// Create a sphere and change the wireframe color to red
	// This demonstrates the OM equivalent of using
	// SetValue for object properties.
	var obj = root.AddGeometry( "Sphere", "MeshSurface" );
	var prop = 	MakeLocal( obj.Properties( "Display" ), siNodePropagation );

	// MakeLocal returns an XSICollection containing the new local Display
	// property, so we can use it to get at the Wireframe Color parameter
	prop(0).Parameters( "wirecol" ).Value = 15;

	// Make four circles and add them to an XSICollection
	var circles = XSIFactory.CreateActiveXObject( "XSI.Collection" );
	for (var i=0; i<4; i++)
	{
		circles.Add( root.AddGeometry( "Circle", "NurbsCurve" ) );
	}

	// Set the radius of all circles to 1
	// This demonstrates how to access the Radius parameter the long way
	var c = new Enumerator( circles );
	for (; !c.atEnd(); c.moveNext() )
	{
		c.item().Parameters( "radius" ).Value = 1;
    }

    // Set the radius of the 1st circle to 2, and the radius of the 2nd to 3
    // This demonstrates how to access the Radius parameter with a shortcut
	circles(0).radius.Value = 2;
	circles(1).radius.Value = 3;

	// Since viewports are not available in the OM, there is no way to hide
	// the grid in all views, for all cameras (except scene camera) as you
	// can with SetValue:
	//
	//      SetValue( "Views.*.*.camvis.gridvis", false );
	//
	// However, you can hide the grid for the scene camera via the OM.
	// NB: The following three lines can all be combined into a single
	//     line, but it has been split up here for readability:
	var scncam = root.FindChild( "", siCameraPrimType );
	var camvis = scncam.Properties( "Camera Visibility" )
	camvis.Parameters( "gridvis" ).Value = false;

	// Change current frame to 30
	// This demonstrates the OM equivalent of using SetValue for playback 
	// settings.
	// NB: Again, this could be a single line but is split for readability:
	var playctrl = ActiveProject.Properties( "Play Control" )
	playctrl.Parameters( "Current" ).Value = 30.000;

ケース スタディ: アニメーション パスに沿って null を配置する

Michael Isner(www.isner.com)の好意により転載

通常の作業方法では、コマンドを使用しておおまかなスクリプトを作成してから、部分的にオブジェクト モデルを使用して改造し、高速化を図ります。 以下に、このようなプロセスの簡単な例を示します。この例では、球をアニメートし、すべてのフレームに null を配置します。

それでは、球をアニメートするスクリプトを使用して開始します。 以下は、ログ ウィンドウをカット アンド ペーストしたものです(読み易いように表面的な手直しを加えています)。

	NewScene
	CreatePrim "Sphere", "NurbsSurface"
	SaveKey "sphere.kine.local.posx,sphere.kine.local.posy" _
		& ",sphere.kine.local.posz", 1
	SetValue "PlayControl.Current", 50
	Translate , -16.6915152140782, 1.0970178921512E-15, -17.9162534756914, _
		siRelative, siView, siObj, siXYZ
	SaveKey "sphere.kine.local.posx,sphere.kine.local.posy" _
		& ",sphere.kine.local.posz", 50
	SetValue "PlayControl.Current", 100
	Translate , -17.3869950146648, -9.15956686650513E-16, 14.9592019311598, _
		siRelative, siView, siObj, siXYZ
	SaveKey "sphere.kine.local.posx,sphere.kine.local.posy" _
		& ",sphere.kine.local.posz", 100

コマンドを使用した最初の記述

すべてのフレームに null を配置するには、時系列で処理を繰り返す方法を知っておく必要があります。 タイムラインをスクラブするとフィードバックが得られます。

	SetValue "PlayControl.Current", 27

したがって、時系列で処理を繰り返すには、以下のように記述します。

	for i = 0 to 100
		SetValue "PlayControl.Current", i
	next

続いて、フレームごとに null を取得する必要があります。この処理は簡単で、null の取得をカット アンド ペーストするだけです。

	GetPrim "Null"

もちろん、GetPrim "Null" を実行するだけでは、100 個の null すべてを追跡するのは困難です。 最後に作成した null を取得するには、新しく作成した null(またはオブジェクト)を currentNull のような変数名に戻す必要があります。 この処理は以下のように記述します。

	Set currentNull = GetPrim ("Null")

続いて、各フレームでの球の位置を取得し、そこに現在の null を配置する必要があります。 値を取得するには、その値をスライダ内で探して移動し、そのログをコピー アンド ペーストし、SetValue の箇所を GetValue に変更します。 したがって、球のグローバル位置を取得するには、[Global Transform]プロパティ エディタを開き、x、y、z の位置をスライドします。以下のコードが戻ります。

	SetValue "sphere.kine.global.posx", -9.535
	SetValue "sphere.kine.global.posy", 0.233
	SetValue "sphere.kine.global.posz", -1.744

これらの行の 1 つが以下のようになります。

	x = GetValue("sphere.kine.global.posx")

丸括弧を追加する必要もあることが確認できます。 変数と等しい結果を設定する場合はいつでも、コマンドでこの処理を実行する必要があります。 null を取得した場合とは異なり、冒頭に set を使用しなかったことに注意してください。 これは、xが単なる数値であるのに対して、currentNull はオブジェクトを戻すため、set キーワードを使用しなければならないからです。

それでは、すべてをまとめて、各フレームでの球の位置をロギングします。 このコードは、以下のようなります。

	for i = 0 to 100
		SetValue "PlayControl.Current", i
		x = GetValue("sphere.kine.global.posx")
		y = GetValue("sphere.kine.global.posy")
		z = GetValue("sphere.kine.global.posz")
		logmessage "("& x &","& y &","& z & ")"
	next

実に簡単ですね。それでは、その null を x、y、z の各値に移動します。 何かを移動すると、以下のコードが戻ります。

	Translate , 0, 0, 0, siRelative, siView, siObj, siZ

相対的な座標(球からの戻り値で取得しているようなグローバルではない)を意味する siRelative が表示されているため、厳密には正しくないことがすぐに分かるはずです。 カット アンド ペーストしただけのコマンドで何が発生するか不明な場合、私はすぐにヘルプを参照することにしています。 このケースでは、siDeltaMode のモードとして、代わりに siAbsolute を使用する必要があることが分かりました。 したがって、null を取得する行も追加すると、以下のスクリプトになります。

	dim i, x, y, z
	for i = 0 to 100
		SetValue "PlayControl.Current", i
		x = GetValue("sphere.kine.global.posx")
		y = GetValue("sphere.kine.global.posy")
		z = GetValue("sphere.kine.global.posz")
		Set currentNull = GetPrim ("Null")
		Translate currentNull, x, y, z, siAbsolute, siView, siObj, siXYZ
	next

うまく動作しました。 冒頭に dim i, x, y, z の行を追加したことにお気づきになったかもしれません。 この行を追加したのは、変数を宣言するとクリプトの実行が高速になるためです。 すべてのスクリプト上に Option Explicit 行を配置する習慣をつけることをお勧めします。宣言していない変数が処理されるたびにエラーが発生するため、スクリプトのデバッグ(変数の入力ミスの指摘など)が速く簡単にできるからです。

スクリプトの処理時間の計測

スクリプトの実行速度が特に問題にならない場合もあります。 そのような場合は、おそらく広範なループがない簡単な処理を実行しているため、コマンドを使用するだけですみます。

ただし、高速化する必要がある処理を実行したいが、C++ で書き直す時間がないような場合、オブジェクト モデルは最適なオプションです。

以下に、処理にかかった秒数を戻すスクリプトの簡単なバリエーションを示します。

	time_in = timer
	dim i, x, y, z
	for i = 0 to 100
		SetValue "PlayControl.Current", i
		x = GetValue("sphere.kine.global.posx")
		y = GetValue("sphere.kine.global.posy")
		z = GetValue("sphere.kine.global.posz")
	Set currentNull = GetPrim ("Null")
	Translate currentNull, x, y, z, siAbsolute, siView, siObj, siXYZ
	next
	time_out = timer
	logmessage "elapsed time: " & round(time_out - time_in,3) 

私のマシン上では、17.3611 秒かかりました。 この処理ではログ ウィンドウが開かれたことに注意してください。 時間計測スクリプトを実験する場合は、以下の点を了解しておいてください。

  1. コマンドのロギングには時間がかかります。 ログ ウィンドウが開くと、スクリプトの実行が遅くなります。 さらに開く回数が増えるほど、スクリプトの実行が遅くなります。

  2. スクリプトをボタンにドラッグ アンド ドロップすると、ロギングされなくなるため、実行速度が速くなります。 実際にスクリプトをボタンにドラッグ アンド ドロップすると、5.787 秒に短縮されました。

  3. オブジェクト モデルの実行速度が速い理由の 1 つは、上記と同様にロギングされないことです。 もう 1 つの理由は、ローレベルで Softimage のコアにアクセスできることです。

オブジェクト モデル バージョンを作成する

それでは、このスクリプトをオブジェクト モデルで修正した場合を見てみましょう。 最初に、位置の取得を見てみます。 以下に、オブジェクト モデルを使用した例を示します。

	x = mySphere.Kinematics.Global.parameters("posx").value

GetValue などのコマンドを呼び出すのではなく、その球オブジェクトのプロパティを調べているのが確認できます。 これで数値が戻されるため、元のスクリプト内の GetValue をこのような行に置き換えることができます。

しかし、このスクリプトの本当の時間のロスは、時間をスクラブしていることです。 実際に、この球がはるかに大きなシーン内に配置された場合、PlayControl を修正すると、各フレームでのシーン内にあるすべてのオブジェクトの更新で非常に多くの時間をロスすることになります。 ここで実行できる大きな時間節約は、球の変換全体(Scale、Rotation、Position の記述)をピックアップすることです。 変換全体をピックアップすることで、値を読み取るタイミングを定義できます。 現在の時間で変換を読み取る処理は、以下のようになります。

	set currentTransform = mySphere.Kinematics.Global.Transform

時間の経過とともに変換を取得する処理は、以下のようになります。

	set currentTransform = mySphere.Kinematics.Global.Transform(currentFrame)

しかし、x = 34.333のような数値を簡単に扱う方法は利用できません。利用できるのは変換オブジェクトです。 「コマンドおよびスクリプト リファレンス」には、次のような空間を操作するのに役立つオブジェクトが数多く用意されています: SIMatrix3SIMatrix4SIQuaternionSIRotationSITransformationSIVector3。 さらに、XSIMath までスクロールしていくと、これらのオブジェクトを作成し、異なる空間の間での変換を行うためのツールも豊富に用意されていることがわかります。

これらのオブジェクト(SITransformation など)の大半で、他のオブジェクトに変換するメソッドを含む数多くのツールが利用できます。 ここでは、変換を xyz 位置に変更します。 xyz 位置のベクトルを返す SITransformation.GetTranslation というメソッドがあります。 したがって、スクリプトは以下のようになります。

	set posVector = xsimath.CreateVector3
	set currentTransform = mySphere.Kinematics.Global.Transform(i)
	currentTransform.GetTranslation posVector 

これで位置を取得できたので、このコードを元のスクリプトに統合します。 SIVector3 のヘルプをのぞいてみるだけで、SIVector3 から x を取得する最も簡単な方法は、posVector.xを記述することだとわかります。 したがって、スクリプトは以下のようになります。

	for i = 0 to 100
		set posVector = xsimath.CreateVector3
		set currentTransform = mySphere.Kinematics.Global.Transform(i)
		currentTransform.GetTranslation posVector 
		Set currentNull = GetPrim ("Null")
		Translate currentNull, posVector.x, posVector.y, posVector.z, _
			siAbsolute, siView, siObj, siXYZ
	next

しかし、このスクリプトを実行しても、うまく動作しません。 理由は、mySphere が何であるかをまだ定義していないためです。 コマンドでは、名前を持つだけで十分な場合がほとんどです。 しかし、オブジェクト モデルでは、常にオブジェクトが必要です。 この処理の最も簡単でよく使用する実行方法は、セレクションからです。 冒頭に以下の行を追加できます。

	SelectObj "sphere"
	set mySphere = Selection(0)

プログラミングの経験がある場合、この操作方法があまり適切ではないと感じ、Selection を使わない方法を選択するかもしれません。 同じ処理を実行するもう 1 つの手法は、シーン ルートを取得し、それ以降で sphere という名前を検索する方法です。 以下に、これらの行を含むスクリプトを示します。

	set oRoot = Application.ActiveProject.ActiveScene.Root
	set mySphere = oRoot.FindChild("sphere")
	for i = 0 to 100
		set posVector = xsimath.CreateVector3
		set currentTransform = mySphere.Kinematics.Global.Transform(i)
		currentTransform.GetTranslation posVector 
		Set currentNull = GetPrim ("Null")
		Translate currentNull, posVector.x, posVector.y, posVector.z, _
			siAbsolute, siView, siObj, siXYZ
	next

この時点で終わりにして、先に進むことができます。 オブジェクトモデルですべての処理を実行する必要はありません。 通常、私がオブジェクト モデルを使用するのは、パフォーマンスが格段に向上すると思われる場合や当面のタスクに対して最も便利な場合だけです。 しかし、ユーザにオブジェクト モデルを理解してもらうために、このスクリプトの分解を続けます。

スクリプトをちょっと見てみると、コマンドで実行している 2 つの処理(null の取得と変換)があることを確認できます。 変換について言えば、currentTransform を xyz 座標に変換するのではなく、変換を currentNull に入れ替えることで、多くの時間を節約できます。 スクリプトは以下のようになります。

	set oRoot = Application.ActiveProject.ActiveScene.Root
	set mySphere = oRoot.FindChild("sphere")
	for i = 0 to 100
		set currentTransform = mySphere.Kinematics.Global.Transform(i)
		Set currentNull = GetPrim ("Null")
		currentNull.Kinematics.Global.Transform = currentTransform 
	next

ジョブを完了して、完全なオブジェクト モデルにするには、オブジェクト モデルから null を取得します。 AddPrimitive(X3DObject)メソッドの良いところは、新しいオブジェクトを作成する際に親を定義することです。 したがって、球の子として null を配置すると、最終的なスクリプトは以下のようになります。

	dim oRoot, mySphere, currentTransform, currentNull
	set oRoot = Application.ActiveProject.ActiveScene.Root
	set mySphere = oRoot.FindChild("sphere")
	for i = 0 to 100
		set currentTransform = mySphere.Kinematics.Global.Transform(i)
		set currentNull= mySphere.AddPrimitive("Null")
		currentNull.Kinematics.Global.Transform = currentTransform 
	next

この実行時間を計測したところ、私のマシン上では 2.3148 秒かかりました。 つまり、ボタンから実行するコマンド バージョンより 2.5 倍、UI から実行するコマンドより 7.5 倍速くなりました。