投稿日:2025-05-03
#Rhinoceros
#C#
#Plugin
Rhinoceros の DisplayConduit クラスを使うと、Rhino の描画パイプラインに割り込んで、独自の描画処理を追加することができます。この機能を使えば、たとえば以下のようなことが可能になります
本記事では DisplayConduit の各描画タイミングが「いつ」「なぜ」呼ばれるのかを公式ドキュメントを参照しながら確認していこうと思います。
この記事のソースコード ↓
とりあえず VisualStudio で Rhino のプラグイン開発環境を立ち上げます。この辺は詳しく説明しないので必要であれば以下の公式ドキュメントを参照ください。
今回私は DisplayConduitSandBox というプロジェクト名で立ち上げました。 立ち上がったら、とりあえず DisplayConduitSandBoxCommand.cs からいらないものを削除して綺麗にします。
DisplayConduitSandBoxCommand.cs
1using Rhino; 2using Rhino.Commands; 3 4namespace DisplayConduitSandBox 5{ 6 public class DisplayConduitSandBoxCommand : Command 7 { 8 public DisplayConduitSandBoxCommand() 9 { 10 Instance = this; 11 } 12 public static DisplayConduitSandBoxCommand Instance { get; private set; } 13 14 public override string EnglishName => "DisplayConduitSandBoxCommand"; 15 16 protected override Result RunCommand(RhinoDoc doc, RunMode mode) 17 { 18 //ここにコードを書いていく 19 return Result.Success; 20 } 21 } 22}
これで準備 OK。
DisplayConduit クラスがどのような描画のタイミングに介入できるかを確認するために、オーバーライド可能な描画イベントにログを仕込んだ MyConduit.cs を作成します。
MyConduit.cs
1using Rhino.Display; 2using Rhino; 3 4namespace DisplayConduitSandBox 5{ 6 public class MyConduit : DisplayConduit 7 { 8 public MyConduit() 9 { 10 RhinoApp.WriteLine("init"); 11 Enabled = false; 12 } 13 14 protected override void ObjectCulling(CullObjectEventArgs e) 15 { 16 RhinoApp.WriteLine("1. ObjectCulling"); 17 base.ObjectCulling(e); 18 } 19 20 protected override void CalculateBoundingBox(CalculateBoundingBoxEventArgs e) 21 { 22 RhinoApp.WriteLine("2. CalculateBoundingBox"); 23 base.CalculateBoundingBox(e); 24 } 25 26 protected override void CalculateBoundingBoxZoomExtents(CalculateBoundingBoxEventArgs e) 27 { 28 RhinoApp.WriteLine("3. CalculateBoundingBoxZoomExtents"); 29 base.CalculateBoundingBoxZoomExtents(e); 30 } 31 32 protected override void PreDrawObjects(DrawEventArgs e) 33 { 34 RhinoApp.WriteLine("4. PreDrawObjects"); 35 base.PreDrawObjects(e); 36 } 37 38 protected override void PreDrawObject(DrawObjectEventArgs e) 39 { 40 RhinoApp.WriteLine("5. PreDrawObject"); 41 base.PreDrawObject(e); 42 } 43 44 protected override void PostDrawObjects(DrawEventArgs e) 45 { 46 RhinoApp.WriteLine("6. PostDrawObjects"); 47 base.PostDrawObjects(e); 48 } 49 50 protected override void DrawForeground(DrawEventArgs e) 51 { 52 RhinoApp.WriteLine("7. DrawForeground"); 53 base.DrawForeground(e); 54 } 55 56 protected override void DrawOverlay(DrawEventArgs e) 57 { 58 RhinoApp.WriteLine("8. DrawOverlay"); 59 base.DrawOverlay(e); 60 } 61 } 62}
各関数内に記載している base.は Rhino が本来行う描画もちゃんとやってね。親クラスの処理もちゃんとやれよという記述。
これを DisplayConduitSandBoxCommand.cs でインスタンスを生成し、 conduit.Enabled = true にして有効化、doc.Views.Redraw()で再描画します。
DisplayConduitSandBoxCommand.cs
1using Rhino; 2using Rhino.Commands; 3 4namespace DisplayConduitSandBox 5{ 6 public class DisplayConduitSandBoxCommand : Command 7 { 8 public DisplayConduitSandBoxCommand() 9 { 10 Instance = this; 11 } 12 public static DisplayConduitSandBoxCommand Instance { get; private set; } 13 14 public override string EnglishName => "DisplayConduitSandBoxCommand"; 15 16 protected override Result RunCommand(RhinoDoc doc, RunMode mode) 17 { 18 var conduit = new MyConduit(); 19 conduit.Enabled = true; 20 RhinoApp.WriteLine("Conduit started..."); 21 doc.Views.Redraw(); 22 23 return Result.Success; 24 } 25 } 26}
とりあえずデバッグモードでビルドして Rhino 上で"DisplayConduitSandBoxCommand"コマンドを実行するとコマンドラインに RhinoApp.WriteLine で記載したものが出てくると思います。ただしそれぞれ発火のタイミングがあるので全部は出てこないと思います。
各メソッドの役割と呼ばれるタイミングをまとめるとこんな感じ ↓
# | メソッド名 | 呼ばれるタイミング | 補足・目的 |
---|---|---|---|
1 | ObjectCulling | 描画対象のオブジェクトのリストに変化があったとき?たぶん・・・ | オブジェクトを追加・削除したり、非表示やロックしたりすると発火。自信ない・・・ |
2 | CalculateBoundingBox | ビューの再描画時 | これから描画するシーンの大きさを決めている。DisplayConduit で新しいものを追加したときに、ここでそのオブジェクトを追加してあげないと描画範囲とみなされなくて表示されないことがある。 |
3 | CalculateBoundingBoxZoomExtents | _Zoom _Extents などの全体表示コマンド実行時 | 全体表示などのコマンド実行時の精密な BoundingBox を計算する。 |
4 | PreDrawObjects | すべてのオブジェクトが描画される前に一度だけ呼び出される | - |
5 | PreDrawObject | 各オブジェクトが描画される前に、オブジェクトごとに呼び出される | - |
6 | PostDrawObjects | Rhino がすべてのオブジェクトを描画した後に呼ばれる | ここではまだ深度テストが有効、つまりオブジェクトの前後関係を考慮したまま何かを追加できる |
7 | DrawForeground | PostDrawObjects の後 | 深度テストがオフ。つまり常に最前面に表示される。例えば画面左下の XYZ 座標軸とか |
8 | DrawOverlay | Rhino がフィードバックモードのとき | 例えば Box 等を入力中の一時的な描画をとか |
それぞれのタイミングで独自のロジックを割り込ませたいときに使用していく感じです。
公式ドキュメントのサンプルを実行してみますが、公式ドキュメントでは原点に点を配置しているのですが、原点だと CalculateBoundingBox の効果がわかりにくいので(100,0,0)に変えています。
MyConduit.cs
1using Rhino.Display; 2using Rhino; 3using Rhino.Geometry; 4 5namespace DisplayConduitSandBox 6{ 7 public class MyConduit : DisplayConduit 8 { 9 public MyConduit() 10 { 11 Enabled = false; 12 } 13 14 // protected override void ObjectCulling(CullObjectEventArgs e) 15 // { 16 // base.ObjectCulling(e); 17 // } 18 19 protected override void CalculateBoundingBox(CalculateBoundingBoxEventArgs e) 20 { 21 base.CalculateBoundingBox(e); 22 var bbox = new BoundingBox(); 23 bbox.Union(new Point3d(100, 0, 0)); 24 e.IncludeBoundingBox(bbox); 25 26 } 27 28 protected override void PreDrawObjects(DrawEventArgs e) 29 { 30 base.PreDrawObjects(e); 31 e.Display.DrawPoint(new Point3d(100, 0, 0)); 32 } 33 34 // protected override void PreDrawObject(DrawObjectEventArgs e) 35 // { 36 // base.PreDrawObject(e); 37 // } 38 39 // protected override void PostDrawObjects(DrawEventArgs e) 40 // { 41 // base.PostDrawObjects(e); 42 // } 43 44 // protected override void DrawForeground(DrawEventArgs e) 45 // { 46 // base.DrawForeground(e); 47 // } 48 49 // protected override void DrawOverlay(DrawEventArgs e) 50 // { 51 // base.DrawOverlay(e); 52 // } 53 } 54}
でこの状態でコマンドを実行すると、以下画像のように(100,0,0)の位置に点が描かれるんですけど、 例えば CalculateBoundingBox の e.IncludeBoundingBox(bbox);をコメントアウトして再度実行すると、カメラの Zoom 具合だったり位置によって見切れちゃうときがあります。(おそらく frustum の計算に考慮されなくて、たまたま frustum の範囲内だと表示されるって感じ)なので CalculateBoundingBox で表示対象を登録してあげることが大事だよという公式のサンプルでした。
もう1つのサンプルも実行してみますが、作業平面の XYZ 軸の矢印を描画しています。
MyConduit.cs
1using Rhino.Display; 2using Rhino; 3using Rhino.Geometry; 4 5namespace DisplayConduitSandBox 6{ 7 public class MyConduit : DisplayConduit 8 { 9 public MyConduit() 10 { 11 Enabled = false; 12 } 13 14 //protected override void ObjectCulling(CullObjectEventArgs e) 15 //{ 16 // base.ObjectCulling(e); 17 //} 18 19 protected override void CalculateBoundingBox(CalculateBoundingBoxEventArgs e) 20 { 21 base.CalculateBoundingBox(e); 22 var bbox = new BoundingBox(); 23 bbox.Union(e.Display.Viewport.ConstructionPlane().Origin); 24 e.IncludeBoundingBox(bbox); 25 } 26 27 //protected override void CalculateBoundingBoxZoomExtents(CalculateBoundingBoxEventArgs e) 28 //{ 29 // base.CalculateBoundingBoxZoomExtents(e); 30 //} 31 32 //protected override void PreDrawObjects(DrawEventArgs e) 33 //{ 34 // base.PreDrawObjects(e); 35 //} 36 37 protected override void PreDrawObject(DrawObjectEventArgs e) 38 { 39 base.PreDrawObjects(e); 40 41 var cPlane = e.Display.Viewport.ConstructionPlane(); 42 var xColor = Rhino.ApplicationSettings.AppearanceSettings.GridXAxisLineColor; 43 var yColor = Rhino.ApplicationSettings.AppearanceSettings.GridYAxisLineColor; 44 var zColor = Rhino.ApplicationSettings.AppearanceSettings.GridZAxisLineColor; 45 46 e.Display.EnableDepthWriting(false); 47 e.Display.EnableDepthTesting(false); 48 49 e.Display.DrawPoint(cPlane.Origin, System.Drawing.Color.White); 50 e.Display.DrawArrow(new Line(cPlane.Origin, new Vector3d(cPlane.XAxis) * 10.0), xColor); 51 e.Display.DrawArrow(new Line(cPlane.Origin, new Vector3d(cPlane.YAxis) * 10.0), yColor); 52 e.Display.DrawArrow(new Line(cPlane.Origin, new Vector3d(cPlane.ZAxis) * 10.0), zColor); 53 54 e.Display.EnableDepthWriting(false); 55 e.Display.EnableDepthTesting(false); 56 } 57 58 // protected override void PostDrawObjects(DrawEventArgs e) 59 // { 60 // base.PostDrawObjects(e); 61 // } 62 63 // protected override void DrawForeground(DrawEventArgs e) 64 // { 65 // base.DrawForeground(e); 66 67 // } 68 69 // protected override void DrawOverlay(DrawEventArgs e) 70 // { 71 // base.DrawOverlay(e); 72 // } 73 } 74}
上記のコードで"DisplayConduitSandBoxCommand"コマンドを実行、原点付近に適当に Box を配置します。すると原点付近に XYZ の矢印描画されます。
処理の流れですが、
というサンプルです。
ちなみに、深度テストのを無効化する処理を 2 回書いてる理由はよくわかんないです。1回でも機能しました。
また深度テストを無視したい場合、常に最前面に表示したかったら DrawForeground を使って以下のようにも書けると思います。
MyConduit.cs
1using Rhino.Display; 2using Rhino; 3using Rhino.Geometry; 4 5namespace DisplayConduitSandBox 6{ 7 public class MyConduit : DisplayConduit 8 { 9 public MyConduit() 10 { 11 Enabled = false; 12 } 13 14 //protected override void ObjectCulling(CullObjectEventArgs e) 15 //{ 16 // base.ObjectCulling(e); 17 //} 18 19 protected override void CalculateBoundingBox(CalculateBoundingBoxEventArgs e) 20 { 21 base.CalculateBoundingBox(e); 22 var bbox = new BoundingBox(); 23 bbox.Union(e.Display.Viewport.ConstructionPlane().Origin); 24 e.IncludeBoundingBox(bbox); 25 } 26 27 //protected override void CalculateBoundingBoxZoomExtents(CalculateBoundingBoxEventArgs e) 28 //{ 29 // base.CalculateBoundingBoxZoomExtents(e); 30 //} 31 32 //protected override void PreDrawObjects(DrawEventArgs e) 33 //{ 34 // base.PreDrawObjects(e); 35 //} 36 37 // protected override void PreDrawObject(DrawObjectEventArgs e) 38 // { 39 // base.PreDrawObjects(e); 40 // } 41 42 // protected override void PostDrawObjects(DrawEventArgs e) 43 // { 44 // base.PostDrawObjects(e); 45 // } 46 47 protected override void DrawForeground(DrawEventArgs e) 48 { 49 base.DrawForeground(e); 50 var cPlane = e.Display.Viewport.ConstructionPlane(); 51 var xColor = Rhino.ApplicationSettings.AppearanceSettings.GridXAxisLineColor; 52 var yColor = Rhino.ApplicationSettings.AppearanceSettings.GridYAxisLineColor; 53 var zColor = Rhino.ApplicationSettings.AppearanceSettings.GridZAxisLineColor; 54 55 e.Display.DrawPoint(cPlane.Origin, System.Drawing.Color.White); 56 e.Display.DrawArrow(new Line(cPlane.Origin, new Vector3d(cPlane.XAxis) * 10.0), xColor); 57 e.Display.DrawArrow(new Line(cPlane.Origin, new Vector3d(cPlane.YAxis) * 10.0), yColor); 58 e.Display.DrawArrow(new Line(cPlane.Origin, new Vector3d(cPlane.ZAxis) * 10.0), zColor); 59 60 } 61 62 // protected override void DrawOverlay(DrawEventArgs e) 63 // { 64 // base.DrawOverlay(e); 65 // } 66 } 67}
もう少しややこしいことをすると、以下の GIF のように GLSL でシェーダーを適用することもできます。
また、公式ドキュメントに DisplayConduit を使用する際の注意点が記載されていますが、他の開発者のプラグインで DisplayConduit が使用されている場合、実行順序を特定するすべはなく相互作用への配慮する必要があると述べられてます。つまり他のプラグインも DisplayConduit 使ってるかもだからそれを配慮した開発しろよってことみたいです。
他にも公式のサンプルあるので興味あれば覗いてみてください。
以上
【参考】