Grasshopper

algorithmic modeling for Rhino

Hi all,

 

Long time listener, first time caller.  I'd like to know more about GH's iteration options in compiled components; specifically, how to take advantage of the default iteration options (longest list, shortest list, cross reference) in cases where the number of iterations has to be kept track of and used in the code.

 

I am developing a number of components that instantiate objects of a custom class type (which I define), and add the object's parameters to a database for use in other applications.  In almost all cases my classes have an ID parameter, which I typically set in the SolveInstance method using an int variable which is incremented after each object's instantiation.  I'd like to be able to access or keep track of the number of iterations without having to register my params as GH_ParamAccess.tree, and looping over one of the input trees.

 

I've included a simple example to illustrate the point.  I define a simple class that takes two numbers as inputs, adds them, and includes an ID attribute:

 

public class AddTwoNumbers
    {
        //Basic addition parameters
        public string ID = "not set";
        public double A;
        public double B;
        public double C;

        //Addition method
        public void Add()
        {
            C = A + B;
        }

        //Constructor
        public AddTwoNumbers(double a, double b)
        {
            A = a;
            B = b;
            this.Add();
        }
    }

 

 

If I do not register my param access as .tree, I am not able to increment the ID each time through the SolveInstance.  Here are my SolveInstance and RegisterInputParams methods using GH's iteration:

 

protected override void RegisterInputParams(GH_Component.GH_InputParamManager pManager)
        {
            pManager.Register_DoubleParam("Double A", "A", "First number to add.");
            pManager.Register_DoubleParam("Double B", "B", "Second number to add.");
        }

protected override void SolveInstance(IGH_DataAccess DA)
        {
            //local variables to catch incoming data
            double myA = 0.0;
            double myB = 0.0;

            //assign incoming data to local variables
            if (!DA.GetData(0, ref myA)) { return; }
            if (!DA.GetData(1, ref myB)) { return; }

            //instantiate a new AddTwoNumbers object
            AddTwoNumbers myAdd = new AddTwoNumbers(myA, myB);

            //give this object an ID
            //   ???  how do we assign a numerical ID if we don't have access to an iterator?

            //add the object to a static list in this namespace - not implemented here for simplicity's sake

            //a string to report all of myAdd's parameters
            string myParams = myAdd.ID + ", " + myAdd.A.ToString() + ", " + myAdd.B.ToString() + ", " + myAdd.C.ToString();

            //set output data
            DA.SetData(0, myParams);
        }

and a screenshot of the component in action:

If I do register my params as .tree, I am able to increment an ID variable each time through my nested for loop, but I'd have to do a lot of work to account for all of the possible input scenarios (an item and a list, 2 lists of different lengths, a list and a tree, etc.).  Here are my SolveInstance and RegisterOutputParams methods, minus all of the defense, and a screenshot of this component:

 

protected override void RegisterInputParams(GH_Component.GH_InputParamManager pManager)
        {
            pManager.Register_DoubleParam("Double A", "A", "First number to add.", GH_ParamAccess.tree);
            pManager.Register_DoubleParam("Double B", "B", "Second number to add.", GH_ParamAccess.tree);
        }

protected override void SolveInstance(IGH_DataAccess DA)
        {
            //local variables to catch incoming data
            GH_Structure<GH_Number> myATree = new GH_Structure<GH_Number>();
            GH_Structure<GH_Number> myBTree = new GH_Structure<GH_Number>();

            //An output data tree
            DataTree<string> myOutTree = new DataTree<string>();

            //An iterator counter
            int IteratorCounter = 0;


            //pass incoming data into local variables
            if (!DA.GetDataTree(0, out myATree)) { return; }
            if (!DA.GetDataTree(1, out myBTree)) { return; }

            //pick a data tree to loop over - since we don't know which tree is larger, we'd have to
            //do a bunch of defensive programming here.  In this case we'll just loop over the A tree
            for (int i = 0; i < myATree.Branches.Count; i++)
            {
                //set the output path = to the incoming path
                GH_Path myOutPath = new GH_Path(myATree.Paths[i]);

                //loop over each item in the branch
                for (int j = 0; j < myATree.Branches[i].Count; j++)
                {
                    //instantiate a new AddTwoNumbers object
                    AddTwoNumbers myAdd = new AddTwoNumbers(myATree.Branches[i][j].Value, myBTree.Branches[i][j].Value);
                        //here we assume that that the Btree will be of identical structure ... of course this might not be the case
                        //again, we could do a bunch of defense here, but we'd like to be able to use GH's iteration.

                    //give this object an ID
                    myAdd.ID = IteratorCounter.ToString();
                        //now since we have access to a counter, we can assign IDs using that counter.  Since the counter is
                        //incremented each time through the loop, we know we'll never get a duplicate ID.

                    //add the object to a static list in this namespace - not implemented here for simplicity's sake

                    //a string to report all of myAdd's parameters
                    string myParams = myAdd.ID + ", " + myAdd.A.ToString() + ", " + myAdd.B.ToString() + ", " + myAdd.C.ToString();

                    //add the string to the out tree
                    myOutTree.Add(myParams, myOutPath);

                    //increment the counter
                    IteratorCounter++;
                }
            }

 

 

I think this is a specific breed of a more general question: what are the scenarios under which GH's built in iteration will not suffice?  When do you need to specify the GH_ParamAccess and take control of the iteration?  Another example: sometimes you need to access and use branch paths in a component's code ... can you do this without setting the access to .tree?

 

Any help would be greatly appreciated.  Thanks!

 

PS - I included a .zip of my visual studio 2010 project, in case anyone would like to take a closer look.

 

Views: 3045

Attachments:

Replies to This Discussion

Hi Ben,

would the DA.Iteration property help out here? It will be zero the first time SolveInstance is called during a specific solution and one higher every other time. 

I'm still reading the rest of your post...

--

David Rutten

david@mcneel.com

Poprad, Slovakia

Hi David,

DA.Iteration is exactly what I was looking for.  Thank you!

If you could shed some light on the more general question at the end, it would be very helpful: When do you need to specify the GH_ParamAccess and take control of the iteration? 

I assume the answer is something like 'whenever you can avoid it', but it feels like I'm missing something here ... I've typically been using GH_ParamAccess.tree whenever I know I'm going to be dealing with Data trees (especially when I need to mess with the output tree's path structure), and ending up with components that have a pretty narrow scope.

I don't have a rule really, basically the only components that use trees directly actually operate on the tree structure. 'whenever you can avoid it' sounds good to me.

Sorry for not being much help here...


--

David Rutten

david@mcneel.com

Poprad, Slovakia

That is exactly what I was looking for.  I'll try to avoid .tree in the future whenever possible.

Thanks again, David. 

Hi David,

is there a way to detect the last call to SolveInstance during an iteration?

Best,

Clemens

Yes, GH_Document has a readonly property called SolutionHistory which returns a list of GH_SolutionSpan objects (at most 1000 though, the last one being the most recent one). These span object record both the start and end-time of a solution, if you're inside a solution there is no span associated with it yet as the end-time is not known yet.

--

David Rutten

david@mcneel.com

Poprad, Slovakia

Sorry, I was imprecise. I did not mean the last call to SolveInstance in the definition but during Data Matching Resolution for a custom component.

What I am looking for could be the end-value of DA.Iteration.

I would like to use it for e.g. summing up the user input in one status message

Ah, there's no such thing unfortunately. Grasshopper doesn't know it's done until it's done and then it's already too late to call into SolveInstance one more time. I can add a method that can be overridden which is called after the last call to SolveInstance, would that help?

 

In the meantime, you may have to handle the SolutionEnd event on GH_Document for messaging purposes. Where is this status message displayed btw.?

 

--

David Rutten

david@mcneel.com

Poprad, Slovakia

The status message is displayed by the Karamba-components that generate load-objects: Until now I had an output at each of them that showed the users the sum of loads (for error checking). It was implemented using a custom Data Matching Resolution inside the components.

If the method that is called after the last SolveInstance has access to the value of the output-plugs it would solve the issue.

Once the output parameters have been assigned, you can always access the data via Me.Params.Output(i).VolatileData.etc.etc.etc. so that shouldn't be a problem.

I'll think about the best way to add overridable methods to be called before and after all calls to SolveInstance.

--

David Rutten

david@mcneel.com

Poprad, Slovakia

Thank you very much.

I added BeforeSolveInstance() and AfterSolveInstance() methods which can be overridden. AfterSolveInstance is called after the post-processes have been applied to outputs (i.e. grafting, flattening, expressions, reversing).

--

David Rutten

david@mcneel.com

Poprad, Slovakia

RSS

About

Translate

Search

Videos

  • Add Videos
  • View All

© 2024   Created by Scott Davidson.   Powered by

Badges  |  Report an Issue  |  Terms of Service