Display Conduit Introduction
Last changed: -204.177.179.108

.
Developer.NET
SummaryA How-To introduction to writing display conduits

Rhino4 allows you to define your own display conduits, which provide access to many levels of the display pipeline. In contrast, in Rhino3 it was only possible to implement a DrawCallBack which allowed you to draw geometry in back, middle or foreground. DrawCallBacks did not expose any methods to adjust the clipping of the viewports, the lighting setup and a lot of other properties that would be useful. However, they were straightforward to implement. DisplayConduits are a bit trickier, hence this Wiki page.

 

The DisplayPipeline in Rhino4 is a big and complicated class and we do not recommend you try and 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):

 

 

At one end is the Rhino model, a collection of 3D geometry and data. At the other end is the image we want to display on the screen, a collection of 2D pixels. In order 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:

 

 

Note that the pipeline itself is not bound to the channels, it just executes its code and every once and again 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 of these channels and tinker with variables and drawing methods.

 

In the above case, the conduit implemented two channels; SC_CALCBOUNDINGBOX and SC_POSTDRAWOBJECTS (the "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 will be 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 in order 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 3D 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 take a 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 coloured, c-plane axis system ON TOP of all objects. Because I disable DepthWriting and Testing prior to drawing my points and arrows, my objects are not obscured by the existing geometry (which was drawn in a channel previous to SC_POSTDRAWOBJECTS:

Another thing to realize is that there can be many other active conduits present as well and there's no way of telling which one is going to be called first. Do not write code that might screw up other peoples conduits.