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):
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:
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:
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):
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.
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; } }