STUDIO TAMA


thumbnail

投稿日:2025-05-03

【Rhinoceros】RhinoのDisplayPipelineに割り込む:DisplayConduitクラスを理解する

  • #Rhinoceros

  • #C#

  • #Plugin

概要

Rhinoceros の DisplayConduit クラスを使うと、Rhino の描画パイプラインに割り込んで、独自の描画処理を追加することができます。この機能を使えば、たとえば以下のようなことが可能になります

  • モデルの上にカスタム UI(HUD)を表示
  • 独自のアノテーションやガイドを描画
  • 特定オブジェクトに対してハイライトや補助線を描く

本記事では DisplayConduit の各描画タイミングが「いつ」「なぜ」呼ばれるのかを公式ドキュメントを参照しながら確認していこうと思います。


  • 公式ドキュメント
  • API ドキュメント

この記事のソースコード ↓

GitHub - shuya-tamaru/Rhino-DisplayConduitSandBox

Contribute to shuya-tamaru/Rhino-DisplayConduitSandBox development by creating an account on GitHub.

プロジェクトの立上げ

とりあえず 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 の準備

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 で記載したものが出てくると思います。ただしそれぞれ発火のタイミングがあるので全部は出てこないと思います。

thumbnail

DisplayConduit の各オーバーライド関数と呼ばれる条件・目的

各メソッドの役割と呼ばれるタイミングをまとめるとこんな感じ ↓

#メソッド名呼ばれるタイミング補足・目的
1ObjectCulling描画対象のオブジェクトのリストに変化があったとき?たぶん・・・オブジェクトを追加・削除したり、非表示やロックしたりすると発火。自信ない・・・
2CalculateBoundingBoxビューの再描画時これから描画するシーンの大きさを決めている。DisplayConduit で新しいものを追加したときに、ここでそのオブジェクトを追加してあげないと描画範囲とみなされなくて表示されないことがある。
3CalculateBoundingBoxZoomExtents_Zoom _Extents などの全体表示コマンド実行時全体表示などのコマンド実行時の精密な BoundingBox を計算する。
4PreDrawObjectsすべてのオブジェクトが描画される前に一度だけ呼び出される-
5PreDrawObject各オブジェクトが描画される前に、オブジェクトごとに呼び出される-
6PostDrawObjectsRhino がすべてのオブジェクトを描画した後に呼ばれるここではまだ深度テストが有効、つまりオブジェクトの前後関係を考慮したまま何かを追加できる
7DrawForegroundPostDrawObjects の後深度テストがオフ。つまり常に最前面に表示される。例えば画面左下の XYZ 座標軸とか
8DrawOverlayRhino がフィードバックモードのとき例えば Box 等を入力中の一時的な描画をとか

それぞれのタイミングで独自のロジックを割り込ませたいときに使用していく感じです。

サンプル 1:点を描く

公式ドキュメントのサンプルを実行してみますが、公式ドキュメントでは原点に点を配置しているのですが、原点だと 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}
  1. まず、CalculateBoundingBox で追加する点(100,0,0)に対する BoundingBox を生成します。点に対する BoundingBox って意味不明だけど、これを e.IncludeBoundingBox(bbox);で追加してあげることで、オブジェクトの描画範囲として認識してくれます。
  2. PreDrawObjects で Rhino のオブジェクトが描画される前に、e.Display.DrawPoint(new Point3d(100, 0, 0));で点を描きます。

でこの状態でコマンドを実行すると、以下画像のように(100,0,0)の位置に点が描かれるんですけど、 例えば CalculateBoundingBox の e.IncludeBoundingBox(bbox);をコメントアウトして再度実行すると、カメラの Zoom 具合だったり位置によって見切れちゃうときがあります。(おそらく frustum の計算に考慮されなくて、たまたま frustum の範囲内だと表示されるって感じ)なので CalculateBoundingBox で表示対象を登録してあげることが大事だよという公式のサンプルでした。

thumbnail

サンプル 2:作業平面の軸を色付きで描く

もう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 の矢印描画されます。

thumbnail

処理の流れですが、

  1. CalculateBoundingBox で原点に対する BoundingBox を計算して登録
  2. PreDrawObjects でオブジェクトの描画前に矢印を描画する。
  3. 本来 PreDrawObjects は深度テストが有効だけど、e.Display.EnableDepthWriting(false)と e.Display.EnableDepthTesting(false)で深度バッファに登録もしないし前後関係の検証も無効化していないので、配置した Box の後ろに隠れないで矢印が表示されるよ。

というサンプルです。

ちなみに、深度テストのを無効化する処理を 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 でシェーダーを適用することもできます。

thumbnail

また、公式ドキュメントに DisplayConduit を使用する際の注意点が記載されていますが、他の開発者のプラグインで DisplayConduit が使用されている場合、実行順序を特定するすべはなく相互作用への配慮する必要があると述べられてます。つまり他のプラグインも DisplayConduit 使ってるかもだからそれを配慮した開発しろよってことみたいです。

他にも公式のサンプルあるので興味あれば覗いてみてください。

以上

【参考】

目 次