【集中講座】Rhino: スクリプトで連続して座標値を出力する

はじめに

空間上の点の座標を調べる際、一般的にRhinoの[EvaluatePt]コマンドを使用します。このコマンドを実行すると、選択した位置のワールド座標および作業平面(ローカル)座標の値を表示できます。

しかし、[EvaluatePt]コマンドは1回の実行につき1つしか座標を出力できず、複数の点の座標を次々と取得するには、調べたい点の数だけ複数回コマンドを実行する必要があります。

 このページではスクリプトを使用して「点座標を連続して表示する」方法や似た方法で「連続して座標値が書かれたドットを作成する」方法などを紹介したいと思います。

スクリプトファイルはこちらよりダウンロードいただけますので、ぜひご活用ください。

[EvaluatePt]コマンドの課題とGrasshopperとの違い

Rhinoの[EvaluatePt]コマンドを実行し、ビューポート上でクリックすると、ワールド座標およびローカル作業平面の座標が表示できます。また、オプションの「ラベル」を使用することで、指定した座標にドットを作成することができるなど非常に便利なコマンドです。

ただしこのコマンドは一度に一箇所にしか使用できず、複数の箇所の座標値を求めるには点の数だけコマンドを繰り返す必要があります。

Grasshopperを使用する場合の課題

複数の点を扱う場合、Grasshopperを思い浮かべる方もいるかもしれません。確かにGrasshopperであれば、点をまとめて取り込んで一括処理することは得意です。しかし、「Rhinoのコマンドのように画面上で一つずつクリックしていき、その都度結果を見る」といったインタラクティブな操作はGrasshopperではできません。またパネルに繋いで点座標を確認した場合は、どれがどの座標なのかも一見すると理解しづらいです(もちろん点の位置に座標を表示するといった形でアルゴリズムを作成すれば確認できます)。

既存コマンドは修正できない

「それなら[EvaluatePt]コマンド自体を連続で使えるように、変更すればいいのでは?」と考えるかもしれません。ですが、Rhinoに組み込まれている既存のコマンドは、中身が見えないようコンパイルされているため、それを修正することはできません。

ここでは、自分好みにコマンドの挙動をカスタマイズする手段として、Rhino上で動作するスクリプトを作成してみたいと思います。

スクリプトを利用した点取得とカスタマイズ

以下は、点を表示するスクリプトの骨組みです。またRhinoでは複数のPython(2系のIronPython、3系のCpython)やC#など複数のプログラム言語でスクリプトを作成できます。ここでは、C#を使用して、RhinoCommonでスクリプトを記述してみます。

補足 : Pythonの場合はRhinoCommonだけでなく、より簡易なRhinoScriptSyntaxを使用することもできます。RhinoScriptSyntax はRhinoCommonの一部の機能を呼び出しやすくまとめたものとなります。

1. まずは「1点だけ」取得する基本形

連続処理に入る前に、まずは「1回だけクリックして座標を取得する」シンプルなコードを見てみましょう。動作イメージは上図のような処理となります。GetPoint クラスの説明や例は後でリンク等を貼っておきますが、RhinoCommonのUIから点を選択する機能をまとめたクラスになります。

// #! csharp
using Rhino;
using Rhino.Geometry;
using Rhino.Input;
using Rhino.Input.Custom;

var doc = RhinoDoc.ActiveDoc;

// GetPoint(点取得用クラス)のインスタンスを作成
var getPoint = new GetPoint();

//  プロンプトに表示されるメッセージを設定
getPoint.SetCommandPrompt("点を選択してください");

// ユーザーの入力を待機
var result = getPoint.Get();

// 結果が「点」だった場合のみ処理を行う
if (result == GetResult.Point)
{
    var point = getPoint.Point();
    ShowCoordinates(doc,point);
}

補足:
[ScriptEditor]コマンドを実行し、csファイルを開き三角形の実行ボタンを押すことでスクリプトを実行できます。

この状態では、1度クリックするとプログラムが終了してしまいます。これを「右クリックやEnterを押すまで終わらない」ように作り替えたのが、以下の連続取得スクリプトです。

2. 連続して点を取得する(whileループの活用)

さきほどのコードを while (true) というループで囲うことで、ユーザーが終了操作を行うまで何度も処理を繰り返すことができます。

// #! csharp 
using System; 
using Rhino; 
using Rhino.Geometry; 
using Rhino.Input; 
using Rhino.Input.Custom;

var doc = RhinoDoc.ActiveDoc;

var getPoint = new GetPoint(); 
getPoint.SetCommandPrompt("点を選択してください(EnterキーかEscキーで終了)");

// Enterキーやキャンセルが実行されるまで無限ループ 
while (true)
{ 
    // ユーザー操作による、点の取得を実行 
    var getResult = getPoint.Get();
    //  点を取得した場合に処理を実行する。その後ループの先頭に戻り再度入力を待機する
    if (getResult == GetResult.Point)
    {    
        var point = getPoint.Point();
        ShowCoordinates(doc,point);
    }
    // 点の取得(クリック)以外(EnterやEsc)は、無限ループを抜ける
    else
    {
        break;
    }
}

ループの仕組み

最初の例と見比べるとわかるとおり、無限に処理を行う箇所が while (true) { … } という括弧の中身の部分となります。

while の次の () 内はループを続ける条件です。ここに常に true(真)が入っているため、明示的に break(中断)が呼ばれるまで、Rhinoは「点を取って、座標を表示する」という作業を永遠に繰り返してくれるようになります。

補足:

ループの説明のため、ShowCoordinatesメソッドの中身は表示していませんでしたが、下記のようなコードとなります。1回のみ取得するスクリプトでも、連続で取得するスクリプトでも、共通して以下のメソッドを呼び出しています。

//- - - 以下、座標を出力するメソッド 
void ShowCoordinates(RhinoDoc doc, Point3d worldPoint)
{
    // アクティブなビューから作業平面(CPlane)を取得
    var activeView = doc.Views.ActiveView;
    Plane cPlane = activeView.ActiveViewport.ConstructionPlane();
    
    // ワールド座標を作業平面を基準とした作業平面座標に変換
    Point3d localPoint;
    cPlane.RemapToPlaneSpace(worldPoint, out localPoint);
    // :F2 を使用して座標値を小数点以下2桁にフォーマットする。F3であれば小数点3桁
    RhinoApp.WriteLine($"World: {worldPoint.X:F2}, {worldPoint.Y:F2}, {worldPoint.Z:F2}");
    RhinoApp.WriteLine($"Local: {localPoint.X:F2}, {localPoint.Y:F2}, {localPoint.Z:F2}");
}

まとめ

スクリプトを活用することで、標準コマンドでは手間がかかる反復作業を効率化することができます。既存コマンドの仕様やGrasshopperの性質上カバーしづらいインタラクティブな処理を、スクリプトに置き換えることで、日々のモデリング作業の効率化に繋げることができるかと思います。

是非、お試しいただければ幸いです。

補足1 骨組みがわかればカスタマイズも自由自在

スクリプトと聞くと難しく感じるかもしれませんが、行いたい内容の「骨組みの部分」さえ分かれば、あとは必要箇所のみを差し替えることで自分が作りたい機能を自在に作ることができます。

上の例で言えば、World座標が必要ないなら、ShowCoordinates内の

RhinoApp.WriteLine($"World: {worldPoint.X:F2}, {worldPoint.Y:F2}, {worldPoint.Z:F2}");

を削除すれば、表示されなくなります。また、F2の箇所をF3に変更すれば小数点3桁までの表示に変更できます。

補足2 テキストドットでZ方向の高さを表示

上記のスクリプトの、ShowCoordinatesメソッドの箇所を、テキストドットを作成するといった内容に変更すれば、連続して独自の設定を行なったテキストドットを作ることも可能です。

取得した座標のうち、ワールドZ方向の高さのみを抽出し、注釈ドット(Dot)として作成・配置する例です。ShowCoordinates箇所を別のメソッドに差し替えていますがGetPointの骨組みなどは変更していません。

//- - - 以下、Z座標を書いたTextDotを作成するメソッド
void CreateZCoordinateTextDot(RhinoDoc doc, Point3d worldPoint)
{
	//  Z座標を文字列として作成
	var zCoordinateText = $"Z={worldPoint.Z:F2}";

	//  TextDotを作成
	var textDot = new TextDot(zCoordinateText , worldPoint);

	//  フォントの大きさを指定
	textDot.FontHeight = 20;

	//  Rhinoのドキュメントに追加
	doc.Objects.AddTextDot(textDot);
}

補足3 テキストドットに昇順で番号を振る

上記のスクリプトの、ShowCoordinatesメソッドの箇所を、番号を昇順で一つずつ増やしていけば、連続した番号を持つテキストドットを作成できます。

上記のように番号を振っていくことで、後で変更箇所を伝える、図面等で指示するといった用途でも使用できるかもしれません。このあたりは日常的に行っている処理をスクリプトで置き換えると効率的に作業できるかと思います。

これも基本的な骨組みは同様です。カウントする数字のみWhileループの外側でint count = 1;のような形で初期値を与えて、実行するたびに一つずつ数字を足してそれをテキストとして設定しています。

補足4 GetPointの高度な設定

GetPointは

  • オプションから詳細な設定を設定できるようにする
  • Undoオプションをつけて、取得結果を戻す
  • 点取得時に形状のプレビューを表示
  • Constraint(拘束)設定をすることで、「サーフェスやメッシュ、曲線上にマウスの移動を制限して、プレビューを表示しながら連続して点を取得していく」

といった、より高度な専用コマンドに拡張していくことも可能です。

RhinoCommon自体のURLは下記を、

https://developer.rhino3d.com/api/rhinocommon/

RhinoCommon内のGetPointクラスの詳細は、下記を参照ください。

https://developer.rhino3d.com/api/rhinocommon/rhino.input.custom.getpoint

またGitHub内のGetPointの使用例は下記などがあります。単数の点を取得した例。

https://github.com/mcneel/rhino-developer-samples/blob/8/rhinocommon/cs/SampleCsCommands/SampleCsGetPoint.cs

複数点を連続取得する例。

https://github.com/mcneel/rhino-developer-samples/blob/8/rhinocommon/cs/SampleCsCommands/SampleCsGetMultiplePoints.cs

 GetPointで点を取得する際に、円や直線のプレビュー表示を追従させた例。

https://github.com/mcneel/rhino-developer-samples/blob/8462fc3487f8b7a58595ef73f6a5c09f92717f60/rhinocommon/snippets/from_rhinocommon/cs/ex_getpointdynamicdraw.cs

公式サンプルにある、サーフェス上にGetPointのマウス位置を拘束してオブジェクトを配置する例です。

https://developer.rhino3d.com/samples/rhinocommon/orient-on-surface/

また上記は、それぞれRhinoのプラグインとして記述されています。プラグインとして実行するときはRunCommandメソッドの中で実行され、戻り値がResult型の結果となります。

 protected override Result RunCommand(RhinoDoc doc, RunMode mode)
{
	コマンドの処理
	return Result型の結果(Result.Success や Result.Failureなど)
}

スクリプトとして実行する際は、以下の変更を加えると使い勝手が良くなります。

  • 不要な「戻り値」の記述を削除する
  • スクリプト全体を try-catch で囲み、エラー時に安全に停止できる例外処理を追加する

 下記は、スクリプト自体をtry〜 catch で挟んで記述した例です。

try
{
	スクリプトの記述;
	if(問題があるときの条件)
{
	throw new Exception(“エラーとなった内容を書く”);
}
	
}
// 発生した例外(エラー)をcatchブロックで受け取る
catch (Exception e)
{
	// エラー内容をRhinoに表示するなど
	Rhino.RhinoApp.WriteLine(e.Message);
}

スクリプトデータダウンロード

こちらで紹介したScriptは下記ボタンよりダウンロードいただけます。