Site Tools


Display Conduit Introduction for .NET

Summary: A how-to introduction to writing display conduits
Notice: The Rhino.NET SDK is deprecated in Rhino 5. This example is adapted for the new RhinoCommon SDK is here.

Rhino 4 lets you define your own display conduits, which provide access to many levels of the display pipeline. In contrast, in Rhino 3 it was only possible to implement a DrawCallBack which let you draw geometry in the back, middle, or foreground. DrawCallBacks did not expose any methods to adjust the clipping of the viewports, the lighting setup, or a lot of other useful properties. But they were straightforward to implement. DisplayConduits are a bit trickier, hence this Wiki page.

The DisplayPipeline in Rhino 4 is a big and complicated class and we do not recommend you derive your own pipeline. Instead, we've exposed something called a conduit for easy access. The pipeline itself is structured like this (except in reality there are many more channels):

pipelineimagea.jpg

At one end is the Rhino model, a collection of 3-D geometry and data. At the other end is the image we want to display on the screen, a collection of 2-D pixels. To get from model to image, the pipeline has to process a lot of information. These steps have been put into things we call Channels. Whenever you implement a new conduit, you have to implement at least one of these channels, like so:

pipelineimageb.jpg

Note that the pipeline itself is not bound to the channels. It just executes its code and occasionally it calls the conduits, who perceive this as a new channel execution. This means that you cannot disable certain portions of the pipeline (or portions of other conduits) from within your conduit. The pipeline has to go through these motions to generate an image:

  1. Set up the drawing buffer so we have something to draw on (SC_INITFRAMEBUFFER).
  2. Load projection {camera; target; lens length; etc.} and calculate world-to-screen transformations (SC_SETUPFRUSTUM).
  3. Create a list of all objects to draw (SC_OBJECTCULLING).
  4. Determine the extent of the entire scene (SC_CALCBOUNDINGBOX).
  5. Calculate the depth of the z-buffer based on the boundingbox (SC_CALCCLIPPINGPLANES).
  6. Create a lighting environment to be used in Shaded modes (SC_SETUPLIGHTING).
  7. Draw the background of the scene. This includes the viewport color, BackgroundBitmaps and grids (SC_DRAWBACKGROUND).
  8. Draw the stuff that has to be drawn before any objects are drawn (SC_PREDRAWOBJECTS)
  9. Prepare for the drawing of an object, i.e. set up the display attributes for it (SC_PREOBJECTDRAW).
  10. Draw all the objects (SC_DRAWOBJECT (this channel is called once for every object)).
  11. Recover from drawing the object (SC_POSTOBJECTDRAW).
  12. Draw stuff on top of all the objects, like selection wireframes etc. (SC_POSTDRAWOBJECTS).
  13. Draw the foreground, like the little axis-system in the lower left corner of viewports (SC_DRAWFOREGROUND).
  14. Honestly, I have no idea what Overlay is for (SC_DRAWOVERLAY).
  15. Perform pixel operations on the framebuffer (such as contrast and brightness adjustments) (SC_POSTPROCESSFRAMEBUFFER).

You can implement all these channels and tinker with variables and drawing methods.

In the above case, the conduit implemented two channels, SC_CALCBOUNDINGBOX and SC_POSTDRAWOBJECTS (SC stands for Support Channel). In VB.NET this conduit would look like:

   Public Class MyConduit
     Inherits MRhinoDisplayConduit
 
     Public Sub New()
       MyBase.New(New MSupportChannels(RMA.Rhino.MSupportChannels.SC_CALCBOUNDINGBOX Or _
                                       RMA.Rhino.MSupportChannels.SC_POSTDRAWOBJECTS), True)
     End Sub
 
     Public Overrides Function ExecConduit(ByRef dp As RMA.Rhino.MRhinoDisplayPipeline, _
                                           ByVal current_channel As UInteger, _
                                           ByRef termination_flag As Boolean) As Boolean
       Return True
     End Function
   End Class

As you can see we have to supply the channels we're going to use in the constructor. This means we cannot change which channels we want to tinker with at runtime. By default, conduits are not enabled. You have to specifically do that. But it can be done easily by setting the second constructor argument to True. Once our conduit is constructed and enabled, the ExecConduit() function is called whenever the Rhino pipeline has reached a channel we're interested in. In our case, this function will always be called once with the current_channel flag set to SC_CALCBOUNDINGBOX and then once with the flag set to SC_POSTDRAWOBJECTS. Thus, inside the ExecConduit() function we have to evaluate which channel we're in and then take appropriate action:

   Public Overrides Function ExecConduit(ByRef dp As RMA.Rhino.MRhinoDisplayPipeline, _
                                         ByVal current_channel As UInteger, _
                                         ByRef termination_flag As Boolean) As Boolean
     If current_channel = RMA.Rhino.MSupportChannels.SC_CALCBOUNDINGBOX Then
       MyBase.m_pChannelAttrs.m_BoundingBox.Set(New On3dPoint(0, 0, 0), True)
     End If
 
     If current_channel = RMA.Rhino.MSupportChannels.SC_PREDRAWOBJECTS Then
       dp.DrawPoint(New On3dPoint(0, 0, 0))
     End If
 
     Return True
   End Function

Note that you have to return True to keep the pipeline alive. Only return False in an emergency. The code above simply draws a single point at the world origin. Since a point is 3-D geometry, it is subject to z-depth clipping. This means that if the point resides outside the z-buffer region, it will not be visible. (It will get clipped.) By default, the clipping planes are set up to encompass the boundingbox of the entire Rhino model. If you're drawing stuff which is potentially outside this box, you should override SC_CALCBOUNDINGBOX to make sure your objects are not clipped.

Let's look at a more complex drawing routine:

   Public Overrides Function ExecConduit(ByRef dp As RMA.Rhino.MRhinoDisplayPipeline, _
                                         ByVal current_channel As UInteger, _
                                         ByRef termination_flag As Boolean) As Boolean
 
     If current_channel = RMA.Rhino.MSupportChannels.SC_CALCBOUNDINGBOX Then
       MyBase.m_pChannelAttrs.m_BoundingBox.Set(dp.GetRhinoVP.ConstructionPlane.m_plane.origin, True)
     End If
 
     If current_channel = RMA.Rhino.MSupportChannels.SC_POSTDRAWOBJECTS Then
       Dim cPlane As IOn3dmConstructionPlane = dp.GetRhinoVP.ConstructionPlane
 
       Dim colX As New OnColor(RMA.Rhino.RhUtil.RhinoApp.AppSettings.GridSettings.m_xaxis_color)
       Dim colY As New OnColor(RMA.Rhino.RhUtil.RhinoApp.AppSettings.GridSettings.m_yaxis_color)
       Dim colZ As New OnColor(RMA.Rhino.RhUtil.RhinoApp.AppSettings.GridSettings.m_zaxis_color)
 
       dp.EnableDepthWriting(False)
       dp.EnableDepthTesting(False)
 
       dp.GetRhinoVP.SetDrawColor(0)
       dp.DrawPoint(cPlane.m_plane.origin)
 
       dp.GetRhinoVP.SetDrawColor(colX)
       dp.GetRhinoVP.DrawDirectionArrow(cPlane.m_plane.origin, New On3dVector(cPlane.m_plane.xaxis) * 0.75)
 
       dp.GetRhinoVP.SetDrawColor(colY)
       dp.GetRhinoVP.DrawDirectionArrow(cPlane.m_plane.origin, New On3dVector(cPlane.m_plane.yaxis) * 0.75)
 
       dp.GetRhinoVP.SetDrawColor(colZ)
       dp.GetRhinoVP.DrawDirectionArrow(cPlane.m_plane.origin, New On3dVector(cPlane.m_plane.zaxis) * 0.75)
 
       dp.EnableDepthWriting(True)
       dp.EnableDepthTesting(True)
     End If
 
     Return True
   End Function

This piece of code draws a colored, c-plane axis system on top of all objects. Because I disable DepthWriting and Testing before drawing my points and arrows, my objects are not obscured by the existing geometry (which was drawn in a channel previous to SC_POSTDRAWOBJECTS):

conduitcplaneaxis.jpg

There can be many other active conduits present as well and there's no way of telling which one will be called first. Do not write code that might screw up other people's conduits.

pipelineimagec.jpg

C#

public class MyConduit : MRhinoDisplayConduit
{
  public MyConduit() : base(new MSupportChannels(RMA.Rhino.MSupportChannels.SC_CALCBOUNDINGBOX | RMA.Rhino.MSupportChannels.SC_POSTDRAWOBJECTS), true)
  {}
 
  public override bool ExecConduit(ref RMA.Rhino.MRhinoDisplayPipeline dp, uint current_channel, ref bool termination_flag)
  {
    if (current_channel == RMA.Rhino.MSupportChannels.SC_CALCBOUNDINGBOX)
    {
      base.m_pChannelAttrs.m_BoundingBox.Set(dp.GetRhinoVP().ConstructionPlane().m_plane.origin, true );
    }
    if (current_channel == RMA.Rhino.MSupportChannels.SC_POSTDRAWOBJECTS)
    {
      IOn3dmConstructionPlane cPlane = dp.GetRhinoVP().ConstructionPlane();
 
      OnColor colX = new OnColor(RMA.Rhino.RhUtil.RhinoApp().AppSettings().GridSettings().m_xaxis_color);
      OnColor colY = new OnColor(RMA.Rhino.RhUtil.RhinoApp().AppSettings().GridSettings().m_yaxis_color);
      OnColor colZ = new OnColor(RMA.Rhino.RhUtil.RhinoApp().AppSettings().GridSettings().m_zaxis_color);
 
      dp.EnableDepthWriting(false);
      dp.EnableDepthTesting(false);
 
      dp.GetRhinoVP().SetDrawColor(0);
      dp.DrawPoint(cPlane.m_plane.origin);
 
      dp.GetRhinoVP().SetDrawColor(colX);
      dp.GetRhinoVP().DrawDirectionArrow(cPlane.m_plane.origin, new On3dVector(cPlane.m_plane.xaxis) * 0.75);
 
      dp.GetRhinoVP().SetDrawColor(colY);
      dp.GetRhinoVP().DrawDirectionArrow(cPlane.m_plane.origin, new On3dVector(cPlane.m_plane.yaxis) * 0.75);
 
      dp.GetRhinoVP().SetDrawColor(colZ);
      dp.GetRhinoVP().DrawDirectionArrow(cPlane.m_plane.origin, new On3dVector(cPlane.m_plane.zaxis) * 0.75);
 
      dp.EnableDepthWriting(true);
      dp.EnableDepthTesting(true);
    }
    return true;
  }
}
developer/displayconduit.txt ยท Last modified: 2016/01/22 by sandy