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.
Tags:
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?
It is possible to get access to the entire IGH_Structure stored inside any input parameter from within SolveInstance(). You can use the Params object on GH_Component to get at any input or output param:
Kernel.Data.IGH_Structure tree = Params.Input[0].VolatileData;
If you need to know exactly where in the IGH_Structure the native iteration currently is, it will be a bit trickier. The implementation of the IGH_DataAccess interface is internal and you cannot get a hold of it. You'll probably need to ask the DA instance for actual GH_Number rather than double, so you can then use the == operator on the class instances. But this is getting quite complex already and I'm not sure it's what you are after.
--
David Rutten
david@mcneel.com
Poprad, Slovakia
Nice!
It's good to know we can get at the inputs and outputs through the Params object within SolveInstance().
Thanks again,
Ben
Hi Ben, here's my 2c for what it's worth.
For my plugins, I leave the parameter combination abstract and leave the combinations to David and just accept the solveInstance method as it's executed.
If you need a counter that is incremented each solution, then use a shared variable, and reset it when the component expires.
Here's my result for the attached changes. Hope it helps,
Jon
Thanks for the advice, Jon. Much obliged.
Cheers,
BH
What if I have two (or more) component instances that need to use the same counter variable to populate a shared dictionary within my namespace? In this example the dictionary would be something like myDictionary<int, AddTwoNumbers>, and the shared counter variable should be used to generate unique keys.
It seems that if I reset the counter variable every time a component calls ClearData(), I'll end up overwriting values with the same key, some of which may have been generated by other components.
I'm thinking of using this.InstanceGuid as a way to build unique keys for each component instance ... but if there is a cleaner way I'd love to know. Thanks in advance!
-Ben
Welcome to
Grasshopper
Added by Parametric House 0 Comments 0 Likes
Added by Parametric House 0 Comments 0 Likes
Added by Parametric House 0 Comments 0 Likes
Added by Parametric House 0 Comments 0 Likes
Added by Parametric House 0 Comments 0 Likes
© 2024 Created by Scott Davidson. Powered by