C# for Grasshopper, Reference, 3rd ed.

Page 1

C#

Basic C# for GH3d Reference in a Nutshell

3rd edition author: Krzysztof Nazar contact: krzysztof.nazar@pw.edu.pl



C#

Basic C# for GH3d Reference in a Nutshell

2nd edition author: Krzysztof Nazar contact: krzysztof.nazar@pw.edu.pl

1. C# scripting component

C# scripting component can be found in Math tab in Script section. You can place it on the canvas also by double-clicking (or pressing space bar in Rhino 6) and typing „C#”. By default the component has ‘x’ and ‘y’ inputs and ‘out’ and ‘A’ outputs. „out” is a special output, which prints text messages about errors, warnings and script behaviour. Names of the inputs and outputs (excluding „out”) become names of the variables inside a script. This means you have to follow C# naming rules when you rename them. Pluses and minuses appear after zooming in, and they allow for adding and removing inputs and outputs in case you need more.

toggle script editor shrinking run script

variable names


Before you start scripting you have to specify input types by right clicking on the component. In addition, you have three ways of providing data (item, list, data tree). Visit http://4.rhino3d.com/5/rhinocommon/ for full reference of RhinoCommon types. Note: outputs types are always set to generic „object” type. the most generic base class

Note: Actually you don't need to specify input types and ways of providing data. Sometimes it's even better (faster!) to leave inputs as generic "object" and to check types dynamically inside the component, but it's an advanced technique, not covered in this handout.

RhinoCommon structures RhinoCommon classes

ways of providing data

2. Declaring variables In C# you declare a variable by providing type followed by variables name. The rules of naming are: – names can’t be any of the reserved keywords, – they have to start with a letter or an underscore, – they can contain letters, underscores and numbers, – no whitespaces! A declared variable is a container for a certain type of data. Assigning a value for the first time is called initialization. data_type data_type data_type data_type

variable_name; variable_name = value; variable1, variable2, variable3; variable1 = value, variable2 = value;

3. Data types

Tips: 1. Names should be as descriptive as possible. „agentPosition” is good, „my_variable321” is bad. 2. „Camel case” is a widely accepted naming convention in C#. The rule is that you start with lowercase and separate words with uppercase letters: eg. „decreasedAgentSpeed”. Sometimes we start with an uppercase - described in chapter 15c.

C# has some built in data types. Numeric types are divided into: 1. Signed integrals (positive and negative values):

sbyte, short, int, long

2. Unsigned integrals (only positive values):

byte, ushort, uint, ulong

3. Real numbers:

float, double, decimal

These types differ with an amount of memory occupied and the method of number representation. In Rhino and Grasshopper you will almost always use only int (integers) and double (double–precision floating point numbers). You can find the following as well: char type

which represents a single Unicode character (eg. 'C')

string type

which represents an immutable chain of char (eg „C# course”)


3b. Rhino and Grasshopper data types There are some custom data types in Rhino which were designed specifically to represent geometry. Some of the examples are Point3d, Line, Poyline, Brep. Check out Rhino SDK (software development kit) documentation for full reference. Every RhinoCommon type has its „wrapper” in Grasshopper. Wrapping RhinoCommon objects in GH objects inside a script before outputting is a good practice which can speed up the script a lot. Point3d

GH_Point

// example of wrapping:

Vector3d

GH_Vector

Curve

GH_Curve

// create a point at 0.5, 5, 10: Point3d myPt = new Point3d(0.5, 5, 10);

Surface

GH_Surface

double

GH_Number

bool

GH_Boolean

// wrap in GH type: GH_Point ghPt = new GH_Point(myPt); // assign ghPt to output: output = ghPt

RhinoCommon and Grasshopper type comparison

4. Punctuators and operators Puncuatorsdemarcate the structure of the program. Braces group multiple statements into a statement block and semicolons signalize the end of a single statement (you don’t need semicolons at the ends of statement blocks). All instructions in C# have to end with a semicolon. { block of code }

instruction;

An operator transforms and combines expressions. Following is the list of operators commonly used: + * / %

addition subtraction multiplication division remainder after division

++ incrementation -- decrementation = assignment

+= -= *= /=

add and assign subtract and assign multiply and assign divide and assign

5. Boolean type C# bool type is a logical value that can be assigned either true or false. It is used to check conditions of script or program execution. bool myCondition; myCondition = true;

declaration and initialization of a bool type variable „myCondition”

If you want to compare numbers you have to use one of the following operators. All of them return bool type data (true or false – it's like getting an answer for a question. Notice the difference between an assignment (single ‘=’) and comparison (double ‘=’) operators! == equal != not equal && conditional AND || conditional OR ! conditional NOT ^ bitwise XOR (exclusive alternative)

< smaller than <= smaller or equal to > larger than >= larger or equal to


6. Comments Comments are lines of code ommited by the program. It is a good practice to explain complex parts of the code. There are two types of comments: single and multiline. You should avoid multi line comments.

// single line comment starts with double forward slashes // here you can write whatever you want /* multi line comment starts with a forward slash and an asterix. You can write whatever you want but you have to end the commentary block with an asterix and a forward slash: */

Code is like humour. If you have to explain it, it's bad.

Hint: You can use single line comments to "turn off" selected lines of code.

7. if() {...} else {...} statement You can control your program flow by using if–else statements. Notice the braces and lack of semicolons! if (some_condition) { //doSomething; } else { //doSomethingElse; }

if() checks if the condition (bool type) is true or false and executes one of the statement blocks

bool itRains = false; bool itIsSunny = true; bool itIsWindy = false; if (!itIsWindy && (itRains || itIsSunny )) { TakeAnUmbrella(); } else if (itIsWindy && itRains) { TakeACoat(); } else { StayAtHome(); }

Explanation: If it is not windy and it rains or it is sunny, take an umbrella. Else, if it is windy and it rains, take a coat. Else stay at home. In this case computer (or a robot) will take an umbrella.

8. switch() { ... } statement If you have to check multiple conditions which lead to different scenarios, it is convenient to use switch() statement. It checks if provided variable is equal to specified cases. switch (some_variable) { case value1: doSomething(); break; case value2: doSomethingElse(); break; case value3: doSomethingElse2(); break; default: doSomethingElse3(); break; }

Is equivalent to:

if (some_variable == value1) doSomething(); else if (some_variable == value2) doSomethingElse(); else if (some_variable == value3) doSomethingElse2(); else doSomethingElse3();


9. for() loop Sometimes you need to loop some actions, because it would be inefficient to repeat the same line tens of hundreds of times.. The most popular way is to use a for() loop. The syntax is as follows: for (initial_statement; condition; action_after_every_pass) { //instructions here }

The most common approach is to use initial statement for declaring and initializing an indexing variable (usually ‘i’, ‘j’, ‘k’, ‘l’, ‘m’, ‘n’) with 0 (zero), set the condition to „indexing variable smaller than number of passes” and end every pass with incrementation of the indexing variable (eg. „i++;” or „i = i + 1;”) for (int i = 0; i < count; i++) { Print(i.ToString()); }

This loop would print values from zero to count - 1

It is possible to end a loop on demand with break; instruction or to jump to the next pass with continue; for (int i = 0; i < count; i++) { if (i % 2 != 0) continue; // jump to next pass, i++ if (i == 10) break; // exit loop Print(i.ToString()); }

This loop would print only even values from zero to eight (at ten loop exits). If count is lower than 10, then the loop will finish earlier.

for() loop is not starting if the condition is not met at the beginning.

10. while() loop while (condition) { // do something }

This is a simpler version of a loop. It executes until the conditions are met and is not starting if they are not. You can use break; and continue; like in a for() loop.

int number=1; while (number < 10) { // instructions here Print(number.ToString()); number += 2; }

This loop would print only uneven numbers from one to nine (at ten loop exits)


11. do {...} while(); loop do { // do something } while (condition);

The only difference between while() and do ... while() loops is the latter executes at least once no matter if the condition is met or not. Notice the semicolon after while() ! int number = -10; do { Print(number.ToString()); number--; } while(number < 0);

This loop would print „-10”

12. foreach() loop foreach (type a_variable in a_list_or_array) { //do something with a_variable }

foreach() loop is used to iterate through an array or a list.

int[] numbers = new int[]{1, 2, 4, 6, 7}; foreach (int num in numbers) { Print(num.ToString()); }

Tips: 1. You can replace the type with var keyword. The program will try to guess the type. 2. If you want to declare an array in a foreach() loop or if you are used to python-like loops, try using Enumerable.Range(int start, int count) method: foreach (var num in Enumerable.Range(0, 10)){ Print(num.ToString()); // prints numbers } //from 0 to 9

This loop would print all numbers from the array: 1, 2, 4, 6, 7

13a. Collections: Arrays type[] arrayName = new type[numberOfElements];

An array is a collection type which represents a fixed number of variables (elements) of a particular type. They are always stored in a contiguous block of memory, what provides an efficient access. You need to declare how many elements will an array have. You access a specific element in an array by providing its index in square brackets [ ] after the array’s name. Indexing starts from zero [0]! int[] numbers = new int[50]; for (int i = 0; i < numbers.Length; i++) { numbers[i] = i; } int[] numbers2 = new int[]{1, 2, 4, 6, 7};

Two ways of initializing an array: 1) creating an array with 50 elements of default value 2) creating an array and specifying all of the elements inside braces Hint: If you don't know the number of elements in array, use its Length property:

array_name.Length


You can create a multidimensional array by declaring number of elements in more than one dimension. You access a specific element by providing multiple indices separated by a comma.

int[,] numbers = new int[10, 20]; for(int i = 0; i < numbers.GetLength(0); i++) Creates an array with { for(int j = 0; j < numbers.GetLength(1); i++) 10 rows and 20 colums { numbers[i, j] = i*j; Hint: } GetLength(rank) is a property specific to } multidimensional arrays. and gives the number of elements in the specified dimension.

array_name.GetLength(rank)

13b. Collections: Lists List<type> listName = new List<type>();

Lists are similar to arrays but can have varying number of elements. You can add and remove data from a list. And once again: indexing starts from 0!

List<int> numbers = new List<int>(); for(int i=0; i<10; i++) { numbers.Add(i); }

Two ways of initializing a list: 1) creating an empty list and filling it with new elements 2) creating a list and specifying the elements inside braces

List<int> numbers2 = new List<int>(){1, 2, 4, 6, 7};

for(int i=numbers.Count-1; i>0; i--) { if(numbers[i] % 2 == 0) numbers.RemoveAt(i); }

Removing every even number from the list. Notice iterating from the end towards the beginning.

14a. Methods (aka functions or subprograms or subroutines) <modifiers> return_type method_name(parameters) { // a set of instructions }

A method performs some action in a series of instructions. It can receive some data from the caller by parameters and output data back through return statement. You always have to specify the return type in a method definition. A method can be marked with void return type, which indicates that it’s not returning any value to the caller. It is possible to output more than one object via ref/out parameters.


public void PrintHello() { Print(”Hello”); }

A non-returning method (subroutine)

public void PrintNumber(int num) { Print(num.ToString()); }

A non-returning method with single parameter

public int TwoTimes(int num); { return num*2; }

A method returning single integer number (function)

//... //inside a Runscript() method: int number1 = 4; int number2 = 0; PrintHello(); PrintNumber(number1); number2 = TwoTimes(number1); PrintNumber(number2); //...

Results: Hello 4 8

Methods allow, inter alia, the following modifiers: (mentioned only for the sake of completness) Static modifier static Access modifiers public internal private protected Inheritance modifiers new virtual abstract override sealed

14b. Method overloading It is possible to have multiple methods with the same name but different implementation and parameter list. Consider the following example:

public void PrintNumber(int intNum) { Print(intNum.ToString()); } public void PrintNumber(double realNum) { Print(realNum.ToString()); } public void PrintNumber(double realNum, string label) { Print(label + „: ” + realNum.ToString()); } //... //inside a Runscript() method: int number1 = 2; double number2 = 3.14; string label = „number2”; PrintNumber(number1); PrintNumber(number2); PrintNumber(number2, label); //...

Results: 2 3.14 number2: 3.14

Overloaded PrintNumber() method allows for two types of parameters: int or double. This gives more elasticity. It is not possible to overload methods by changing only the return type. Signatures of PrintNumber() methods: PrintNumber(int) PrintNumber(double) PrintNumber(double, string)


14c. Out and ref modifiers It is possible to return more than one value to a caller. By default arguments are passed by value, what means that a copy of object is created inside a method and destroyed after returning. You can however pass by reference, what means that an actual memory adress of an object is passed to the method and an alias operating on that memory is created. It causes the changes of the passed object will be visible outside the method body, after it was executed. public void Iterate(int intNum) { intNum += 1; Print(”inside Iterate(): ” + intNum.ToString()); } public void IterateRef(ref int intNum) { intNum += 1; Print(”inside IterateRef(): ” + intNum.ToString()); } //... //inside a Runscript() method: int myNumber = 2; Print(myNumber.ToString()); Iterate(myNumber); Print(myNumber.ToString());

Results: 2 inside Iterate(): 3 2 inside IterateRef(): 3 3

Iterate() iterated only a local copy of myNumber. IterateRef() iterated the original myNumber variable through a reference

IterateRef(ref myNumber); Print(myNumber.ToString()); //...

An out argument is like ref, except it does not need to be assigned before being passed into a method and has to be assigned inside a method before it comes out. Notice you have to write ref or out both when you define and call a method.

15a. Understanding value and reference types. There are four categories of data types in C#: values, references, generic type parameters and pointers. In this section we’ll focus on the first two, because they’re most common and the others are more advanced. Value types comprise most built-in types (all numeric types, char and bool) and custom struct and enum types. Reference types comprise all class, array, delegate and interface types (including predefined string type).. The fundamental difference is how these types are stored in computer memory.

public struct MyPoint2d_Struct { public double X; public double Y; } public class MyPoint2d_Class { public double X; public double Y; }

Value type

Reference type


//... //inside a Runscript() method: MyPoint2d_Struct point1 = new MyPoint2d_Struct(); point1.X = 10; point1.Y = 20;

Declaration and initialization of a custom-type object Accessing a member with . operator

MyPoint2d_Struct point2 = point1; point2.X = 15; Print(point1.X.ToString()); Print(point1.Y.ToString()); Print(point2.X.ToString()); Print(point2.Y.ToString()); //...

// // // //

Copying object

10 20 15 20

In case of value types, assignment operator creates an actual independent copy of an object. point1 wasn’t affected by changes made on point2.

MyPoint2d_Struct instance:

}

X Y memory

values

point1:

point2:

10

15

20

20

independent instances

//... //inside a Runscript() method: MyPoint2d_Class point1 = new MyPoint2d_Class(); point1.X = 10; point1.Y = 20;

Declaration and initialization of a custom-type object Accessing a member with . operator

MyPoint2d_Class point2 = point1; point2.X = 15; Print(point1.X.ToString()); Print(point1.Y.ToString()); Print(point2.X.ToString()); Print(point2.Y.ToString()); //...

// // // //

Copying a reference to an object! (creating an alias)

15 20 15 20

In case of reference types, assignment operator creates a copy of reference to data. Reference is in fact a variable storing a memory adress. Data referenced by point1 was affected by changes made on point2, because they are pointing to the same place in memory!.

MyPoint2d_Class instance: Reference

X Y

}

point1 15 data point2

20

}

data

references to the same adress in the memory


15b. Creating custom types: enum This is the simplest custom type. Its name comes from „enumerable”. It lets you specify a group of named numeric constants. Each enum member has an underlying value. These values are by default of type int and are set to 0, 1, 2... in the declaration order.

public enum BoxSide { Front, Back, Left, Right, Top, Bottom, Invalid = -1 }

//0 //1 //2 //3 //4 //5 //explicitly defined -1

Results: We’re on top! 4

//... //inside a Runscript() method: BoxSide mySide = BoxSide.Top;

Note: you can cast enum to its underlying type.

if(mySide == BoxSide.Top) Print(”We’re on top!”); Print(((int)mySide).ToString()); //...

15c. Creating custom types: class Every program written in C# consists of classes. Classes are object descriptions. They can contain information about what an object is (through defined fields and properties) and how it behaves (through defined methods). Fields, properties and methods are called class members. The following is a classic example of a class. Reminder: classes are reference types! public class Point { public double X; public double Y; } //... //inside a Runscript() method: Point myPoint = new Point(); myPoint.X = 10; myPoint.Y = 15.5; //...

This class contains two members (fields) which are public (can be accessed from the outside of class) and of type double.

In order to create an object instance you have to declare a reference by typing: <object_type> name_of_your_choice; After this you create an actual object in memory by typing: new <object_type>() and assign it to previously created reference You can access object’s members by typing reference’s name followed by a dot ( . ) operator and a member’s name.

More complex classes may have the following: before the class keyword:

attributes and class modifiers: public, internal, abstract, sealed, static, unsafe and partial

following the class Name:

generic type parameters, a base class and interfaces

Within the braces: class members:

methods, properties, indexers, events, fields, constructors, overloaded operators, nested types and a finalizer


A class usually contains a lot of different data fields and methods. Let’s add some functionality to the Point class.

public class Point { private double x; private double y; public void SetCoordinates(double newX, double newY) { x = newX; y = newY; Note: }

naming convention is that private fields start with a lowercase and public fields start with an uppercase

public double GetX(){return x;} public double GetY(){return y;} }

//... //inside a Runscript() method: Point myPoint = new Point(); myPoint.X = 10; //Error! X is private and can be accessed //only from the inside of Point class //...

Notice that now both x and y are private (can be accessed only from the inside of class). To make it possible to set and read coordinates we defined three public methods. This approach is reasonable if you want to prevent the user from inputting irrelevant values (then a check inside the setting method is needed). //... //inside a Runscript() method: Point myPoint = new Point(); myPoint.SetCoordinates(10, 15.3); Print(myPoint.GetX().ToString()); Print(myPoint.GetY().ToString()); //...

Results: 10 15.3

15d. Classes: properties Although having Set... and Get... methods might be useful, it is unhandy for user to call them every time to make changes or read values. It is also not intuitive. Look at this code: //... //inside a Runscript() method: Point myPoint = new Point(); myPoint.SetX(10); if(myPoint.GetX() > 20) Print(”We’re so far!”); //...

Same member is accessed with different methods!

There is a way to combine restricted only-method access with convenience of public fields. You can achieve that by using properties, which are a special kind of containers of object's data. public class Point { private double _x; //a private field... private double _y; //a private field... public double X { //a public property... set {if(value>=0) _x = value; else _x = 0;} get {return _x;} } public double Y { //a public property... set {if(value>=0) _y = value; else _y = 0;} get {return Y;} } }


Properties resemble methods but don’t define any arguments. The returning type is the type of an underlying field (which has to be explicitly defined by us, but can in case of auto-properties it is implicitly defined by a compiler). Inside the braces two behaviours are described: set, which accepts an implicit argument denoted with keyword value and get which has to return some data to caller. Now it is possible to use myPoint as follows: //... //inside a Runscript() method: Point myPoint = new Point(); myPoint.X = 10; myPoint.Y = -10; //”set” checks if provided value is smaller //than 0 and assigns Y a default value of 0. Print(myPoint.X.ToString()); Print(myPoint.Y.ToString()); //...

//10 //0

If you don’t want to specify any special behaviour you can just restrict setting or getting a value by the user. This type of a member is called an automatic property and the most common use is to make „setting” private (controlled by the class) and „getting” being public. public class Point { public double X { get; private set; } public double Y { get; private set; } }

Underlying methods are implicitly defined by a compiler.

15e. Classes: constructors public class class_name { public class_name(optional_arguments) { constructor’s instructions... } }

When an object is created in memory, a special method called a constructor is executed. It usually initializes all fields and properties of an object and thus allows to set initial state. Constructors are non-returning, mostly public (in general) and have the same name as the class. If you don’t define any constructor, then a default parameterless, empty constructor is implicitly created by the compiler. public class Point { public Point(double initX, double initY){ X = initX; Y = initY; } public double X { get; private set; } public double Y { get; private set; } } //... //inside a Runscript() method: Point myPoint = new Point(15, 16.2); Print(myPoint.X.ToString()); Print(myPoint.Y.ToString()); //...

//15 //16.2

Note: constructors can be (and very often are) overloaded (14b) just as regular methods.


What if a constructor accepts arguments that are named exactly as the object’s fields? How to distinct between new and old values? No problem, just use this keyword which denotes the object itself: public class Point { public Point(double X, double Y){ this.X = X; this.Y = Y; } public double X { get; private set; } public double Y { get; private set; } }

15f. Classes: inheritance public class class_name : base_class { }

Inheritance is one of the most important aspects of every object-oriented programming (OOP) language. It is used for code reuse and extensibility.

public class ColouredPoint : Point // Point class from the example from chapter 15e { public System.Drawing.Color ColourRGB { get; set; } } //... //inside a Runscript() method: ColouredPoint myColouredPoint = new myColouredPoint(); myColouredPoint.X = 3.14; myColouredPoint.Y = 10.5; myColouredPoint.ColourRGB = Color.FromArgb(0,0,0); Print(myPoint.X.ToString()); //3.14 Print(myPoint.Y.ToString()); //10.5 Print(myPoint.ColourRGB .ToString()); //Color [A=255, R=0, G=0, B=0] //...

ColouredPoint inherits all members of its base class Point. When a ColouredPoint object is created, a default base class constructor is called first. It is possible to reuse some base code like this: public class ColouredPoint : Point { public ColouredPoint(double x, double y, int r, int g, int b) : base(x, y) { ColourRGB = Color.FromArgb(r, g, b); } public System.Drawing.Color ColourRGB { get; set; } }

Explicitly calling the base class constructor with provided arguments


15g. Classes: overriding base methods When a class is derived (inherits) from a class which has some virtual (or override) methods, it can override their implementation. As every object is implicitly derived from System.Object class which has a public virtual string ToString() method, we can override it in our Point class. public class Point { public Point(double initX, double initY){ X = initX; Y = initY; } public double X { get; private set; } public double Y { get; private set; } public override string ToString() { return string.Format(”X: {0}, Y: {1}”, X, Y); } } //... //inside a Runscript() method: Point myPoint = new Point(15, 16.2); Print(myPoint.ToString()); //...

//X: 15, Y: 16.2

15g. Static members A static modifier makes a member common to a whole class instead of being specific for a single instance. Different from regular members, you access static members by typing class name (not the object name!) followed by a dot operator and static member’s name. public class Point { public Point(double initX, double initY){ X = initX; Y = initY; PointCount++; } public static int PointCount = 0; public double X { get; private set; } public double Y { get; private set; } public override string ToString() { return string.Format(”X: {0}, Y: {1}”, X, } } //... //inside a Runscript() method: Point myPoint1 = new Point(15, 16.2); Point myPoint2 = new Point(-3, 12); Point myPoint3 = new Point(5, -7.8); Print(Point.PointCount.ToString()); //3 //...

Note: A whole class can be marked as static. If so, then it can contain only static members. A very good example is System.Math class containing many useful methods and values.

Y);

Accessing a static member by typing class’ name, a dot operator and a member’s name


16a. DataTree<> class overview DataTree<> class is designed for handling branched data you know from Grasshopper. Consider the following example:

(C) (D)

(A) (B)

1. A list of two items is created

(E)

2. A list of two items is branched

3. A data tree is branched

In fact, the resulting data structure is a list of lists which are marked with „branching info”. We have the following: List<int> {2, 1};

with branching info {0;0;0;0}

(made from the first item(C) made from the first item(A))

List<int> {2};

with branching info {0;0;0;1}

(made from the second item(D) made from the first item(A))

List<int> {2, 1};

with branching info {0;0;1;0}

(made from the first item(E) made from the second item(B))

This data tree would be created like this in C#: DataTree<int> myTree = new DataTree<int>(); myTree.AddRange(new List<int>(){2, 1}, new GH_Path(0,0,0,0)); myTree.AddRange(new List<int>(){2}, new GH_Path(0,0,0,1)); myTree.AddRange(new List<int>(){2, 1}, new GH_Path(0,0,1,0));

It’s that simple! When components try to combine data trees, they match lists by GH_Paths. Here’s how you iterate through a DataTree: DataTree<int> myTree = new DataTree<int>(); myTree.AddRange(new List<int>(){2, 1}, new GH_Path(0,0,0,0)); myTree.AddRange(new List<int>(){2}, new GH_Path(0,0,0,1)); myTree.AddRange(new List<int>(){2, 1}, new GH_Path(0,0,1,0));

for(int i=0; i<myTree.BranchCount; i++) { for(int j=0; j<myTree.Branch(i).Count; j++) { Print(myTree.Branch(i)[j].ToString()); } }

Property returning a number of branches Method returning a branch as a list Accessing elements of returned list


16b. DataTree<> declaring and handling DataTree<type> dataTreeName = new DataTree<type>();

The statement above shows a scheme of declaring a DataTree collection and allocating memory with the new keyword. In general it is the same as with lists (see chapter 13b). The difference between Lists and DataTrees is that DataTrees are in fact lists of lists. Lists inside DataTrees are called Branches. Branches contain actual objects (numbers, text, geometry etc.). Each Branch has a corresponding Path which is visible in Grasshopper as for example {0; 2; 1}. Basically it is just an array of integers. Return to chapter 13a for a recap. Formally it’s GH_Path type. Below is an example code with the most popular methods and properties of a DataTree<> object.

Tip: Sharp brackets <> almost always follow a generic method or class. A generic method or class can work with different types of objects and usually you have to explicitly „tell” the program which type you want to use. List<> and DataTree<> are the most common ones you will see while scripting in Grasshopper. DataTree<int> pointIndices; DataTree<Point3d> agentLocations; List<Brep> nearBuildings; //etc...

The example is verbose on purpose. DataTree<Point3d> exampleDataTree = new DataTree<Point3d>(); //declaration with Point3d type Point3d tempPoint = new Point3d(10, 5.0, 3);

//creating a point object

GH_Path tempPath = new GH_Path(0, 1, 2); tempPath.Indices = new int[]{1, 1}; Print(tempPath.Indices[0].ToString());

//creating a {0; 1; 2} path for the tree //Indices is an array of integers //path changed to {1, 1} //accessing path indices just like with arrays

exampleDataTree.Add(tempPoint, tempPath);

//adding the point to the tree

//changing the variables a little bit - doesn’t affect the objects which has been //already added to the tree: tempPath.Indices[1] = 0; tempPoint.X = 3.5; tempPoint.Y = 10; exampleDataTree.Add(tempPoint, tempPath);

//adding changed point to branch {1; 0}

//you don’t need to define objects before adding them: exampleDataTree.Add(new Point3d(2, 5, 15.1), new GH_Path(1, 1)); List<Point3d> singleBranch = exampleDataTree.Branch(0); //a branch is basically a list Print(singleBranch[0].ToString()); Method returning a

branch as a list

Print(exampleDataTree.Branch(0)[0].ToString()); Print(exampleDataTree.Path(0).ToString()); //you can get paths in a same way as you get //branches. Paths and branches are tied together, so Branch(0) contains items stored on //Path(0), Branch(1) contains items stored on Path(1) etc... int numberOfBranches = exampleDataTree.BranchCount; //know we know how many branches we have // going through a whole DataTree:

Property returning a number of branches

for(int i=0; i<exampleDataTree.BranchCount; i++) // branches first { Print(”Branch ” + exampleDataTree.Path(i).ToString() + „ has that many elements: ” + exampleDataTree.Branch(i).Count.ToString()); for(int j=0; j<exampleDataTree.Branch(i).Count; j++) // items on specific branches { Print(exampleDataTree.Branch(i)[j].ToString()); } }


Turn static files into dynamic content formats.

Create a flipbook
Issuu converts static files into: digital portfolios, online yearbooks, online catalogs, digital photo albums and more. Sign up and create your flipbook.