Properly override GH_ActiveObject.RegisterRemoteIDs method

I’m actually resurrecting an old thread from the Legacy forums here…

https://www.grasshopper3d.com/forum/topics/properly-override-gh-activeobject-registerremoteids-method?page=1&commentId=2985220%3AComment%3A1988863&x=1#2985220Comment1988863

I posted there but I doubt many people (if any) are checking those forums for updates.

I’m reposting my response here:

"I’ve tried to boil down what I’m trying to do into the silliest little component, just to test some concepts that might become part of a bigger UI workflow. The component is very dumb. It has one input for one a single point. If this parameter isn’t assigned to, the component makes a point at the origin and assigns this point as persistent data to the point parameter input. It also registers the point’s guid by overriding RegisterRemoteIds. The user should then be able to move the point around in the rhino space and the component should update according to the new point position. Script attached below.

I realize it sounds a bit nutty to be doing this but the reasons to try this are to see if I can generate a workflow where running a script automatically bakes elements into Rhino that the user can easily modify without having to manually reference into Grasshopper. "

SimpleMakePointIfNotAssigned.cs (3.2 KB)

You shouldn’t have to do that. If your input is a point parameter it will handle the Rhino object live link when you set persistent data which is a referenced point.

However what you will have to do is find some other place to check for an empty parameter and set up this origin point. I’ll be in the office in a few hours and can run your code then.

How about this:

using System;
using Grasshopper.Kernel;
using Grasshopper.Kernel.Data;
using Grasshopper.Kernel.Parameters;
using Grasshopper.Kernel.Types;
using Rhino.Geometry;

namespace TestComponent
{
  public class TestComponent : GH_Component
  {
    public TestComponent()
    : base("Point Test", "PTest", "Test component for automatic input point referencing", "Test", "Test")
    { }
    public override Guid ComponentGuid
    {
      get { return new Guid("{77B9E083-4F54-4218-9BB2-7C161A0B2B01}"); }
    }

    #region the usual
    protected override void RegisterInputParams(GH_InputParamManager pManager)
    {
      pManager.AddPointParameter("Point", "P", "Referenced point", GH_ParamAccess.item);
    }
    protected override void RegisterOutputParams(GH_OutputParamManager pManager)
    {
      pManager.AddCircleParameter("Circle", "C", "Resulting circle", GH_ParamAccess.item);
    }
    protected override void SolveInstance(IGH_DataAccess access)
    {
      var point = Point3d.Unset;
      if (!access.GetData(0, ref point)) return;
      if (!point.IsValid) return;

      access.SetData(0, new Circle(point, 2));
    }
    #endregion

    #region automatic point referencing
    public override void AddedToDocument(GH_Document document)
    {
      base.AddedToDocument(document);
      document.SolutionStart+= SolutionStartHandler;
    }
    public override void RemovedFromDocument(GH_Document document)
    {
      base.RemovedFromDocument(document);
      document.SolutionStart -= SolutionStartHandler;
    }
    private void SolutionStartHandler(object sender, GH_SolutionEventArgs e)
    {
      var param = Params.Input[0];
      if (param.SourceCount > 0) return;
      if (!param.VolatileData.IsEmpty) return;

      if (param is Param_Point pointParam)
        if (pointParam.PersistentData.IsEmpty)
        {
          var pointObj = Rhino.RhinoDoc.ActiveDoc.Objects.AddPoint(0, 0, 0);
          var pointGoo = new GH_Point(pointObj);
          pointParam.PersistentData.Append(pointGoo, new GH_Path(0));
          e.Document.ClearReferenceTable();
        }
    }
    #endregion
  }
}

Thanks/awesome! I think understanding this will prove very useful as I begin to apply this to more complicated components that require UI feedback in the Rhino viewport (intermediate points in the script where the user manipulates auto-generated baked geometry that is fed back into the script). I have some follow-up questions so that I can understand this a bit better as things get more complex.

It is interesting that performing the object referencing inside the SolutionStartHandler causes the component to know when the point has been moved. My basic understanding/inference is this: that we add the method SolutionStartHandler to the SolutionStart event that is called whenever an update is required on the GH script, which means that…SolutionStartHandler is called on every solution update event…yes? However I also see that if the point has been already referenced, then we just return anyway. Yet, somehow GH knows that the param_Point has been updated and updates the input to our component accordingly.

I also note that if reference the point within SolveInstance, omitting the event handling region of your code, and bringing these lines up into SolveInstance (I know this is what you said NOT to do, but I’m just testing it here to understand better):

  protected override void SolveInstance(IGH_DataAccess DA)
    {
        GH_Point gH_Point = null;
        DA.GetData(0, ref gH_Point);

        bool unassigned = false;
        if (gH_Point == null)
        {
            unassigned = true;
        }

        var param_Point = (Grasshopper.Kernel.Parameters.Param_Point)Params.Input[0];

        if (unassigned)
        {
            var pointObj = Rhino.RhinoDoc.ActiveDoc.Objects.AddPoint(0, 0, 0);
            var pointGoo = new GH_Point(pointObj);
            param_Point.PersistentData.Append(pointGoo, new GH_Path(0));
        }
    }

The point is referenced to the component but the component only updates the point information when it is disabled/enabled again.

I’m not sure if I am fully grasping how your solution, which works beautifully and is more concise than mine, enables the component to “know” it should update its input data whenever we change the point in the model, because SolutionStartHandler would appear to simply return without having done anything if the point is already assigned. It would seem that in both my example and yours we are assigning persistent data to the input of the component in more or less the same way. But I do see, that based on the component’s behavior, there is a substantial difference here…which perhaps you can help me understand a bit better.

Oh, nevermind, I think I’ve figured this out. I wasn’t calling ClearReferenceTable() for the GH document. Now things work as expected using this code here:

    protected override void SolveInstance(IGH_DataAccess DA)
    {
        GH_Point gH_Point = null;
        DA.GetData(0, ref gH_Point);

        bool unassigned = false;
        if (gH_Point == null)
        {
            unassigned = true;
        }

        var param_Point = (Grasshopper.Kernel.Parameters.Param_Point)Params.Input[0];

        if (unassigned)
        {
            var pointObj = Rhino.RhinoDoc.ActiveDoc.Objects.AddPoint(0, 0, 0);
            var pointGoo = new GH_Point(pointObj);
            param_Point.PersistentData.Append(pointGoo, new GH_Path(0));
            OnPingDocument().ClearReferenceTable();
            param_Point.ExpireSolution(true);
        }
    }

The reason I am trying to make it possible to assign the points during the solve instance is that there will be times when we have to reference new points that don’t just occur the first time the user places the component into the script (intermediate times during interaction when user wants to reset points to default positions, for example).

Anyway, thanks for the help! This is going to be super-useful.

1 Like