Display Glitch when using RhinoDoc.SelectedObjects event handlers in C# component

As promised @dale

I noticed that the Live mode of the Select Objects in Human (@andheum ) has a new display glitch when activated and selected objects are dragged around. This appeared some time in Rhino 6, not sure which service release.

I know his component was written a long time ago, so I wrote a component myself to see if I could debug. I tried removing the objects themselves from the outputs to see if it had to do with previewing referenced geometry in the component.

Unfortunately, I’m having the same issue (see attached). De-activating the component returns previews to normal.

Is there a bug or is there some kind of preview override I need to implement to avoid this? It causes a fairly significant UX issue for my tools and I really need live object selection.

Thanks!

Marc

Hi @marcsyp,

If you turn of the Gumball, do you still see the same display glitch?

– Dale

Yes – still happens with gumball off and regardless of whether I use the click and drag or the “_Move” command.

Also happens in every viewport regardless of DisplayMode. In Perspective, it even sometimes leaves a ghosted remnant until a viewport change happens.

Final note, I notice that the ghosted glitch is proportional to the distance moved (a ghosted geometry appears exactly 2X the distance of the move itself and then snaps back, sometimes leaving a ghost in rendered mode).

Marc

FWIW this is the source of the component in question… I wrote it a very long time ago and probably did bad and dumb things :slight_smile:

using System;
using System.Collections.Generic;

using Grasshopper.Kernel;
using Rhino.Geometry;
using Grasshopper.Kernel.Types;
using Rhino.DocObjects;
using System.Windows.Forms;
using GH_IO.Serialization;

namespace Human
{
    public class SelectedObjects : GH_Component
    {

        private bool liveMode;
        /// <summary>
        /// Initializes a new instance of the SelectedObjects class.
        /// </summary>
        public SelectedObjects()
            : base("Objects By Selection", "SelObj",
                "Gets the currently selected objects in Rhino",
                "Human", "Reference")
        {
            liveMode = false;

        }


        void UpdateMessage()
        {
            if (liveMode)
            {
                Message = "Live";
            }
            else
            {
                Message = "";
            }
        }

        /// <summary>
        /// Registers all the input parameters for this component.
        /// </summary>
        protected override void RegisterInputParams(GH_InputParamManager pManager)
        {
            pManager.AddBooleanParameter("Reset", "reset", "Set this value to true in order to get the currently selected geometry in Rhino.",GH_ParamAccess.item);
        }

        /// <summary>
        /// Registers all the output parameters for this component.
        /// </summary>
        protected override void RegisterOutputParams(GH_OutputParamManager pManager)
        {
            pManager.AddGenericParameter("Selected Geometry", "G", "The Rhino Geometry currently selected.", GH_ParamAccess.item);
        }

        /// <summary>
        /// This is the method that actually does the work.
        /// </summary>
        /// <param name="DA">The DA object is used to retrieve from inputs and store in outputs.</param>
        protected override void SolveInstance(IGH_DataAccess DA)
        {
            bool run = false;
            DA.GetData(0, ref run);

            if (run)
            {
                Rhino.RhinoDoc doc = Rhino.RhinoDoc.ActiveDoc;
                List<RhinoObject> objs = new List<RhinoObject>();
                objs.AddRange(doc.Objects.GetSelectedObjects(true, false));
                List<object> objsOut = new List<object>();
                foreach (RhinoObject ro in objs)
                {
                    objsOut.Add(TryGetGoo(ro));
                }

                DA.SetDataList(0, (objsOut));

            }
            else
            {
                DA.SetDataList(0, null);

            }
            Rhino.RhinoDoc.SelectObjects -= ExpireThis;
            Rhino.RhinoDoc.DeselectObjects -= ExpireThis;
            Rhino.RhinoDoc.DeselectAllObjects -= ExpireThis;
            if (liveMode && run)
            {
                Rhino.RhinoDoc.SelectObjects += ExpireThis;
                Rhino.RhinoDoc.DeselectObjects += ExpireThis;
                Rhino.RhinoDoc.DeselectAllObjects += ExpireThis;

            }

        }

        /// <summary>
        /// Provides an Icon for the component.
        /// </summary>
        protected override System.Drawing.Bitmap Icon
        {
            get
            {
                //You can add image files to your project resources and access them like this:
                // return Resources.IconForThisComponent;
                return Properties.Resources.GetSelected;
            }
        }

        /// <summary>
        /// Gets the unique ID for this component. Do not change this ID after release.
        /// </summary>
        public override Guid ComponentGuid
        {
            get { return new Guid("{778D4CA4-6352-4447-9D80-51228BED5C2D}"); }
        }


        void ExpireThis(object sender, RhinoObjectSelectionEventArgs e)
        {
            ExpireSolution(true);
        }

        void ExpireThis(object sender, RhinoDeselectAllObjectsEventArgs e)
        {
            ExpireSolution(true);
        }

     

        protected override void AppendAdditionalComponentMenuItems(ToolStripDropDown menu)
        {
            Menu_AppendItem(menu, "Live Mode", LiveModeClicked, true, liveMode);

            base.AppendAdditionalComponentMenuItems(menu);
        }

        private void LiveModeClicked(object sender, EventArgs e)
        {
            liveMode = !liveMode;
            UpdateMessage();
            ExpireSolution(true);

        }

        public override bool Write(GH_IWriter writer)
        {
            writer.SetBoolean("LiveMode", liveMode);
            return base.Write(writer);
        }

        public override bool Read(GH_IReader reader)
        {
            bool _live = false;
            reader.TryGetBoolean("LiveMode", ref _live);
            liveMode = _live;
            UpdateMessage();
            return base.Read(reader);
        }

        private static object TryGetGoo(RhinoObject obj)
        {
            IGH_GeometricGoo goo = GH_Convert.ToGeometricGoo(obj.Id);
            if (goo != null)
            {
                return goo;
            }
            else // goo conversion was null
            {
                try
                {
                    return obj;
                }
                catch (Exception e)
                {
                    return null;
                }
            }
        }


    }
}
1 Like

Hi @marcsyp,

Do you see the same anomaly of the gumball is disabled? How about selecting objects just using Rhino? How can I repeat what you are seeing?

– Dale

Is disabled different than “off”?

To repeat you should be able to open this GH file (Human installed) and simply make an object and drag it around.

displayGlitch-liveSelection.gh (4.2 KB)

If I had to hazard a guess, what I think is happening is that there’s a process by which a temporary transform (from in-command preview) gets applied to an object and then officially committed to the object. Somehow the temporary transform is getting applied for a split second to the object after it is committed to the object… hence the doubling. You can see this even if you scale the object; the object will scale double for a split second before the correct change takes effect. The involvement of Human is simply that it inserts a slow action somewhere in this temporary-to-permanent transform process (all those nasty event listeners attached to the select events). I suspect this slow action makes the effect visible when otherwise it would be unnoticeable.

I was thinking something similar but wasn’t quite able to articulate it like you have here Andrew – as a workround, I’m wondering if using a ScheduleSolution within the event listener might allow the temporary transform to finish its business before the “slow action” interferes with it. Might try it in a couple hours…

Marc

1 Like

Solved it using a ScheduleSolution approach. Incidentally, moving objects causes a massive number of events. Window selecting is great, it’s all bundled up in one event. When you have 5 objects selected and you move them, you trigger 5 Selected and 5 Unselected events. This causes major pain when you are expiring SolveInstance in the GH component. By using ScheduleSolution, you basically get a built in debouncer that prevents the display glitch and also only results in one downstream execution of the result. The old version of the Human component causes a wave of recompute downstream and sometimes crashes hard on even a small number of objects. Lucky for us @andheum, this is a quick fix by adding a schedulesolution callback that grabs the currently selected objects from the activedoc. I’ve chosen to order objects by guid before passing out because the order of the list is randomized and can change after dragging.

Anyway, happy to have this one tied up! Keep us posted @dale if any changes are imminent. Thanks!

Marc

cool - when I get around to it I’ll fold this into Human. Thanks for the solution!

1 Like

Cool – I’ve got my own solution in the meantime.

Incidentally, I set the callback delay to 100ms because occasionally you can get a crash at 20-30ms or so for trying to read protected memory from all these events firing. 100ms seems pretty stable but I was able to get it to crash by click-dragging the gumball as fast as possible like a crazy person…

Marc

nobody finds ways to crash rhino doing unholy shit quite like you do :joy:

1 Like