Geometry Preview using Eto Forms Checkbox

Hi all!

I am working on a Rhino 7 plugin that has a simple UI that I am developing with Eto Forms (A dialog box that contains a simple checkbox with two states). I have a noob question about Eto as this is the first project where I am implementing it.

The idea is to select some meshes and perform a boolean union, however, before adding it to the rhino file I would like to preview it and decide if I want to go forward (and add the resulting mesh to the file) or abort the command without adding the geometry. I am facing some issues:

I am calling the Eto Dialog box at some point in my command execution, right after I performed the object selection. I am calling the Eto form as SemiModal, this blocks further code execution until I close the dialog so my data does not get passed to the preview method until I close the window at the moment which is not great.

I am not very familiar with Eto or Events, I managed to write some text in the Rhino Command line when the checkbox is clicked but I am not sure if I can pass values to a method (that creates the preview) using the event. This is because the event does not know what the selection is when I create it as it is not in the scope.

I am not sure if the issue I am having is related to the fact that I am not running the Eto asynchronously or if it can be solved with events and handlers only.

I am attaching the code of my command to this message. Any kind of help, even if it is pointing me in the right direction would be amazing. I tested all the Eto developer samples but could not find what I am looking for.

namespace TestEtoCheckbox
{
    public class TestEtoCheckboxCommand : Command
    {
        public TestEtoCheckboxCommand()
        {
            // Rhino only creates one instance of each command class defined in a
            // plug-in, so it is safe to store a refence in a static property.
            Instance = this;
        }

        ///<summary>The only instance of this command.</summary>
        public static TestEtoCheckboxCommand Instance { get; private set; }

        ///<returns>The command name as it appears on the Rhino command line.</returns>
        public override string EnglishName => "TestEtoCheckboxCommand";

        public bool Preview { get; set; } = false;

        protected override Result RunCommand(RhinoDoc doc, RunMode mode)
        {
            #region 1.Selection

            // Perform Selection
            List<Rhino.DocObjects.ObjRef> my_selection = new List<Rhino.DocObjects.ObjRef>();

            //Set Filter
            bool isMeshCheck(Rhino.DocObjects.RhinoObject rhObject, GeometryBase geometry, ComponentIndex componentIndex)
            {
                bool is_Mesh = false;

                if (geometry.ObjectType == Rhino.DocObjects.ObjectType.Mesh)
                {

                    is_Mesh = true;
                }

                return is_Mesh;
            }

            var gm = new GetObject();
            gm.SetCommandPrompt("Select Meshes to Join");
            gm.SetCustomGeometryFilter(isMeshCheck);
            gm.GetMultiple(1, 0);
            gm.EnablePreSelect(true, false);
            gm.DeselectAllBeforePostSelect = false;

            if (gm.CommandResult() != Result.Success)
            {
                return gm.CommandResult();
            }

            else
            {
                for (int i = 0; i < gm.ObjectCount; i++)
                {
                    var tempGeo = gm.Object(i);
                    my_selection.Add(tempGeo);
                }

            }

            List<Mesh> MeshSelection = new List<Mesh>();
            foreach (var obj in my_selection)
            {
                if((obj.Geometry().ObjectType) == Rhino.DocObjects.ObjectType.Mesh)
                {
                    MeshSelection.Add(obj.Mesh());
                }
            }

            #endregion

            #region 2. Eto Form

            var args = new my_Args
            {
                Preview = Preview
            };

            var dlg = new my_Dialog(args);
            var res = dlg.ShowSemiModal(doc, Rhino.UI.RhinoEtoApp.MainWindow);

            if (res)
            {
                args = dlg.Results;

                Preview = args.Preview;


            }

            if (dlg.Result == false)
            {
                return Result.Cancel;
            }

            #endregion

            #region 3. Perform Boolean Union

            Mesh meshResult = new Mesh();

            if (MeshSelection != null)
            {
                if (MeshSelection.Count > 1)
                {
                    meshResult = Mesh.CreateBooleanUnion(MeshSelection)[0];
                }
                else
                {
                    meshResult = MeshSelection[0];
                }

            }

            doc.Objects.AddMesh(meshResult);

            #endregion

            #region 4. Preview Result

            //Setup Preview?
            var conduit = new DrawBlueMeshConduit(meshResult);

            if (Preview == true)
            {
                conduit.Enabled = true;
                doc.Views.Redraw();
            }
            else
            {
                conduit.Enabled = false;
                doc.Views.Redraw();
            }

            #endregion
            // ---
            doc.Views.Redraw();
            return Result.Success;
        }
    }

    #region Define DisplayConduit

    class DrawBlueMeshConduit : DisplayConduit
    {
        readonly Mesh m_mesh;
        readonly Color m_color;
        readonly DisplayMaterial m_material;
        readonly BoundingBox m_bbox;

        public DrawBlueMeshConduit(Mesh mesh)
        {
            // set up as much data as possible so we do the minimum amount of work possible inside
            // the actual display code
            m_mesh = mesh;
            m_color = System.Drawing.Color.Blue;
            m_material = new DisplayMaterial();
            m_material.Diffuse = m_color;
            if (m_mesh != null && m_mesh.IsValid)
                m_bbox = m_mesh.GetBoundingBox(true);
        }

        // this is called every frame inside the drawing code so try to do as little as possible
        // in order to not degrade display speed. Don't create new objects if you don't have to as this
        // will incur an overhead on the heap and garbage collection.
        protected override void CalculateBoundingBox(CalculateBoundingBoxEventArgs e)
        {
            base.CalculateBoundingBox(e);
            // Since we are dynamically drawing geometry, we needed to override
            // CalculateBoundingBox. Otherwise, there is a good chance that our
            // dynamically drawing geometry would get clipped.

            // Union the mesh's bbox with the scene's bounding box
            e.IncludeBoundingBox(m_bbox);
        }

        protected override void PreDrawObjects(DrawEventArgs e)
        {
            base.PreDrawObjects(e);
            var vp = e.Display.Viewport;
            if (vp.DisplayMode.EnglishName.ToLower() == "wireframe")
                e.Display.DrawMeshWires(m_mesh, m_color);
            else
                e.Display.DrawMeshShaded(m_mesh, m_material);
        }
    }

    #endregion

    #region Define Eto Form
    //Define Eto Form
    internal class my_Args
    {
        public bool Preview { get; set; } = false;
    }
    internal class my_Dialog : Eto.Forms.Dialog<bool>
    {
        my_Args m_args;
        Eto.Forms.CheckBox m_Preview_checkbox;

        public my_Dialog(my_Args args)
        {
            m_args = args ?? throw new ArgumentNullException(nameof(args));

            Title = "Test";
            Padding = new Eto.Drawing.Padding(5);

            var layout = new Eto.Forms.DynamicLayout
            {
                Padding = new Eto.Drawing.Padding(5),
                Spacing = new Eto.Drawing.Size(5, 5)
            };

            layout.AddRow(null); // spacer
            layout.AddRow(CreateCheckBoxes());
            layout.AddRow(null); // spacer
            layout.AddRow(CreateButtons());

            Content = layout;
        }

        public my_Args Results => m_args;

        private Eto.Forms.DynamicLayout CreateCheckBoxes()
        {

            m_Preview_checkbox = new Eto.Forms.CheckBox
            {
                Text = "Preview",
                Checked = m_args.Preview,
                ThreeState = false
            };

            m_Preview_checkbox.CheckedChanged += ChangedValPreview;

            var layout = new Eto.Forms.DynamicLayout { Spacing = new Eto.Drawing.Size(5, 5) };
            layout.AddRow(m_Preview_checkbox);
            return layout;
        }

        private Eto.Forms.DynamicLayout CreateButtons()
        {
            DefaultButton = new Eto.Forms.Button { Text = "OK" };
            DefaultButton.Click += DefaultButton_Click;

            AbortButton = new Eto.Forms.Button { Text = "Cancel" };
            AbortButton.Click += AbortButton_Click;

            var layout = new Eto.Forms.DynamicLayout { Spacing = new Eto.Drawing.Size(5, 5) };
            layout.AddRow(null, DefaultButton, AbortButton, null);
            return layout;
        }

        private void DefaultButton_Click(object sender, EventArgs e)
        {
            m_args.Preview = (bool)m_Preview_checkbox.Checked;
            Close(true);
        }

        private void AbortButton_Click(object sender, EventArgs e)
        {
            Close(false);
        }

        private void ChangedValPreview(object sender, EventArgs e)
        {
            RhinoApp.WriteLine("Value has changed");
        }
    }

    #endregion
}

Thank you very much and keep the good work in this amazing community!

TestEtoCheckboxCommand.cs (8.4 KB)

First, you should move the code that creates the preview into a separate method. This method can take a boolean parameter that determines whether the preview should be enabled or disabled. For example:

private void UpdatePreview(bool previewEnabled, Mesh meshResult, RhinoDoc doc)
{
   https://developer.rhino3d.com/api/RhinoCommon/html/T_Rhino_Display_DisplayConduit.htm
}

You can call this method from your RunCommand method whenever the preview state changes:

Preview = args.Preview;
UpdatePreview(Preview, meshResult, doc);

Now you just need to wire up the checkbox in your Eto form to update the preview state. You can do this by adding an event handler to the CheckedChanged event of the checkbox. For example:

var myCheckBox = new CheckBox { Text = "Enable Preview" };
myCheckBox.CheckedChanged += (sender, e) => {
    args.Preview = myCheckBox.Checked == true;
    UpdatePreview(args.Preview, meshResult, doc);
};

This will update the Preview property in your my_Args object whenever the checkbox is checked or unchecked, and call the UpdatePreview method to update the preview.

Finally, to ensure that your Eto form updates the preview state correctly when the form is shown, you should set the initial state of the checkbox based on the current value of the Preview property. You can do this by adding the following line after creating the checkbox:

myCheckBox.Checked = Preview;

With these changes, your Eto form should update the preview state correctly based on the checkbox, and you should be able to see the preview update in real-time as you check and uncheck the checkbox.

1 Like

Hi Farouk, first of all thank you very much for your prompt response. I managed to have a look today and tried to implement the changes you proposed but I bumped into some scoping issues.

If you have a look at my code, the method RunCommand lives in the class TestEtoCheckboxCommand : Command while the Eto form and the and UpdatePreview live in its own separate class internal class my_Dialog : Eto.Forms.Dialog. I am struggling to pass arguments like “MeshResult” between the two. If I define the method in the opposite class I still have issues passing the values between the two.

The method Update Preview does not declare the variable m_conduit or pass it so I am not sure if there is a part missing within the method were we search for existing DrawBlueMeshConduit classes in the active document or similar.

Could you please throw some light into these? Thank you very much for your time.

I am attaching below the code with the proposed changes.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

using Rhino;
using Rhino.Commands;
using Rhino.Geometry;
using Rhino.Input;
using Rhino.Input.Custom;

using Rhino.UI;
using System.Drawing;
using Rhino.Display;


namespace TestEtoCheckbox
{
    public class TestEtoCheckboxCommand : Command
    {
        public TestEtoCheckboxCommand()
        {
            // Rhino only creates one instance of each command class defined in a
            // plug-in, so it is safe to store a refence in a static property.
            Instance = this;
        }

        ///<summary>The only instance of this command.</summary>
        public static TestEtoCheckboxCommand Instance { get; private set; }

        ///<returns>The command name as it appears on the Rhino command line.</returns>
        public override string EnglishName => "TestEtoCheckboxCommand";

        public bool Preview { get; set; } = false;

        protected override Result RunCommand(RhinoDoc doc, RunMode mode)
        {
            #region 1.Selection

            // Perform Selection
            List<Rhino.DocObjects.ObjRef> my_selection = new List<Rhino.DocObjects.ObjRef>();

            //Set Filter
            bool isMeshCheck(Rhino.DocObjects.RhinoObject rhObject, GeometryBase geometry, ComponentIndex componentIndex)
            {
                bool is_Mesh = false;

                if (geometry.ObjectType == Rhino.DocObjects.ObjectType.Mesh)
                {

                    is_Mesh = true;
                }

                return is_Mesh;
            }

            var gm = new GetObject();
            gm.SetCommandPrompt("Select Meshes to Join");
            gm.SetCustomGeometryFilter(isMeshCheck);
            gm.GetMultiple(1, 0);
            gm.EnablePreSelect(true, false);
            gm.DeselectAllBeforePostSelect = false;

            if (gm.CommandResult() != Result.Success)
            {
                return gm.CommandResult();
            }

            else
            {
                for (int i = 0; i < gm.ObjectCount; i++)
                {
                    var tempGeo = gm.Object(i);
                    my_selection.Add(tempGeo);
                }

            }

            List<Mesh> MeshSelection = new List<Mesh>();
            foreach (var obj in my_selection)
            {
                if((obj.Geometry().ObjectType) == Rhino.DocObjects.ObjectType.Mesh)
                {
                    MeshSelection.Add(obj.Mesh());
                }
            }

            #endregion

            #region 2. Eto Form

            var args = new my_Args
            {
                Preview = Preview
            };

            var dlg = new my_Dialog(args);
            var res = dlg.ShowSemiModal(doc, Rhino.UI.RhinoEtoApp.MainWindow);

            if (res)
            {
                args = dlg.Results;

                Preview = args.Preview;


            }

            if (dlg.Result == false)
            {
                return Result.Cancel;
            }

            #endregion

            #region 3. Perform Boolean Union

            Mesh meshResult = new Mesh();

            if (MeshSelection != null)
            {
                if (MeshSelection.Count > 1)
                {
                    meshResult = Mesh.CreateBooleanUnion(MeshSelection)[0];
                }
                else
                {
                    meshResult = MeshSelection[0];
                }

            }

            doc.Objects.AddMesh(meshResult);

            #endregion

            #region 4. Preview Result

            //Setup Preview?

            Preview = args.Preview;
            UpdatePreview(Preview, meshResult, doc);

            #endregion
            // ---
            doc.Views.Redraw();
            return Result.Success;
        }


    }


    #region Define DisplayConduit

    class DrawBlueMeshConduit : DisplayConduit
    {
        readonly Mesh m_mesh;
        readonly Color m_color;
        readonly DisplayMaterial m_material;
        readonly BoundingBox m_bbox;

        public DrawBlueMeshConduit(Mesh mesh)
        {
            // set up as much data as possible so we do the minimum amount of work possible inside
            // the actual display code
            m_mesh = mesh;
            m_color = System.Drawing.Color.Blue;
            m_material = new DisplayMaterial();
            m_material.Diffuse = m_color;
            if (m_mesh != null && m_mesh.IsValid)
                m_bbox = m_mesh.GetBoundingBox(true);
        }

        // this is called every frame inside the drawing code so try to do as little as possible
        // in order to not degrade display speed. Don't create new objects if you don't have to as this
        // will incur an overhead on the heap and garbage collection.
        protected override void CalculateBoundingBox(CalculateBoundingBoxEventArgs e)
        {
            base.CalculateBoundingBox(e);
            // Since we are dynamically drawing geometry, we needed to override
            // CalculateBoundingBox. Otherwise, there is a good chance that our
            // dynamically drawing geometry would get clipped.

            // Union the mesh's bbox with the scene's bounding box
            e.IncludeBoundingBox(m_bbox);
        }

        protected override void PreDrawObjects(DrawEventArgs e)
        {
            base.PreDrawObjects(e);
            var vp = e.Display.Viewport;
            if (vp.DisplayMode.EnglishName.ToLower() == "wireframe")
                e.Display.DrawMeshWires(m_mesh, m_color);
            else
                e.Display.DrawMeshShaded(m_mesh, m_material);
        }
    }

    #endregion

    #region Define Eto Form
    //Define Eto Form
    internal class my_Args
    {
        public bool Preview { get; set; } = false;
    }
    internal class my_Dialog : Eto.Forms.Dialog<bool>
    {
        my_Args m_args;
        Eto.Forms.CheckBox m_Preview_checkbox;

        public my_Dialog(my_Args args)
        {
            m_args = args ?? throw new ArgumentNullException(nameof(args));

            Title = "Test";
            Padding = new Eto.Drawing.Padding(5);

            var layout = new Eto.Forms.DynamicLayout
            {
                Padding = new Eto.Drawing.Padding(5),
                Spacing = new Eto.Drawing.Size(5, 5)
            };

            layout.AddRow(null); // spacer
            layout.AddRow(CreateCheckBoxes());
            layout.AddRow(null); // spacer
            layout.AddRow(CreateButtons());

            Content = layout;
        }

        public my_Args Results => m_args;

        private Eto.Forms.DynamicLayout CreateCheckBoxes()
        {

            m_Preview_checkbox = new Eto.Forms.CheckBox
            {
                Text = "Preview",
                Checked = m_args.Preview,
                ThreeState = false
            };

            m_Preview_checkbox.CheckedChanged += ChangedValPreview;

            var layout = new Eto.Forms.DynamicLayout { Spacing = new Eto.Drawing.Size(5, 5) };
            layout.AddRow(m_Preview_checkbox);
            return layout;
        }

        private Eto.Forms.DynamicLayout CreateButtons()
        {
            DefaultButton = new Eto.Forms.Button { Text = "OK" };
            DefaultButton.Click += DefaultButton_Click;

            AbortButton = new Eto.Forms.Button { Text = "Cancel" };
            AbortButton.Click += AbortButton_Click;

            var layout = new Eto.Forms.DynamicLayout { Spacing = new Eto.Drawing.Size(5, 5) };
            layout.AddRow(null, DefaultButton, AbortButton, null);
            return layout;
        }

        private void DefaultButton_Click(object sender, EventArgs e)
        {
            m_args.Preview = (bool)m_Preview_checkbox.Checked;
            Close(true);
        }

        private void AbortButton_Click(object sender, EventArgs e)
        {
            Close(false);
        }

        private void ChangedValPreview(object sender, EventArgs e)
        {
            RhinoApp.WriteLine("Value has changed");
            m_args.Preview = m_Preview_checkbox.Checked == true;
            UpdatePreview(m_args.Preview, meshResult, RhinoDoc.ActiveDoc);
        }

        private void UpdatePreview(bool previewEnabled, Mesh meshResult, RhinoDoc doc)
        {
            // Remove previous conduit if there is one
            doc.Views.Redraw();
            var m_conduit = new DrawBlueMeshConduit(meshResult);
            if (m_conduit != null)
            {
                m_conduit.Enabled = false;
                m_conduit = null;
            }

            if (previewEnabled)
            {
                // Create and enable a new conduit
                m_conduit = new DrawBlueMeshConduit(meshResult);
                m_conduit.Enabled = true;
            }

            doc.Views.Redraw();
        }
    }

    #endregion
}

Best,

Alfredo

This is how I display a preview adapt it to your case

  public class CsDrawBrepConduit : Rhino.Display.DisplayConduit
        {
            public Rhino.Geometry.Brep Brep { get; set; }

            protected override void CalculateBoundingBox(Rhino.Display.CalculateBoundingBoxEventArgs e)
            {
                if (null != Brep)
                {
                    e.IncludeBoundingBox(Brep.GetBoundingBox(false));
                }
            }

            protected override void PostDrawObjects(Rhino.Display.DrawEventArgs e)
            {
                if (null != Brep)
                {
                    Rhino.Display.DisplayMaterial material = new Rhino.Display.DisplayMaterial();
                    material.IsTwoSided = true;
                    material.Diffuse = System.Drawing.Color.DeepPink;
                    material.BackDiffuse = System.Drawing.Color.HotPink;
                    e.Display.EnableLighting(true);
                    e.Display.DrawBrepShaded(Brep, material);
                    e.Display.DrawBrepWires(Brep, System.Drawing.Color.Black);
                }
            }
        }

To display it


 CsDrawBrepConduit conduit = new CsDrawBrepConduit();
                conduit.Brep = preview.First();
                conduit.Enabled = true;
                doc.Views.Redraw();

To turn the conduit off simply change the attr enabled to False
You can see more at > DisplayConduit Class

1 Like

Thanks again Farouk, I will give it a go following your latest example!

Best,

Alfredo