Question about Rhino.Display.RhinoViewport Wallpaper and OpenCV


The RhinoViewport has a Wallpaper property, which could be filled in the viewport.
Is it possible to use “OpenCV” / “OpenCvSharp” to get my camera frames and display those bitmaps in the Rhino Viewport in real-time?

Not with the wallpaper property - that takes a file on disk.

But you could use the display conduit pipeline and draw a bitmap for each frame. For some ideas maybe look at https://github.com/mcneel/rhino-developer-samples/blob/8/rhinocommon/snippets/cs/sprite-drawing.cs . It doesn’t exactly what you’re looking for, but may give some useful starting points, like how to draw bitmaps in the viewport using code.

Ok, I will check that out, thank you very much!

I’ve download the c# file and wrap the code into a RhinoCommandPlugin template. It works fine.

But I’m new to this, and feel confused with the Rhino.Display.DisplayPipeline Class, the CalculateBoundingBox event and the corresponding callback method.

Because the bbox in the callback method seems like a nosense box :face_with_open_eyes_and_hand_over_mouth:

Could anyone shed some light on this and provide more information? I’m eager to learn and appreciate your kindness in advance.

Thank you for your support!

I don’t understand the full context of the code, but the actual call you’re pointing to is making sure that the point (20,0,0) is included in the bounding box being applied.

While that specific box is really a point, it could make perfect sense to be telling Rhino “make sure this point is included in your region of interest” because the rest of the region of interest seems to be defined by some other items.

Please check this guide: Rhino - Display Conduits

Thanks! I’ve seen this guide. And I successfully draw the camera frames onto the RhinoViewport.
But RhinoUI itself becomes frozen and not responding. I’ve also tried to use async method but it seems that RhinoAPP doesn’t allow me to use that.
Here is the non-async version code:

using Rhino;
using Rhino.Commands;
using Rhino.Geometry;
using System;
using OpenCvSharp.Extensions;

namespace MyRhinoCommandPlugin1
{
    public class MyRhinoCommand1 : Command
    {
        bool pressed;
        static Rhino.Display.DisplayBitmap m_sprite;
        static float m_sprite_size = 300;
        static bool m_draw_world_location = true;
        public MyRhinoCommand1()
        {
            Instance = this;
        }
        public static MyRhinoCommand1 Instance { get; private set; }
        public override string EnglishName => "MyRhinoCommand1";
        protected override Result RunCommand(RhinoDoc doc, RunMode mode)
        {
            pressed = false;
            var size_option = new Rhino.Input.Custom.OptionDouble(m_sprite_size);
            var space_option = new Rhino.Input.Custom.OptionToggle(m_draw_world_location, "Screen", "World");
            var go = new Rhino.Input.Custom.GetOption();
            go.SetCommandPrompt("Sprite drawing mode");
            go.AddOptionDouble("Size", ref size_option);
            go.AddOptionToggle("DrawSpace", ref space_option);

            OpenCvSharp.Mat mat = new OpenCvSharp.Mat();
            OpenCvSharp.VideoCapture vc = new OpenCvSharp.VideoCapture(2, OpenCvSharp.VideoCaptureAPIs.DSHOW);

            Rhino.Display.DisplayPipeline.PostDrawObjects += DisplayPipeline_PostDrawObjects;
            Rhino.Display.DisplayPipeline.CalculateBoundingBox += DisplayPipeline_CalculateBoundingBox;
            RhinoApp.EscapeKeyPressed += RhinoApp_EscapeKeyPressed;

            while (true)
            {
                vc.Read(mat);
                System.Drawing.Bitmap bmp = mat.ToBitmap();
                m_sprite = new Rhino.Display.DisplayBitmap(bmp);

                doc.Views.Redraw();
                if (pressed)
                {
                    RhinoApp.WriteLine("Esc key pressed. Exiting command.");
                    break;
                }
            }

            mat.Dispose();
            vc.Dispose();

            Rhino.Display.DisplayPipeline.PostDrawObjects -= DisplayPipeline_PostDrawObjects;
            Rhino.Display.DisplayPipeline.CalculateBoundingBox -= DisplayPipeline_CalculateBoundingBox;
            return Result.Success;
        }
        private void RhinoApp_EscapeKeyPressed(object sender, EventArgs e)
        {
            pressed = true;
        }
        static void DisplayPipeline_CalculateBoundingBox(object sender, Rhino.Display.CalculateBoundingBoxEventArgs e)
        {
            BoundingBox bbox = new BoundingBox(new Point3d(20, 0, 0), new Point3d(20, 0, 0));
            e.IncludeBoundingBox(bbox);
        }
        static void DisplayPipeline_PostDrawObjects(object sender, Rhino.Display.DrawEventArgs e)
        {
            var spr = m_sprite;
            if (m_draw_world_location)
                e.Display.DrawSprite(spr, new Point3d(20, 0, 0), m_sprite_size, System.Drawing.Color.White, false);
            else
                e.Display.DrawSprite(spr, new Point2d(150, 150), m_sprite_size, System.Drawing.Color.White);
        }

    }
}

Here is the async version code, with the help of ChatGPT:

using Rhino;
using Rhino.Commands;
using Rhino.Geometry;
using System;
using OpenCvSharp.Extensions;
using System.Threading.Tasks;

namespace MyRhinoCommandPlugin1
{
    public class MyRhinoCommand1 : Command
    {
        bool pressed;
        static Rhino.Display.DisplayBitmap m_sprite;
        static float m_sprite_size = 300;
        static bool m_draw_world_location = true;
        public MyRhinoCommand1()
        {
            Instance = this;
        }
        public static MyRhinoCommand1 Instance { get; private set; }
        public override string EnglishName => "MyRhinoCommand1";
        protected override Result RunCommand(RhinoDoc doc, RunMode mode)
        {
            pressed = false;
            var size_option = new Rhino.Input.Custom.OptionDouble(m_sprite_size);
            var space_option = new Rhino.Input.Custom.OptionToggle(m_draw_world_location, "Screen", "World");
            var go = new Rhino.Input.Custom.GetOption();
            go.SetCommandPrompt("Sprite drawing mode");
            go.AddOptionDouble("Size", ref size_option);
            go.AddOptionToggle("DrawSpace", ref space_option);

            OpenCvSharp.Mat mat = new OpenCvSharp.Mat();
            OpenCvSharp.VideoCapture vc = new OpenCvSharp.VideoCapture(2, OpenCvSharp.VideoCaptureAPIs.DSHOW);

            Rhino.Display.DisplayPipeline.PostDrawObjects += DisplayPipeline_PostDrawObjects;
            Rhino.Display.DisplayPipeline.CalculateBoundingBox += DisplayPipeline_CalculateBoundingBox;
            RhinoApp.EscapeKeyPressed += RhinoApp_EscapeKeyPressed;

            // Use Task.Run to run the loop in a separate thread
            Task.Run(async () =>
            {
                while (true)
                {
                    vc.Read(mat);
                    System.Drawing.Bitmap bmp = mat.ToBitmap();
                    var m_sprite = new Rhino.Display.DisplayBitmap(bmp);

                    doc.Views.Redraw();

                    if (pressed)
                    {
                        RhinoApp.WriteLine("Esc key pressed. Exiting command.");
                        break;
                    }

                    await Task.Delay(100);
                }
                mat.Dispose();
                vc.Dispose();
            });

            Rhino.Display.DisplayPipeline.PostDrawObjects -= DisplayPipeline_PostDrawObjects;
            Rhino.Display.DisplayPipeline.CalculateBoundingBox -= DisplayPipeline_CalculateBoundingBox;
            return Result.Success;
        }
        private void RhinoApp_EscapeKeyPressed(object sender, EventArgs e)
        {
            pressed = true;
        }
        static void DisplayPipeline_CalculateBoundingBox(object sender, Rhino.Display.CalculateBoundingBoxEventArgs e)
        {
            BoundingBox bbox = new BoundingBox(new Point3d(20, 0, 0), new Point3d(20, 0, 0));
            e.IncludeBoundingBox(bbox);
        }
        static void DisplayPipeline_PostDrawObjects(object sender, Rhino.Display.DrawEventArgs e)
        {
            var spr = m_sprite;
            if (m_draw_world_location)
                e.Display.DrawSprite(spr, new Point3d(20, 0, 0), m_sprite_size, System.Drawing.Color.White, false);
            else
                e.Display.DrawSprite(spr, new Point2d(150, 150), m_sprite_size, System.Drawing.Color.White);
        }

    }
}

Thank you for your further explanation!

I changed the execution method from RhinoCommand to Grasshopper Component. And the UI doesn’t freeze anymore!
Here is my code for drawing the Bitmap onto all RhinoViewports:

using System;
using System.Collections.Generic;

using Grasshopper.Kernel;
using Rhino.Geometry;
using Rhino.Display;
using System.Drawing;
using System.IO;
using Rhino.Render.ChangeQueue;

namespace Hani
{
    public class DrawSprite : GH_Component
    {
        static public RhinoView rhinoView;
        static public Bitmap bmp;

        public DrawSprite()
          : base("DrawSprite", "DS",
              "Draw the Bitmap onto the Viewport",
              "Hani", "Viewport")
        {
        }


        protected override void RegisterInputParams(GH_Component.GH_InputParamManager pManager)
        {
            pManager.AddGenericParameter("rhinoView", "rView", "The viewport to draw Bitmap on.", GH_ParamAccess.item);
            pManager.AddGenericParameter("Bitmap", "B", "The Bitmap to draw.", GH_ParamAccess.item);
            pManager.AddBooleanParameter("run", "r", "Run or not", GH_ParamAccess.item);
        }


        protected override void RegisterOutputParams(GH_Component.GH_OutputParamManager pManager)
        {
        }


        protected override void SolveInstance(IGH_DataAccess DA)
        {
            if (!DA.GetData<RhinoView>(0, ref rhinoView) || !DA.GetData<Bitmap>(1, ref bmp) || !DA.GetData<bool>(2, ref run))
                return;

            var conduit = new MyConduit();
            conduit.Enabled = true;
        }


        protected override System.Drawing.Bitmap Icon
        {
            get
            {
                return null;
            }
        }


        public override Guid ComponentGuid
        {
            get { return new Guid("xxxx"); }
        }
    }

    class MyConduit : DisplayConduit
    {
        protected override void CalculateBoundingBox(CalculateBoundingBoxEventArgs e)
        {
            base.CalculateBoundingBox(e);

            var bbox = new BoundingBox();
            bbox.Union(e.Display.Viewport.ConstructionPlane().Origin);
            e.IncludeBoundingBox(bbox);
        }

        protected override void PostDrawObjects(DrawEventArgs e)
        {
            base.PreDrawObjects(e);


            e.Display.EnableDepthWriting(false);
            e.Display.EnableDepthTesting(false);
            
            DisplayBitmap d_bmp = new DisplayBitmap(DrawSprite.bmp);
            int width = DrawSprite.rhinoView.MainViewport.Bounds.Width;
            int height = DrawSprite.rhinoView.MainViewport.Bounds.Height;
            e.Display.DrawSprite(d_bmp, new Point2d(width/2, height/2), width, height);

            e.Display.EnableDepthWriting(false);
            e.Display.EnableDepthTesting(false);
        }
    }
}