SetDisplayModeOverride component is flaky

I have a component that is designed to allow users to set a custom DisplayModeOverride on an object by object basis. It works – sort of. The thing that is making it harder for me to track down: it ALWAYS works when I am debugging a live session in Visual Studio. But in the wild, it occasionally just plain fails. Stops working entirely.

I have some code in here that is creating a registry for already set DisplayModeOverride (since I haven’t found a method that returns an objects existing override so I can do a name check) – but I had the same problems before I implemented the registry. When the component “stops working”, I get no errors, I get a “true” result from my status output, but I get no overrides. I can add them manually in Rhino but GH won’t do it for me.

Any thoughts? Code follows:

        public ObjectTools_SetObjectDisplayMode()
      : base("Set Object DisplayMode", "SetDisplay",
          "Override the displayMode of individual objects.  Useful for allowing control objects to be in Wireframe mode within shaded viewports.",
          "Squirrel", "Object Tools")
    {
    }

    /// <summary>
    /// Registers all the input parameters for this component.
    /// </summary>
    protected override void RegisterInputParams(GH_Component.GH_InputParamManager pManager)
    {
        pManager.AddGeometryParameter("Object", "O", "Object to fetch.", GH_ParamAccess.item);
        pManager.AddTextParameter("DisplayMode", "D", "DisplayMode to assign to the object.", GH_ParamAccess.item);
        pManager.AddTextParameter("Viewports", "V", "Viewport contraints for the DisplayMode (optional).", GH_ParamAccess.list);
        pManager[1].Optional = true;
        pManager[2].Optional = true;
    }

    /// <summary>
    /// Registers all the output parameters for this component.
    /// </summary>
    protected override void RegisterOutputParams(GH_Component.GH_OutputParamManager pManager)
    {
        pManager.AddBooleanParameter("Status", "St", "Boolean value that indicates whether the object's DisplayMode was successfully set.", GH_ParamAccess.item);
    }


    bool result = false;
    Rhino.RhinoDoc doc = Rhino.RhinoDoc.ActiveDoc;

    protected override void AfterSolveInstance()
    {
        if (result) doc.Views.Redraw(); // redraw viewports if we made any changes
    }

    // Set up a mode registry to prevent modifying objects with the same displayMode over and over (to prevent infinite loops)
    public Dictionary<string, string> modeRegistry = new Dictionary<string, string>();
    
    /// <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)
    {
       

        Grasshopper.Kernel.Types.IGH_GeometricGoo geo = null;
        GeometryBase geoBase = null;
        String dMode = "";
        List<String> viewports = new List<string>(); 

        Guid id;

        if (!DA.GetData(0, ref geo)) return;
        if (!DA.GetData(0, ref geoBase)) return;

        DA.GetData(1, ref dMode);
        DA.GetDataList<string>(2, viewports);




        if (geo.IsReferencedGeometry)
        {
            id = geo.ReferenceID;


            RhinoObject rObj = doc.Objects.Find(id);
            if (rObj == null)
            {
                
            } else
            {
                Rhino.Display.DisplayModeDescription dmDesc = null;
                if (dMode != "")
                {
                    dmDesc = Rhino.Display.DisplayModeDescription.FindByName(dMode);
                    if (dmDesc == null)
                    {
                        AddRuntimeMessage(GH_RuntimeMessageLevel.Warning, String.Format("{0} DisplayMode not found.", dMode));
                        return;
                    }

                }
                {

                    if (viewports.Count == 0)
                    {
                        ObjectAttributes attr = rObj.Attributes;

                        string modeKey = id.ToString() + "_" + "General";

                        if (dmDesc == null)
                        {
                            attr.RemoveDisplayModeOverride();
                        } else
                        {
                            if (!(modeRegistry.ContainsKey(modeKey) && modeRegistry[modeKey] == dmDesc.EnglishName))
                            {
                                
                                attr.SetDisplayModeOverride(dmDesc);

                                result = doc.Objects.ModifyAttributes(rObj, attr, false);
                                rObj.CommitChanges();

                                if (modeRegistry.ContainsKey(modeKey))
                                {
                                    modeRegistry[modeKey] = dmDesc.EnglishName;
                                } else
                                {
                                    modeRegistry.Add(modeKey, dmDesc.EnglishName);
                                }
                                
                            }

                        }

                    
                        
                    } else
                    {
                        //List<Rhino.Display.RhinoView> vps = doc.Views.GetViewList(true, true);
                        //Rhino.Display.RhinoView[] vps = doc.Views.GetViewList(true, true);
                        //vps[0].

                        var selected_views = doc.Views
                            .Where(v => viewports.Contains(v.ActiveViewport.Name))
                            .ToList();

                        foreach (Rhino.Display.RhinoView view in selected_views)
                        {
                            Guid viewportId = view.ActiveViewportID;
                            string viewStringId = view.ActiveViewportID.ToString();

                            ObjectAttributes attr = rObj.Attributes;

                            string modeKey = id.ToString() + "_" + viewportId.ToString();

                            if (attr.HasDisplayModeOverride(viewportId))
                            {
                                attr.RemoveDisplayModeOverride(viewportId); // remove any existing overrides on this viewport
                            }
                            if (dmDesc != null)
                            {
                                if (!(modeRegistry.ContainsKey(modeKey) && modeRegistry[modeKey] == dmDesc.EnglishName))
                                {

                                    attr.SetDisplayModeOverride(dmDesc, viewportId);
                                    bool success = false;
                                    success = doc.Objects.ModifyAttributes(rObj, attr, false);
                                    rObj.CommitChanges();
                                    if (success) result = true;

                                    if (modeRegistry.ContainsKey(modeKey))
                                    {
                                        modeRegistry[modeKey] = dmDesc.EnglishName;
                                    }
                                    else
                                    {
                                        modeRegistry.Add(modeKey, dmDesc.EnglishName);
                                    }

                                }

                            }

                            


                        }
                    }
                }

            }
        } else
        {

        }

        DA.SetData(0, result);

    }

This looks suspicious. The active doc will change if you open or create a new file.
I’d at least refresh this variable using BeforesolveInstance().

1 Like

Eh, after a solution the viewports will already redraw. There shouldn’t be a need to trigger a specific redraw once your component is done.

This was totally it! Duh.

Thank you thank you.

Marc

I’ve been looking at the code (and rewriting it so I understand it better). I’m not really sure why you need the dictionary of cached values. The main problem I did see is that there’s no way to tell whether object attributes already have a specific display override. This means you pretty much have to modify them each time, causing all sorts of never ending processes.

I’m guessing the dictionary is a way around this, but it sounds like we need to expose some more functionality in RhinoCommon.

I created the dictionary specifically because yes, I couldn’t find any way to understand whether the object already had the specified Override – and because I’m using Human’s layer listener (with attribute listening enabled) to update objects and feed them to this component, I immediately have an infinite loop scenario on my hands.

Now I could simply turn off attribute listening on the feed for the input – but I wanted to provide a safeguard for my users who may not understand the intricacies. So I’m creating my own little registry that simply updates which Overrides have been added. That said, it has no document reference and the overrides are not yet removed from the registry properly, so it’s going to be a real mess inside after having it running for a while. But it gets wiped clean on each instantiation and it’s working so far, so I’m going to leave it in there for now. Although I’m sure you have a better idea…?

Cheers,
Marc

I was thinking you could set a UserString on the attributes yourself. It would be stale in case someone modified the display override by hand, but otherwise it would persist and always be part of the object. At any rate, you still have that problem now and there’s no way to properly handle it unless we provide a way for you to test whether a specific display mode override has been assigned, either in general or for a specific viewport.

Aha! That’s a great idea.

I might implement that, since my users are very unlikely to set the override by hand…or even know that it exists. That said – I would love to have a way to check the existing override assignment to be more robust (and reduce extra overhead). It’s odd that there is a method to check whether an override exists, but no method to retrieve the DMDescription…

/shrug

Marc