# Rhino Developer Docs > Official developer resources for Rhino and Grasshopper. Rhino developer tools are royalty free and include support. -------------------------------------------------------------------------------- # 1 Vector Mathematics Source: https://developer.rhino3d.com/en/guides/general/essential-mathematics/vector-mathematics/ This guide discusses vector math including vector representation, vector operation, and line and plane equations. A vector indicates a quantity, such as velocity or force, that has direction and length. Vectors in 3D coordinate systems are represented with an ordered set of three real numbers and look like: $$\mathbf{\vec v} = $$ ## 1.1 Vector representation In this document, lower case bold letters with arrow on top will notate vectors. Vector components are also enclosed in angle brackets. Upper case letters will notate points. Point coordinates will always be enclosed by parentheses. Using a coordinate system and any set of anchor points in that system, we can represent or visualize these vectors using a line-segment representation. An arrowhead shows the vector direction. For example, if we have a vector that has a direction parallel to the x-axis of a given 3D coordinate system and a length of 5 units, we can write the vector as follows: $$\mathbf{\vec v} = <5, 0, 0>$$ To represent that vector, we need an anchor point in the coordinate system. For example, all of the arrows in the following figure are equal representations of the same vector despite the fact that they are anchored at different locations.
Figure (1): Vector representation in the 3-D coordinate system.
Given a 3D vector $$\vec v = $$ , all vector components $$a_1$$, $$a_2$$, $$a_3$$ are real numbers. Also all line segments from a point $$A(x,y,z)$$ to point $$B(x+a_1, y+a_2, z+a_3)$$ are equivalent representations of vector $$\vec v$$. So, how do we define the end points of a line segment that represents a given vector? Let us define an anchor point (A) so that: $$A = (1, 2, 3)$$ And a vector: $$\mathbf{\vec v} = <5, 6, 7>$$ The tip point $$(B)$$ of the vector is calculated by adding the corresponding components from anchor point and vector $$\vec v$$: $$B = A + \mathbf{\vec v}$$ $$B = (1+5, 2+6, 3+7) $$ $$B = (6, 8, 10)$$
Figure (2): The relationship between a vector, the vector anchor point, and the point coinciding with the vector tip location.
### Position vector One special vector representation uses the $$\text{origin point} (0,0,0)$$ as the vector anchor point. The position vector $$\mathbf{\vec v} = $$ is represented with a line segment between two points, the origin and the tip point B, so that: $$\text{Origin point} = (0,0,0)$$ $$B = (a_1,a_2,a_3)$$
Figure (3): Position vector. The tip point coordinates equal the corresponding vector components.
A *position vector* for a given vector $$\vec v= < a_1,a_2,a_3 >$$ is a special line segment representation from the origin point $$(0,0,0)$$ to point $$(a_1, a_2, a_3)$$. ### Vectors vs. points Do not confuse vectors and points. They are very different concepts. Vectors, as we mentioned, represent a quantity that has direction and length, while points indicate a location. For example, the North direction is a vector, while the North Pole is a location (point). If we have a vector and a point that have the same components, such as: $$\mathbf{\vec v} = <3,1,0>$$ $$P = (3,1,0)$$ We can draw the vector and the point as follows:
Figure (4): A vector defines a direction and length. A point defines a location.
### Vector length As mentioned before, vectors have length. We will use $$\vert a \vert$$ to notate the length of a given vector $$ a $$. For example: $$\mathbf{\vec a} = <4, 3, 0>$$ $$\vert \mathbf{\vec a} \vert = \sqrt{4^2 + 3^2 + 0^2}$$ $$\vert \mathbf{\vec a} \vert = 5$$ In general, the length of a vector $$\mathbf{\vec a} $$ is calculated as follows: $$\vert \mathbf{\vec a} \vert = \sqrt{(a_1)^2 + (a_2)^2 + (a_3)^2} $$
Figure (5): Vector length.
### Unit vector A unit vector is a vector with a length equal to one unit. Unit vectors are commonly used to compare the directions of vectors. A unit vector is a vector whose length is equal to one unit. To calculate a unit vector, we need to find the length of the given vector, and then divide the vector components by the length. For example: $$\mathbf{\vec a} = <4, 3, 0>$$    $$\vert \mathbf{\vec a} \vert = \sqrt{4^2 + 3^2 + 0^2}$$    $$\vert \mathbf{\vec a} \vert = 5 \text{ unit length}$$ If $$\mathbf{\vec b} = \text{unit vector}$$ of $$a$$, then:    $$\mathbf{\vec b} = <4/5, 3/5, 0/5>$$    $$\mathbf{\vec b} = <0.8, 0.6, 0>$$    $$\vert \mathbf{\vec b} \vert = \sqrt{0.8^2 + 0.6^2 + 0^2}$$    $$\vert \mathbf{\vec b} \vert = \sqrt{0.64 + 0.36 + 0}$$    $$\vert \mathbf{\vec b} \vert = \sqrt{(1)} = 1 \text{ unit length}$$ In general: $$\mathbf{\vec a} = $$ The unit vector of $$\mathbf{\vec a} = $$
Figure (6): Unit vector equals one-unit length of the vector.
## 1.2 Vector operations ### Vector scalar operation Vector scalar operation involves multiplying a vector by a number. For example: $$\mathbf{\vec a} = <4, 3, 0>$$ $$2* \mathbf{\vec a} = <2*4, 2*3, 2*0>$$ $$2*\mathbf{\vec a} = <8, 6, 0>$$
Figure (7): Vector scalar operation
In general, given vector $$\mathbf{\vec a} = $$, and a real number $$t$$ $$t*\mathbf{\vec a} = < t* a_1, t* a_2, t* a_3 >$$ ### Vector addition Vector addition takes two vectors and produces a third vector. We add vectors by adding their components. Vectors are added by adding their components. For example, if we have two vectors: $$\mathbf{\vec a} = <1, 2, 0> $$ $$\mathbf{\vec b} = <4, 1, 3> $$ $$\mathbf{\vec a}+\mathbf{\vec b} = <1+4, 2+1, 0+3>$$ $$\mathbf{\vec a}+\mathbf{\vec b} = <5, 3, 3>$$
Figure (8): Vector addition.
In general, vector addition of the two vectors a and b is calculated as follows: $$\mathbf{\vec a} = $$ $$\mathbf{\vec b} = $$ $$\mathbf{\vec a}+\mathbf{\vec b} = $$ Vector addition is useful for finding the average direction of two or more vectors. In this case, we usually use same-length vectors. Here is an example that shows the difference between using same-length vectors and different-length vectors on the resulting vector addition:
Figure (9): Adding various length vectors (left). Adding same length vectors (right) to get the average direction.
Input vectors are not likely to be same length. In order to find the average direction, you need to use the unit vector of input vectors. As mentioned before, the unit vector is a vector of that has a length equal to 1. ### Vector subtraction Vector subtraction takes two vectors and produces a third vector. We subtract two vectors by subtracting corresponding components. For example, if we have two vectors $$\mathbf{\vec a}$$ and $$\mathbf{\vec b}$$ and we subtract $$\mathbf{\vec b}$$ from $$\mathbf{\vec a}$$, then: $$\mathbf{\vec a} = <1, 2, 0> $$ $$\mathbf{\vec b} = <4, 1, 4> $$ $$\mathbf{\vec a}-\mathbf{\vec b} = <1-4, 2-1, 0-4>$$ $$\mathbf{\vec a}-\mathbf{\vec b} = <-3, 1, -4> = \mathbf{\mathbf{\vec b}a}$$ If we subtract $$\mathbf{\vec b}$$ from $$\mathbf{\vec a}$$, we get a different result: $$\mathbf{\vec b} - \mathbf{\vec a} = <4-1, 1-2, 4-0>$$ $$\mathbf{\vec b} - \mathbf{\vec a} = <3, -1, 4> = \mathbf{\mathbf{\vec a}b}$$ Note that the vector $$\mathbf{\vec b} - \mathbf{\vec a}$$ has the same length as the vector $$\mathbf{\vec a} - \mathbf{\vec b}$$, but goes in the opposite direction.
Figure (10): Vector subtraction.
In general, if we have two vectors, $$\mathbf{\vec a}$$ and $$\mathbf{\vec b}$$, then $$\mathbf{\vec a} - \mathbf{\vec b}$$ is a vector that is calculated as follows: $$\mathbf{\vec a} = $$ $$\mathbf{\vec b} = $$ $$\mathbf{\vec a}-\mathbf{\vec b} = = \mathbf{\mathbf{\vec b}a}$$ Vector subtraction is commonly used to find vectors between points. So if we need to find a vector that goes from the tip point of the position vector $$\mathbf{\vec b}$$ to the tip point of the position vector $$\mathbf{\vec a}$$, then we use vector subtraction $$(\mathbf{\vec a}-\mathbf{\vec b})$$ as shown in Figure (11).
Figure (11): Use vector subtraction to find a vector between two points.
### Vector properties There are eight properties of vectors. If a, b, and c are vectors, and s and t are numbers, then: $$\mathbf{\vec a} + \mathbf{\vec b} = \mathbf{\vec b} + \mathbf{\vec a}$$ $$\mathbf{\vec a} + 0 = a$$ $$s * (\mathbf{\vec a} + \mathbf{\vec b}) = s * a + s * \mathbf{\vec b}$$ $$s * t * (\mathbf{\vec a}) = s * (t * \mathbf{\vec a})$$ $$\mathbf{\vec a} + (\mathbf{\vec b} + \mathbf{\vec c}) = (\mathbf{\vec a} + \mathbf{\vec b}) + \mathbf{\vec c}$$ $$\mathbf{\vec a} + (-\mathbf{\vec a}) = 0$$ $$(s + t) * \mathbf{\vec a} = s * \mathbf{\vec a} + t * \mathbf{\vec a}$$ $$1 * \mathbf{\vec a} = \mathbf{\vec a}$$ ### Vector dot product The dot product takes two vectors and produces a number. For example, if we have the two vectors a and b so that: $$\mathbf{\vec a} = <1, 2, 3> $$ $$\mathbf{\vec b} = <5, 6, 7>$$ Then the dot product is the sum of multiplying the components as follows: $$\mathbf{\vec a} · \mathbf{\vec b} = 1 * 5 + 2 * 6 + 3 * 7$$ $$\mathbf{\vec a} · \mathbf{\vec b} = 38$$ In general, given the two vectors a and b: $$\mathbf{\vec a} = $$ $$\mathbf{\vec b} = $$ $$\mathbf{\vec a} · \mathbf{\vec b} = a_1 * b_1 + a_2 * b_2 + a_3 * b_3$$ We always get a positive number for the dot product between two vectors when they go in the same general direction. A negative dot product between two vectors means that the two vectors go in the opposite general direction.
Figure (12): When the two vectors go in the same direction (left), the result is a positive dot product. When the two vectors go in the opposite direction (right), the result is a negative dot product.
When calculating the dot product of two unit vectors, the result is always between 1 and +1. For example: $$\mathbf{\vec a} = <1, 0, 0>$$ $$\mathbf{\vec b} = <0.6, 0.8, 0>$$ $$\mathbf{\vec a} · \mathbf{\vec b} = (1 * 0.6, 0 * 0.8, 0 * 0) = 0.6$$ In addition, the dot product of a vector with itself is equal to that vector’s length to the power of two. For example: $$\mathbf{\vec a} = <0, 3, 4>$$ $$\mathbf{\vec a} · \mathbf{\vec a} = 0 * 0 + 3 * 3 + 4 * 4 $$ $$\mathbf{\vec a} · \mathbf{\vec a} = 25$$ Calculating the square length of vector $$\mathbf{\vec a}$$ : $$\vert \mathbf{\vec a} \vert = \sqrt{4^2 + 3^2 + 0^2}$$ $$\vert \mathbf{\vec a} \vert = 5$$ $$\vert \mathbf{\vec a} \vert 2 = 25$$ ### Vector dot product, lengths, and angles There is a relationship between the dot product of two vectors and the angle between them. The dot product of two non-zero unit vectors equals the cosine of the angle between them. In general: $$\mathbf{\vec a} · \mathbf{\vec b} = \vert \mathbf{\vec a} \vert * \vert \mathbf{\vec b} \vert * cos(ө)$$ , or $$\mathbf{\vec a} · \mathbf{\vec b} / (\vert \mathbf{\vec a} \vert * \vert \mathbf{\vec b} \vert) = cos(ө)$$ Where: $$ө$$ is the angle included between the vectors. If vectors a and b are unit vectors, we can simply say: $$\mathbf{\vec a} · \mathbf{\vec b} = cos(ө)$$ And since the cosine of a 90-degree angle is equal to 0, we can say: Vectors $$\vec a$$ and $$\vec b$$ are orthogonal if, and only if, $$\vec{a} \cdot \vec{b} = 0$$. For example, if we calculate the dot product of the two orthogonal vectors, World xaxis and yaxis, the result will equal zero. $$\mathbf{\vec x} = <1, 0, 0>$$ $$\mathbf{\vec y} = <0, 1, 0>$$ $$\mathbf{\vec x} · \mathbf{\vec y} = (1 * 0) + (0 * 1) + (0 * 0)$$ $$\mathbf{\vec x} · \mathbf{\vec y} = 0$$ There is also a relationship between the dot product and the projection length of one vector onto another. For example: $$\mathbf{\vec a} = <5, 2, 0>$$ $$\mathbf{\vec b} = <9, 0, 0>$$ $$unit(\mathbf{\vec b}) = <1, 0, 0>$$ $$\mathbf{\vec a} · unit(\mathbf{\vec b}) = (5 * 1) + (2 * 0) + (0 * 0) $$ $$\mathbf{\vec a} · unit(\mathbf{\vec b}) = 2 (\text{which is equal to the projection length of mathbf{\vec a} onto mathbf{\vec b}})$$
Figure (13): The dot product equals the projection length of one vector onto a non-zero unit vector.
In general, given a vector a and a non-zero vector b, we can calculate the projection length pL of vector a onto vector b using the dot product. $$pL = \vert \mathbf{\vec a} \vert * cos(ө) $$ $$pL = \mathbf{\vec a} · unit(\mathbf{\vec b})$$ ### Dot product properties If $$\mathbf{\vec a}$$, $$\mathbf{\vec b}$$, and $$\mathbf{\vec c}$$ are vectors and s is a number, then: $$\mathbf{\vec a} · \mathbf{\vec a} = \vert \mathbf{\vec a} \vert ^2$$ $$\mathbf{\vec a} · (\mathbf{\vec b} + \mathbf{\vec c}) = \mathbf{\vec a} · \mathbf{\vec b} + \mathbf{\vec a} · \mathbf{\vec c}$$ $$0 · \mathbf{\vec a} = 0$$ $$\mathbf{\vec a} · \mathbf{\vec b} = \mathbf{\vec b} · \mathbf{\vec a}$$ $$(s * \mathbf{\vec a}) · \mathbf{\vec b} = s * (\mathbf{\vec a} · \mathbf{\vec b}) = \mathbf{\vec a} · (s * \mathbf{\vec b})$$ ### Vector cross product The cross product takes two vectors and produces a third vector that is orthogonal to both.
Figure (14): Calculating the cross product of two vectors.
For example, if you have two vectors lying on the World xy-plane, then their cross product is a vector perpendicular to the xy-plane going either in the positive or negative World z-axis direction. For example: $$\mathbf{\vec a} = <3, 1, 0>$$ $$\mathbf{\vec b} = <1, 2, 0>$$ $$\mathbf{\vec a} × \mathbf{\vec b} = < (1 * 0 – 0 * 2), (0 * 1 - 3 * 0), (3 * 2 - 1 * 1) > $$ $$\mathbf{\vec a} × \mathbf{\vec b} = <0, 0, 5>$$ The vector $$\vec a \times \vec b$$ is orthogonal to both $$\vec a$$ and $$\vec b$$. You will probably never need to calculate a cross product of two vectors by hand, but if you are curious about how it is done, continue reading; otherwise you can safely skip this section. The cross product $$a × b$$ is defined using determinants. Here is a simple illustration of how to calculate a determinant using the standard basis vectors: $$ \color {red}{i} = <1, 0, 0>$$ $$ \color {blue}{j} = <0,1, 0>$$ $$ \color {green}{k} = <0, 0, 1>$$ The cross product of the two vectors $$\mathbf{\vec a} = $$ and $$\mathbf{\vec b} = $$ is calculated as follows using the above diagram: $$\mathbf{\vec a} × \mathbf{\vec b} = \color {red}{i (a_2 * b_3)} + \color {blue}{ j (a_3 * b_1)} + \color {green}{k(a_1 * b_2)} - \color {green}{k (a_2 * b_1)} - \color {red}{i (a_3 * b_2)} -\color {blue}{ j (a_1 * b_3)}$$ $$\mathbf{\vec a} × \mathbf{\vec b} = \color {red}{i (a_2 * b_3 - a_3 * b_2)} + \color {blue}{j (a_3 * b_1 - a_1 * b_3)} +\color {green}{k (a_1 * b_2 - a_2 * b_1)}$$ $$\mathbf{\vec a} × \mathbf{\vec b} = <\color {red}{a_2 * b_3 – a_3 * b_2}, \color {blue}{a_3 * b_1 - a_1 * b_3}, \color {green}{a_1 * b_2 - a_2 * b_1} >$$ ### Cross product and angle between vectors There is a relationship between the angle between two vectors and the length of their cross product vector. The smaller the angle (smaller sine); the shorter the cross product vector will be. The order of operands is important in vectors cross product. For example: $$\mathbf{\vec a} = <1, 0, 0>$$ $$\mathbf{\vec b} = <0, 1, 0>$$ $$\mathbf{\vec a} × \mathbf{\vec b} = <0, 0, 1>$$ $$\mathbf{\vec b} × \mathbf{\vec a} = <0, 0, -1>$$
Figure (15): The relationship between the sine of the angle between two vectors and the length of their cross product vector.
In Rhino's right-handed system, the direction of $$\mathbf{\vec a} × \mathbf{\vec b}$$ is given by the right-hand rule (where $$\mathbf{\vec a}$$ = index finger, $$\mathbf{\vec b}$$ = middle finger, and $$\mathbf{\vec a} × \mathbf{\vec b}$$ = thumb). In general, for any pair of 3-D vectors $$\mathbf{\vec a}$$ and $$\mathbf{\vec b}$$: $$\vert \mathbf{\vec a} × \mathbf{\vec b} \vert = \vert \mathbf{\vec a} \vert \vert \mathbf{\vec b} \vert sin(ө)$$ Where: $$ө$$ is the angle included between the position vectors of $$\mathbf{\vec a}$$ and $$\mathbf{\vec b}$$ If a and b are unit vectors, then we can simply say that the length of their cross product equals the sine of the angle between them. In other words: $$\vert \mathbf{\vec a} × \mathbf{\vec b} \vert = sin(ө)$$ The cross product between two vectors helps us determine if two vectors are parallel. This is because the result is always a zero vector. Vectors $$\vec a$$ and $$\vec b$$ are parallel if, and only if, $$a \times b = 0$$. ### Cross product properties If $$\mathbf{\vec a}$$, $$\mathbf{\vec b}$$, and $$\mathbf{\vec c}$$ are vectors, and $$s$$ is a number, then: $$\mathbf{\vec a} × \mathbf{\vec b} = -\mathbf{\vec b} × \mathbf{\vec a}$$ $$(s * \mathbf{\vec a}) × \mathbf{\vec b} = s * (\mathbf{\vec a} × \mathbf{\vec b}) = \mathbf{\vec a} × (s * \mathbf{\vec b})$$ $$\mathbf{\vec a} × (\mathbf{\vec b} + \mathbf{\vec c}) = \mathbf{\vec a} × \mathbf{\vec b} + \mathbf{\vec a} × \mathbf{\vec c}$$ $$(\mathbf{\vec a} + \mathbf{\vec b}) × \mathbf{\vec c} = \mathbf{\vec a} × \mathbf{\vec c} + \mathbf{\vec b} × \mathbf{\vec c}$$ $$\mathbf{\vec a} · (\mathbf{\vec b} × \mathbf{\vec c}) = (\mathbf{\vec a} × \mathbf{\vec b}) · \mathbf{\vec c}$$ $$\mathbf{\vec a} × (\mathbf{\vec b} × \mathbf{\vec c}) = (\mathbf{\vec a} · \mathbf{\vec c}) * \mathbf{\vec b} – (\mathbf{\vec a} · \mathbf{\vec b}) * \mathbf{\vec c}$$ ## 1.3 Vector equation of line The vector line equation is used in 3D modeling applications and computer graphics.
Figure (16): Vector equation of a line.
For example, if we know the direction of a line and a point on that line, then we can find any other point on the line using vectors, as in the following: $$\overline{L} = line$$ $$\mathbf{\vec v} = $$ line direction unit vector $$Q = (x_0, y_0, z_0)$$ line position point $$P = (x, y, z)$$ any point on the line We know that: $$\mathbf{\vec a} = t *\mathbf{\vec v}$$ (2) $$\mathbf{\vec p} = \mathbf{\vec q} + \mathbf{\vec a}$$ (1) From 1 and 2: $$\mathbf{\vec p} = \mathbf{\vec q} + t * \mathbf{\vec v}$$ (3) However, we can write (3) as follows: $$ = + $$ $$ = $$ Therefore: $$x = x_0 + t * a$$ $$y = y_0 + t * b$$ $$z = z_0 + t * c$$ Which is the same as: $$P = Q + t * \mathbf{\vec v}$$ Given a point $$Q$$ and a direction $$\vec v$$ on a line, any point $$P$$ on that line can be calculated using the vector equation of a line $$P = Q + t * \vec v$$ where $$t$$ is a number. Another common example is to find the midpoint between two points. The following shows how to find the midpoint using the vector equation of a line: $$\mathbf{\vec q}$$ is the position vector for point $$Q$$ $$\mathbf{\vec p}$$ is the position vector for point $$P$$ $$\mathbf{\vec a}$$ is the vector going from $$Q \rightarrow P$$ From vector subtraction, we know that: $$\mathbf{\vec a} = \mathbf{\vec p} - \mathbf{\vec q}$$ From the line equation, we know that: $$M = Q + t * \mathbf{\vec a}$$ And since we need to find midpoint, then: $$t = 0.5$$ Hence we can say: $$M = Q + 0.5 * \mathbf{\vec a}$$
Figure (17): Find the midpoint between two input points.
In general, you can find any point between $$Q$$ and $$P$$ by changing the $$t$$ value between 0 and 1 using the general equation: $$M = Q + t * (P - Q)$$ Given two points $$Q$$ and $$P$$, any point $$M$$ between the two points is calculated using the equation $$M = Q + t * (P - Q)$$ where t is a number between 0 and 1. ## 1.4 Vector equation of a plane One way to define a plane is when you have a point and a vector that is perpendicular to the plane. That vector is usually referred to as normal to the plane. The normal points in the direction above the plane. One example of how to calculate a plane normal is when we know three non-linear points on the plane. In Figure (16), given: $$A$$ = the first point on the plane $$B$$ = the second point on the plane $$C$$ = the third point on the plane And: $$\mathbf{\vec a} $$ = a position vector of point $$A$$ $$\mathbf{\vec b}$$ = a position vector of point $$B$$ $$\mathbf{\vec c}$$ = a position vector of point $$C$$ We can find the normal vector $$\mathbf{\vec n}$$ as follows: $$\mathbf{\vec n} = (\mathbf{\vec b} - \mathbf{\vec a}) × (\mathbf{\vec c} - \mathbf{\vec a})$$
Figure (18): Vectors and planes
We can also derive the scalar equation of the plane using the vector dot product: $$\mathbf{\vec n} · (\mathbf{\vec b} - \mathbf{\vec a}) = 0$$ If: $$\mathbf{\vec n} = $$ $$\mathbf{\vec b} = $$ $$ \mathbf{\vec a} = $$ Then we can expand the above: $$ · = 0$$ Solving the dot product gives the general scalar equation of a plane: $$a * (x - x_0) + b * (y - y_0) + c * (z - z_0) = 0$$ ## 1.5 Tutorials All the concepts we reviewed in this chapter have a direct application to solving common geometry problems encountered when modeling. The following are stepbystep tutorials that use the concepts learned in this chapter using Rhinoceros and Grasshopper (GH). ### 1.5.1 Face direction Given a point and a surface, how can we determine whether the point is facing the front or back side of that surface? **Input:** 1. a surface 2. a point **Parameters:** The face direction is defined by the surface normal direction. We will need the following information: * The surface normal direction at a surface location closest to the input point. * A vector direction from the closest point to the input point. Compare the above two directions, if going the same direction, the point is facing the front side, otherwise it is facing the back. **Solution:** 1\. Find the closest point location on the surface relative to the input point using the Pull component. This will give us the uv location of the closest point, which we can then use to evaluate the surface and find its normal direction. 2\. We can now use the closest point to draw a vector going towards the input point. We can also draw: 3\. We can compare the two vectors using the dot product. If the result is positive, the point is in front of the surface. If the result is negative, the point is behind the surface. The above steps can also be solved using other scripting languages. Using the Grasshopper VB component: ```vb Private Sub RunScript(ByVal pt As Point3d, ByVal srf As Surface, ByRef A As Object) 'Declare variables Dim u, v As Double Dim closest_pt As Point3d 'get closest point u, v srf.ClosestPoint(pt, u, v) 'get closest point closest_pt = srf.PointAt(u, v) 'calculate direction from closest point to test point Dim dir As New Vector3d(pt - closest_pt) 'calculate surface normal Dim normal = srf.NormalAt(u, v) 'compare the two directions using the dot product A = dir * normal End Sub ``` Using the Grasshopper Python component with RhinoScriptSyntax: ```python import rhinoscriptsyntax as rs #import RhinoScript library #find the closest point u, v = rs.SurfaceClosestPoint(srf, pt) #get closest point closest_pt = rs.EvaluateSurface(srf, u, v) #calculate direction from closest point to test point dir = rs.PointCoordinates(pt) - closest_pt #calculate surface normal normal = rs.SurfaceNormal(srf, [u, v]) #compare the two directions using the dot product A = dir * normal ``` Using the Grasshopper Python component with RhinoCommon only: ```python #find the closest point found, u, v = srf.ClosestPoint(pt) if found: #get closest point closest_pt = srf.PointAt(u, v) #calculate direction from closest point to test point dir = pt - closest_pt #calculate surface normal normal = srf.NormalAt(u, v) #compare the two directions using the dot product A = dir * normal ``` Using the Grasshopper C# component: ```cs private void RunScript(Point3d pt, Surface srf, ref object A) { //Declare variables double u, v; Point3d closest_pt; //get closest point u, v srf.ClosestPoint(pt, out u, out v); //get closest point closest_pt = srf.PointAt(u, v); //calculate direction from closest point to test point Vector3d dir = pt - closest_pt; //calculate surface normal Vector3d normal = srf.NormalAt(u, v); //compare the two directions using the dot product A = dir * normal; } ``` ### 1.5.2 Exploded box The following tutorial shows how to explode a polysurface. This is what the final exploded box looks like: **Input:** Identify the input, which is a box. We will use the Box parameter in GH: **Parameters:** * Think of all the parameters we need to know in order to solve this tutorial. * The center of explosion. * The box faces we are exploding. * The direction in which each face is moving. Once we have identified the parameters, it is a matter of putting it together in a solution by piecing together the logical steps to reach an answer. **Solution:** 1\. Find the center of the box using the **Box Properties** component in GH: 2\. Extract the box faces with the **Deconstruct Brep** component: 3\. The direction we move the faces is the tricky part. We need to first find the center of each face, and then define the direction from the center of the box towards the center of each face as follows: 4\. Once we have all the parameters scripted, we can use the **Move** component to move the faces in the appropriate direction. Just make sure to set the vectors to the desired amplitude, and you will be good to go. The above steps can also be solved using VB script, C# or Python. Following is the solution using these scripting languages. Using the Grasshopper VB component: ```vb Private Sub RunScript(ByVal box As Brep, ByVal dis As Double, ByRef A As Object) 'get the brep center Dim area As Rhino.Geometry.AreaMassProperties area = Rhino.Geometry.AreaMassProperties.Compute(box) Dim box_center As Point3d box_center = area.Centroid 'get a list of faces Dim faces As Rhino.Geometry.Collections.BrepFaceList = box.Faces 'decalre variables Dim center As Point3d Dim dir As Vector3d Dim exploded_faces As New List( Of Rhino.Geometry.Brep ) Dim i As Int32 'loop through all faces For i = 0 To faces.Count() - 1 'extract each of the face Dim extracted_face As Rhino.Geometry.Brep = box.Faces.ExtractFace(i) 'get the center of each face area = Rhino.Geometry.AreaMassProperties.Compute(extracted_face) center = area.Centroid 'calculate move direction (from box centroid to face center) dir = center - box_center dir.Unitize() dir *= dis 'move the extracted face extracted_face.Transform(Transform.Translation(dir)) 'add to exploded_faces list exploded_faces.Add(extracted_face) Next 'assign exploded list of faces to output A = exploded_faces End Sub ``` Using the Grasshopper Python component with RhinoCommon: ```python import Rhino #get the brep center area = Rhino.Geometry.AreaMassProperties.Compute(box) box_center = area.Centroid #get a list of faces faces = box.Faces #decalre variables exploded_faces = [] #loop through all faces for i, face in enumerate(faces): #get a duplicate of the face extracted_face = faces.ExtractFace(i) #get the center of each face area = Rhino.Geometry.AreaMassProperties.Compute(extracted_face) center = area.Centroid #calculate move direction (from box centroid to face center) dir = center - box_center dir.Unitize() dir *= dis #move the extracted face move = Rhino.Geometry.Transform.Translation(dir) extracted_face.Transform(move) #add to exploded_faces list exploded_faces.append(extracted_face) #assign exploded list of faces to output A = exploded_faces ``` Using the Grasshopper C# component: ```cs private void RunScript(Brep box, double dis, ref object A) { //get the brep center Rhino.Geometry.AreaMassProperties area = Rhino.Geometry.AreaMassProperties.Compute(box); Point3d box_center = area.Centroid; //get a list of faces Rhino.Geometry.Collections.BrepFaceList faces = box.Faces; //decalre variables Point3d center; Vector3d dir; List exploded_faces = new List(); //loop through all faces for( int i = 0; i < faces.Count(); i++ ) { //extract each of the face Rhino.Geometry.Brep extracted_face = box.Faces.ExtractFace(i); //get the center of each face area = Rhino.Geometry.AreaMassProperties.Compute(extracted_face); center = area.Centroid; //calculate move direction (from box centroid to face center) dir = center - box_center; dir.Unitize(); dir *= dis; //move the extracted face extracted_face.Transform(Transform.Translation(dir)); //add to exploded_faces list exploded_faces.Add(extracted_face); } //assign exploded list of faces to output A = exploded_faces; } ``` ### 1.5.3 Tangent spheres This tutorial will show how to create two tangent spheres between two input points. This is what the result looks like: **Input:** Two points ($$A$$ and $$B$$) in the 3-D coordinate system. Parameters: The following is a diagram of the parameters that we will need in order to solve the problem: $$A$$ tangent point $$D$$ between the two spheres, at some $$t$$ parameter (0-1) between points $$A$$ and $$B$$. * The center of the first sphere or the midpoint $$C1$$ between $$A$$ and $$D$$. * The center of the second sphere or the midpoint $$C2$$ between $$D$$ and $$B$$. * The radius of the first sphere $$(r1)$$ or the distance between $$A$$ and $$C1$$. * The radius of the second sphere $$(r2)$$ or the distance between $$D$$ and $$C2$$. **Solution:** 1\. Use the **Expression** component to define point $$D$$ between $$A$$ and $$B$$ atsome parameter $$t$$. The expression we will use is based onthe vector equation of a line: $$D = A + t*(B-A)$$ $$B-A$$ : is the vector that goes from point $$A$$ to point $$B$$ using the vector subtraction operation. $$t*(B-A)$$ : where $$t$$ is between 0 and 1 to get us a location on the vector. $$A+t*(B-A)$$ : gets apoint on the vector between A and B. 2\. Use the Expression component to also define the mid points $$C1$$ and$$C2$$ . 3\. The first sphere radius $$(r1)$$ and the second sphere radius $$(r2)$$ can be calculated using the **Distance** component. 4\. The final step involves creating the sphere from a base plane and radius. We need to make sure the origins are hooked to $$C1$$ and $$C2$$ and the radius from $$r1$$ and $$r2$$. **Using the Grasshopper VB component:** ```vb Private Sub RunScript(ByVal A As Point3d, ByVal B As Point3d, ByVal t As Double, ByRef S1 As Object, ByRef S2 As Object) 'declare variables Dim D, C1, C2 As Rhino.Geometry.Point3d Dim r1, r2 As Double 'find a point between A and B D = A + t * (B - A) 'find mid point between A and D C1 = A + 0.5 * (D - A) 'find mid point between D and B C2 = D + 0.5 * (B - D) 'find spheres radius r1 = A.DistanceTo(C1) r2 = B.DistanceTo(C2) 'create spheres and assign to output S1 = New Rhino.Geometry.Sphere(C1, r1) S2 = New Rhino.Geometry.Sphere(C2, r2) End Sub ``` Using Python component: ```python import Rhino #find a point between A and B D = A + t * (B - A) #find mid point between A and D C1 = A + 0.5 * (D - A) #find mid point between D and B C2 = D + 0.5 * (B - D) #find spheres radius r1 = A.DistanceTo(C1) r2 = B.DistanceTo(C2) #create spheres and assign to output S1 = Rhino.Geometry.Sphere(C1, r1) S2 = Rhino.Geometry.Sphere(C2, r2) ``` Using the Grasshopper C# component: ```cs private void RunScript(Point3d A, Point3d B, double t, ref object S1, ref object S2) { //declare variables Rhino.Geometry.Point3d D, C1, C2; double r1, r2; //find a point between A and B D = A + t * (B - A); //find mid point between A and D C1 = A + 0.5 * (D - A); //find mid point between D and B C2 = D + 0.5 * (B - D); //find spheres radius r1 = A.DistanceTo(C1); r2 = B.DistanceTo(C2); //create spheres and assign to output S1 = new Rhino.Geometry.Sphere(C1, r1); S2 = new Rhino.Geometry.Sphere(C2, r2); } ``` ## Download Sample Files Download the [ ](https://www.rhino3d.com/download/rhino/6/essentialmathematics/) [download samplesand tutorials](https://www.rhino3d.com/download/rhino/6/essentialmathematics/) archive, containing all the example Grasshopper and code files in this guide. ## Next Steps Now that you know vector math, check out the [Matrices and Transformations](/guides/general/essential-mathematics/matrices-transformations/) guide to learn more about the moving, rotating and scaling objects.. -------------------------------------------------------------------------------- # 1 What's it all about? Source: https://developer.rhino3d.com/en/guides/rhinopython/primer-101/1-whats-it-all-about/ ## 1.1 Macros Rhinoceros is based on a command-line interface. This means you can control it by using only the keyboard. You type in the commands and the program will execute them. Ever since the advent of the mouse, a user interface which is purely command-line based is considered to be primitive, and rightly so. Instead of typing: ``` Line 0,0,0 10,0,0 ``` you can also click on the Line button and then twice in the viewport to define the starting and ending points of a line-curve. Because of this second (graphical) interface some people have done away with the command-line entirely. Emotions run high on the subject; some users are command-line fanatics, others use only toolbars and menus. Programmers have no emotions in this respect, they are all wedded to the command-line. It’s no use programming the mouse to go to a certain coordinate and then simulate a mouse click, that is just plain silly. Programmers pump text into Rhino and they expect to get text in return. The lowest form of programming in Rhino is using macros. I do not wish to offend those of you who write macros for a living, but it cannot be denied that it is a very primitive way to automate processes. I shall only briefly pause at the subject of macros, partly so we know which is which and partly because we might at some point simulate macros using RhinoScriptSyntax in Python. A macro is a prerecorded list of orders for Rhino to execute. The *_Line* command at the top of this page is an example of a very simple macro. If your job is to open Rhino files, add a line from 0,0,0 to 10,0,0 to each one and save the file again, you would probably get very tired very quickly from typing `_Line w0,0,0 w10,0,0` six times a minute. Enter macros. Macros allow you to automate tasks you would normally do by hand but not by brain. Macros cannot be made smart, nor do they react to the things they help create. They’re a bit like traffic wardens in that respect. An example of a more sophisticated macro would be: ``` _SelNone _Polygon _NumSides=6 w0,0,0 w10,0,0 _SelLast -_Properties _Object _Name RailPolygon _Enter _Enter _SelNone _Polygon _NumSides=6 w10,0,0 w12,0,0 _SelLast _Rotate3D w0,0,0 w10,0,0 90 -_Properties _Object _Name ProfilePolygon _Enter _Enter _SelNone -_Sweep1 -_SelName RailPolygon -_SelName ProfilePolygon _Enter _Enter Closed=Yes _Enter ``` The above code will create the same hexagonal torus over and over again. It might be useful, but it's not flexible. You can type the above text into the command-line by hand, or you can put it into a button. You can even copy-paste the text directly into Rhino. Incidentally, the underscores before all the command names are due to Rhino localization. Using underscores will force Rhino to use English command names instead of -say- Italian or Japanese or whatever the custom setting is. You should always force English command names since that is the only guarantee that your code will work on all copies of Rhino worldwide. The hyphen in front of the *_Properties* and *_Sweep1* command is used to suppress dialog boxes. If you take the hyphens out you will no longer be able to change the way a command works through the command -line. There’s no limit to how complex a macro can become, you can keep adding commands without restrictions, but you’ll never be able to get around the limitations that lie at the heart of macros. ## 1.2 Scripts The limitations of macros have led to the development of scripting languages. Scripts are something halfway between macros and true (compiled) programs and plug-ins. Unlike macros they can perform mathematical operations, evaluate variable conditions, respond to their environment and communicate with the user. Unlike programs they do not need to be compiled prior to running. Rhinoceros implements the standard ‘Microsoft® Visual Basic® Scripting Edition’ language, as well as the Python Programming language. This primer will introduce the Python Programming Language and how to utilize its functionality within Rhinoceros. Scripts, then, are text files which are interpreted one line at a time. But here’s the interesting part; unlike macros, scripts have control over which line is executed next. This flow control enables the script to skip certain instructions or to repeat others. Flow control is achieved by what is called "conditional evaluation" and we must familiarize ourselves with the language rules of Python before we can take advantage of flow control. The language rules are usually referred to as the syntax and they indicate what is and isn’t valid: 1. "There is no apple cake here." » valid 1. "There is here no apple cake"» invalid 2. "Here, there is no apple cake."» valid 3. "There is no Apfelstrudel here."» invalid The above list is a validity check for English syntax rules. The first and third lines are proper English and the others are not. However, there are certain degrees of wrong. Nobody will misunderstand the second line just because the word order is wrong. The forth line is already a bit harder since it features a word from a different language. Although most of us are smart enough to understand all four lines, a computer is not. Python is a wonderful language for beginners or advanced programmers. It offers a simple and efficient syntax as well as powerful programming functions, object-oriented capabilities and a large fan-base with user-built libraries. Also, since Rhino Python is available on both Windows and Mac, the exact same python scripts will run on both versions of Rhino! Don't get too excited yet - will get more of the details in the following sections! ## 1.3 Running Scripts There are several ways to run scripts in Rhino, each has its own (dis)advantages. You could store scripts as external text files and have Rhino load them for you whenever you want to run them. You could also use Rhino's in-build script editor which means you can run the Scripts directly from the editor. The last option is to embed scripts in toolbar buttons, which makes it very hard to edit them, but much easier to distribute them. Throughout this book, I will use the in-build editor method. I find this to be the best way to work on simple scripts. In order to run a script via the in-build editor, use the *_ScriptEditor* command to activate it, then type in your script and press the Run button: All the example code in this primer can be copy-pasted directly into the *_ScriptEditor* dialog. ## Next Steps Now that you know what a scripting language is, check out the [Python Essentials](/guides/rhinopython/primer-101/2-python-essentials/) guide to learn more about the Python language. -------------------------------------------------------------------------------- # 1 What's it all about? Source: https://developer.rhino3d.com/en/guides/rhinoscript/primer-101/1-whats-it-all-about/ ## 1.1 Macros Rhinoceros is based on a command-line interface. This means you can control it by using only the keyboard. You type in the commands and the program will execute them. Ever since the advent of the mouse, a user interface which is purely command-line based is considered to be primitive, and rightly so. Instead of typing: ``` Line 0,0,0 10,0,0 ``` you can equally well click on the Line button and then twice in the viewport to define the starting and ending points of a line-curve. Because of this second (graphical) interface some people have done away with the command-line entirely. Emotions run high on the subject; some users are command-line fanatics, others use only toolbars and menus. Programmers have no emotions in this respect, they are all wedded to the command-line. It’s no use programming the mouse to go to a certain coordinate and then simulate a mouse click, that is just plain silly. Programmers pump text into Rhino and they expect to get text in return. The lowest form of programming in Rhino is using macros. I do not wish to offend those of you who write macros for a living, but it cannot be denied that it is a very primitive way to automate processes. I shall only briefly pause at the subject of macros, partly so we know which is which and partly because we might at some point simulate macros using RhinoScript. A macro is a prerecorded list of orders for Rhino to execute. The _Line command at the top of this page is an example of a very simple macro. If your job is to open Rhino files, add a line from 0,0,0 to 10,0,0 to each one and save the file again, you would probably get very tired very quickly from typing "_Line w0,0,0 w10,0,0" six times a minute. Enter macros. Macros allow you to automate tasks you would normally do by hand but not by brain. Macros cannot be made smart, nor do they react to the things they help create. They’re a bit like traffic wardens in that respect. An example of a more sophisticated macro would be: ``` _SelNone _Polygon _NumSides=6 w0,0,0 w10,0,0 _SelLast -_Properties _Object _Name RailPolygon _Enter _Enter _SelNone _Polygon _NumSides=6 w10,0,0 w12,0,0 _SelLast _Rotate3D w0,0,0 w10,0,0 90 -_Properties _Object _Name ProfilePolygon _Enter _Enter _SelNone -_Sweep1 -_SelName RailPolygon -_SelName ProfilePolygon _Enter _Simplify=None Enter ``` The above code will create the same hexagonal torus over and over again. It might be useful, but it's not flexible. You can type the above text into the command-line by hand, or you can put it into a button. You can even copy-paste the text directly into Rhino. Incidentally, the underscores before all the command names are due to Rhino localization. Using underscores will force Rhino to use English command names instead of -say- Italian or Japanese or whatever the custom setting is. You should always force English command names since that is the only guarantee that your code will work on all copies of Rhino worldwide. The hyphen in front of the _Properties and _Sweep1 command is used to suppress dialog boxes. If you take the hyphens out you will no longer be able to change the way a command works through the command-line. There’s no limit to how complex a macro can become, you can keep adding commands without restrictions, but you’ll never be able to get around the limitations that lie at the heart of macros. ## 1.2 Scripts The limitations of macros have led to the development of scripting languages. Scripts are something halfway between macros and true (compiled) programs and plug-ins. Unlike macros they can perform mathematical operations, evaluate variable conditions, respond to their environment and communicate with the user. Unlike programs they do not need to be compiled prior to running. Rhinoceros implements the standard ‘Microsoft® Visual Basic® Scripting Edition’ language (more commonly known as VBScript) which means that everything that is true of VBScript is also true of RhinoScript. Scripts, then, are text files which are interpreted one line at a time. But here’s the interesting part; unlike macros, scripts have control over which line is executed next. This flow control enables the script to skip certain instructions or to repeat others. Flow control is achieved by what is called "conditional evaluation" and we must familiarize ourselves with the language rules of VBScript before we can take advantage of flow control. VBScript is a very forgiving programming language. ‘Forgiving’ in this sense indicates that the language rules are fairly loose. The language rules are usually referred to as the syntax and they indicate what is and isn’t valid: ``` "There is no apple cake here." » valid "There is here no apple cake" » invalid "Here, there is no apple cake." » valid "There is no Apfelstrudel here." » invalid ``` The above list is a validity check for English syntax rules. The first and third lines are proper English and the others are not. However, there are certain degrees of wrong. Nobody will misunderstand the second line just because the word order is wrong. The forth line is already a bit harder since it features a word from a different language. Although most of us are smart enough to understand all four lines, a computer is not. I mentioned before that VBScript is a forgiving language. That means that it can intercept small errors in the syntax. Before we can start doing anything with Rhino, we must have a good understanding of VBScript syntax. ## 1.3 Running Scripts There are several ways to run scripts in Rhino, each has its own (dis)advantages. You could store scripts as external text files and have Rhino load them for you whenever you want to run them. You could also use Rhino's in-build script editor which means you can run the Scripts directly from the editor. The last option is to embed scripts in toolbar buttons, which makes it very hard to edit them, but much easier to distribute them. Throughout this book, I will use the in-build editor method. I find this to be the best way to work on simple scripts. (Once a script becomes fairly complex and long, it's probably better to switch to an external editor.) In order to run a script via the in-build editor, Use the _EditScript command to activate it, then type in your script and press the Run button: All the example code in this primer can be copy-pasted directly into the EditScript dialog. ## Next Steps Now that you know what a scripting language is, check out the [Scripting Essentials](/guides/rhinoscript/primer-101/2-vbscript-essentials/) guide to learn more about the RhinoScript language. -------------------------------------------------------------------------------- # 2 Matrices and Transformations Source: https://developer.rhino3d.com/en/guides/general/essential-mathematics/matrices-transformations/ This guide reviews matrix operations and transformations. *Transformations* refer to operations such as moving (also called *translating*), rotating, and scaling objects. They are stored in 3 D programming using matrices, which are nothing but rectangular arrays of numbers. Multiple transformations can be performed very quickly using matrices. It turns out that a [4x4] matrix can represent all transformations. Having a unified matrix dimension for all transformations saves calculation time. $$\begin{array}{rcc} \mbox{matrix}&\begin{array}{cccc} c1& c2&c3&c4\end{array}\\\begin{array}{c}row(1)\\row(2)\\row(3)\\row(4)\end{array}& \left[\begin{array}{cr} +&+&+&+\\ +&+&+&+\\ +&+&+&+\\ +&+&+&+\end{array}\right] \end{array}$$ ## 2.1 Matrix operations The one operation that is most relevant in computer graphics is matrix multiplication. We will explain it with some detail. ### Matrix multiplication Matrix multiplication is used to apply transformations to geometry. For example if we have a point and would like to rotate it around some axis, we use a rotation matrix and multiply it by the point to get the new rotated location. $$\begin{array}{ccc} \text{rotate matrix} & \text{input point} & \text{rotate point}\\\begin{bmatrix}a & b & c & d \\e & f & g & h \\i & j & k & l \\0 & 0 & 0 & 1 \\\end{bmatrix}& \cdot\begin{bmatrix}x \\y\\z\\1 \\\end{bmatrix}&= \begin{bmatrix}x' \\y'\\z'\\1 \\\end{bmatrix}\end{array}$$ Most of the time, we need to perform multiple transformations on the same geometry. For example, if we need to move and rotate a thousand points, we can use either of the following methods. **Method 1** 1. Multiply the move matrix by 1000 points to move the points. 2. Multiply the rotate matrix by the resulting 1000 points to rotate the moved points. Number of operations = **2000**. **Method 2** 1. Multiply the rotate and move matrices to create a combined transformation matrix. 2. Multiply the combined matrix by 1000 points to move and rotate in one step. Number of operations = **1001**. Notice that method 1 takes almost twice the number of operations to achieve the same result. While method 2 is very efficient, it is only possible if both the move and rotate matrices are $$[4 \times 4]$$. This is why in computer graphics a $$[4 \times 4]$$ matrix is used to represent all transformations, and a $$[4 \times 1]$$ matrix is used to represent points. Three-dimensional modeling applications provide tools to apply transformations and multiply matrices, but if you are curious about how to mathematically multiply matrices, we will explain a simple example. In order to multiply two matrices, they have to have matching dimensions. That means the number of columns in the first matrix must equal the number of rows of the second matrix. The resulting matrix has a size equal to the number of rows from the first matrix and the number of columns from the second matrix. For example, if we have two matrices, $$M$$ and $$P$$, with dimensions equal to $$[4\times 4]$$ and $$[4 \times 1]$$ respectively, then there resulting multiplication matrix $$M · P$$ has a dimension equal to $$[4 \times 1]$$ as shown in the following illustration: $$\begin{array}{ccc} M & P & P' \\\begin{bmatrix}\color{red}{a} & \color{red}{b} & \color{red}{c} & \color{red}{d} \\e & f & g & h \\i & j & k & l \\0 & 0 & 0 & 1 \\\end{bmatrix}& \cdot\begin{bmatrix}\color{red}{x} \\\color{red}{y} \\\color{red}{z} \\\color{red}{1} \\\end{bmatrix}&= \begin{bmatrix}\color{red}{x'=a*x+b*y+c*z+d*1}\\y'=e*x+f*y+g*z+h*1\\z'=i*x+j*y+k*z+l*1 \\1=0*x+0*y+0*z+1*1\\\end{bmatrix}\end{array}$$ ### Identity matrix The identity matrix is a special matrix where all diagonal components equal 1 and the rest equal 0. The main property of the identity matrix is that if it is multiplied by any other matrix, the values multiplied by zero do not change. ## 2.2 Transformation operations Most transformations preserve the parallel relationship among the parts of the geometry. For example collinear points remain collinear after the transformation. Also points on one plane stay coplanar after transformation. This type of transformation is called an *affine* transformation. ### Translation (move) transformation Moving a point from a starting position by certain a vector can be calculated as follows: $$P' = P + \mathbf{\vec v}$$ Suppose:   $$P(x,y,z)$$ is a given point   $$\mathbf{\vec v}=$$ is a translation vector Then:   $$P'(x) = x + a$$   $$P'(y) = y + b$$   $$P'(z) = z + c$$ Points are represented in a matrix format using a [4x1] matrix with a 1 inserted in the last row. For example the point P(x,y,z) is represented as follows: $$\begin{bmatrix}x\\y\\z\\1\\\end{bmatrix}$$ Using a $$[4 \times 4]$$ matrix for transformations (what is called a homogenous coordinate system), instead of a $$[3 \times 3]$$ matrices, allows representing all transformations including translation. The general format for a translation matrix is: $$\begin{bmatrix}1 & 0 & 0 & \color{red}{a1} \\0 & 1 & 0 & \color{red}{a2} \\0 & 0 & 1 & \color{red}{a3} \\0 & 0 & 0 & 1 \\\end{bmatrix}$$ For example, to move point $$P(2,3,1)$$ by vector $$\vec v <2,2,2>$$, the new point location is: $$P’ = P + \mathbf{\vec v} = (2+2, 3+2, 1+2) = (4, 5, 3)$$ If we use the matrix form and multiply the translation matrix by the input point, we get the new point location as in the following: $$\begin{bmatrix}1 & 0 & 0 & 2 \\0 & 1 & 0 & 2 \\0 & 0 & 1 & 2 \\0 & 0 & 0 & 1 \\\end{bmatrix}\cdot\begin{bmatrix}2 \\3\\1\\1 \\\end{bmatrix}= \begin{bmatrix}(1*2 + 0*3 + 0*1 + 2*1) \\(0*2 + 1*3 + 0*1 + 2*1)\\(0*2 + 0*3 + 1*1 + 2*1)\\(0*2 + 0*3 + 0*1 + 1*1)\\\end{bmatrix}=\begin{bmatrix}4 \\5\\3\\1 \\\end{bmatrix}$$ Similarly, any geometry is translated by multiplying its construction points by the translation matrix. For example, if we have a box that is defined by eight corner points, and we want to move it 4 units in the x-direction, 5 units in the y-direction and 3 units in the z- direction, we must multiply each of the eight box corner points by the following translation matrix to get the new box. $$\begin{bmatrix}1 & 0 & 0 & 4\\ 0 & 1 & 0 & 5 \\0 & 0 & 1 & 3 \\0 & 0 & 0 & 1 \\\end{bmatrix}$$
Figure (19): Translate all box corner points.
### Rotation transformation This section shows how to calculate rotation around the z-axis and the origin point using trigonometry, and then to deduce the general matrix format for the rotation. Take a point on $$x,y$$ plane $$P(x,y)$$ and rotate it by angle($$b$$). From the figure, we can say the following:   $$x = d cos(a)$$ (1)   $$y = d sin(a)$$ (2)   $$x' = d cos(b+a)$$ (3)   $$y' = d sin(b+a)$$ (4) Expanding $$x$$' and $$y'$$ using trigonometric identities for the sine and cosine of the sum of angles:   $$x' = d cos(a)cos(b) - d sin(a)sin(b)$$ (5)   $$y' = d cos(a)sin(b) + d sin(a)cos(b)$$ (6) Using Eq 1 and 2:   $$x' = x cos(b) - y sin(b)$$   y' = x sin(b) + y cos(b) The rotation matrix around the **z-axis** looks like: $$\begin{bmatrix}\color{red}{\cos{b}} & \color{red}{-\sin{b}} & 0 & 0 \\\color{red}{\sin{b}} & \color{red}{\cos{b}} & 0 & 0 \\0 & 0 & 1 & 0 \\0 & 0 & 0 & 1 \\\end{bmatrix}$$ The rotation matrix around the **x-axis** by angle $$b$$ looks like: $$\begin{bmatrix}1 & 0 & 0 & 0 \\0 & \color{red}{\cos{b}} & \color{red}{-\sin{b}} & 0 \\0 & \color{red}{\sin{b}} & \color{red}{\cos{b}} & 0 \\0 & 0 & 0 & 1 \\\end{bmatrix}$$ The rotation matrix around the **y-axis** by angle $$b$$ looks like: $$\begin{bmatrix}\color{red}{\cos{b}} &0 & \color{red}{\sin{b}} & 0 \\0 & 1 & 0 & 0 \\\color{red}{-\sin{b}} & 0 &\color{red}{\cos{b}} & 0 \\0 & 0 & 0 & 1 \\\end{bmatrix}$$ For example, if we have a box and would like to rotate it 30 degrees, we need the following: 1\. Construct the 30-degree rotation matrix. Using the generic form and the cos and sin values of 30-degree angle, the rotation matrix will look like the following: $$\begin{bmatrix}0.87 & -0.5 & 0 & 0 \\0.5 & 0.87 & 0 & 0 \\0 & 0 & 1 & 0 \\0 & 0 & 0 & 1 \\\end{bmatrix}$$ 2\. Multiply the rotation matrix by the input geometry, or in the case of a box, multiply by each of the corner points to find the box's new location.
Figure (20): Rotate geometry.
### Scale transformation In order to scale geometry, we need a scale factor and a center of scale. The scale factor can be uniform scaling equally in x-, y-, and z-directions, or can be unique for each dimension. Scaling a point can use the following equation:   $$P' = ScaleFactor(S) * P$$ Or:   $$P'.x = Sx * P.x$$   $$P'.y = Sy * P.y$$   $$P'.z = Sz * P.z$$ This is the matrix format for scale transformation, assuming that the center of scale is the World origin point (0,0,0). $$\begin{bmatrix}\color{red}{Scale-x} & 0 & 0 & 0 \\0 & \color{red}{Scale-y} & 0 & 0 \\0 & 0 & \color{red}{Scale-z} & 0 \\0 & 0 & 0 & 1 \\\end{bmatrix}$$ For example, if we would like to scale a box by 0.25 relative to the World origin, the scale matrix will look like the following:
Figure (21): Scale geometry
### Shear transformation Shear in 3‑D is measured along a pair of axes relative to a third axis. For example, a shear along a z‑axis will not change geometry along that axis, but will alter it along x and y. Here are few examples of shear matrices: 1\. Shear in x and z, keeping the y-coordinate fixed: $$\begin{bmatrix}1.0 &\color{red}{0.5} & 0.0 & 0.0 \\ 0.0 & 1.0 & 0.0 & 0.0 \\ 0.0 & 0.0 & 1.0 & 0.0 \\ 0.0 & 0.0 & 0.0 & 1.0 \\\end{bmatrix}$$ $$\begin{bmatrix}1.0 & 0.0 & 0.0 & 0.0 \\ 0.0 & 1.0 & 0.0 & 0.0 \\ 0.0 &\color{red}{0.5} & 1.0 & 0.0 \\ 0.0 & 0.0 & 0.0 & 1.0 \\\end{bmatrix}$$ 2\. Shear in y and z, keeping the x-coordinate fixed: $$\begin{bmatrix}1.0 & \color{red}{0.5} & 0.0 & 0.0 \\ 0.0 & 1.0 & 0.0 & 0.0 \\ 0.0 & 0.0 & 1.0 & 0.0 \\ 0.0 & 0.0 & 0.0 & 1.0 \\\end{bmatrix}$$ $$\begin{bmatrix}1.0 & 0.0 & 0.0 & 0.0 \\ 0.0 & 1.0 & 0.0 & 0.0 \\ 0.0 & \color{red}{0.5} & 1.0 & 0.0 \\ 0.0 & 0.0 & 0.0 & 1.0 \\\end{bmatrix}$$ 3\. Shear in x and y, keeping the z-coordinate fixed: $$\begin{bmatrix}1.0 & 0.0 & \color{red}{0.5} & 0.0 \\ 0.0 & 1.0 & 0.0 & 0.0 \\ 0.0 & 0.0 & 1.0 & 0.0 \\ 0.0 & 0.0 & 0.0 & 1.0 \\\end{bmatrix}$$ $$\begin{bmatrix}1.0 & 0.0 & 0.0 & 0.0 \\ 0.0 & 1.0 & \color{red}{0.5} & 0.0 \\ 0.0 & 0.0 & 1.0 & 0.0 \\ 0.0 & 0.0 & 0.0 & 1.0 \\\end{bmatrix}$$ ### Mirror or reflection transformation The mirror transformation creates a reflection of an object across a line or a plane. 2-D objects are mirrored across a line, while 3-D objects are mirrored across a plane. Keep in mind that the mirror transformation flips the normal direction of the geometry faces.
Figure (23): Mirror matrix across World xy-plane. Face directions are flipped.
### Planar Projection transformation Intuitively, the projection point of a given 3-D point $$P(x,y,z)$$ on the world xy-plane equals $$P_{xy} (x,y,0)$$ setting the z value to zero. Similarly, a projection to xz-plane of point P is $$P_{xz}(x,0,z)$$. When projecting to yz-plane, $$P_{xz} = (0,y,z)$$. Those are called orthogonal projections. If we have a curve as an input, and we apply the planar projection transformation, we get a curve projected to that plane. The following shows an example of a curve projected to xy‑plane with the matrix format. Note: NURBS curves (explained in the next chapter) use control points to define curves. Projecting a curve amounts to projecting its control points. $$\begin{bmatrix}1.0 & 0.0 & 0.0 & 0.0 \\ 0.0 & 1.0 & 0.0 & 0.0 \\ 0.0 & 0.0 & \color{red}{0.0} & 0.0 \\ 0.0 & 0.0 & 0.0 & 1.0 \\\end{bmatrix}$$ $$\begin{bmatrix}1.0 & 0.0 & 0.0 & 0.0 \\ 0.0 & \color{red}{0.0} & 0.0 & 0.0 \\ 0.0 & 0.0 & 1.0 & 0.0 \\ 0.0 & 0.0 & 0.0 & 1.0 \\\end{bmatrix}$$ $$\begin{bmatrix} \color{red}{0.0} & 0.0 & 0.0 & 0.0 \\ 0.0 & 1.0 & 0.0 & 0.0 \\ 0.0 & 0.0 & 1.0 & 0.0 \\ 0.0 & 0.0 & 0.0 & 1.0 \\\end{bmatrix}$$ ## Download Sample Files Download the [ ](https://www.rhino3d.com/download/rhino/6/essentialmathematics/) [download samplesand tutorials](https://www.rhino3d.com/download/rhino/6/essentialmathematics/) archive, containing all the example Grasshopper and code files in this guide. ## Next Steps Now that you know more about matrices and trasnformasion, check out the [Parametric Curves and Surfaces](/guides/general/essential-mathematics/parametric-curves-surfaces/) guide to learn more about the detailed structure of NURBS curves and surfaces. -------------------------------------------------------------------------------- # 3 Parametric Curves and Surfaces Source: https://developer.rhino3d.com/en/guides/general/essential-mathematics/parametric-curves-surfaces/ This guide is an in-depth review of parametric curves with special focus on NURBS curves and the concepts of continuity and curvature. Suppose you travel every weekday from your house to your work. You leave at 8:00 in the morning and arrive at 9:00. At each point in time between 8:00 and 9:00, you would be at some location along the way. If you plot your location every minute during your trip, you can define the path between home and work by connecting the 60 points you plotted. Assuming you travel the exact same speed every day, at 8:00 you would be at home (start location), at 9:00 you would be at work (end location) and at 8:40 you would at the exact same location on the path as the 40th plot point. Congratulations, you have just defined your first parametric curve! You have used *time* as a *parameter* to define your path, and hence you can call your path curve a *parametric curve*. The time interval you spend from start to end (8 to 9) is called the curve *domain* or *interval*. In general, we can describe the $$x$$, $$y$$, and $$z$$ location of a parametric curve in terms of some parameter $$t$$ as follows:   $$x = x(t)$$   $$y = y(t)$$   $$z = z(t)$$ Where:   $$t$$ is a range of real numbers We saw earlier that the parametric equation of a line in terms of parameter $$t$$ is defined as:   $$x = x’ + t * a$$   $$y = y’ + t * b$$   $$z = z’ + t * c$$ Where:   $$x$$, $$y$$, and $$z$$ are functions of t where t represents a range of real numbers.   $$x’$$, $$y’$$, and $$z’$$ are the coordinates of a point on the line segment.   $$a$$, $$b$$, and $$c$$ define the slope of the line, such that the vector $$\mathbf{\vec v} $$ is parallel to the line. We can therefore write the parametric equation of a line segment using a $$t$$ parameter that ranges between two real number values $$t0$$, $$t1$$ and a unit vector $$\mathbf{\vec v}$$ that is in the direction of the line as follows: $$P = P’ + t * \mathbf{\vec v}​$$ Another example is a circle. The parametric equation of the circle on the xy-plane with a center at the origin (0,0) and an angle parameter $$t$$ ranging between $$0$$ and $$2π$$ radians is:   $$x = r \dot cos(t)$$   $$y = r \dot sin(t)$$ We can derive the general equation of a circle for the parametric one as follows:   $$ x/r = cos(t)$$   $$y/r = sin(t)$$ And since:   $$cos(t)^2 + sin(t)^2 = 1$$ (Pythagorean identity) Then:   $$(x/r)^2 + (y/r)^2 = 1$$ , or   $$x^2 + y^2 = r^2$$ ## 3.1 Parametric curves ### Curve parameter A parameter on a curve represents the address of a point on that curve. As mentioned before, you can think of a parametric curve as a path traveled between two points in a certain amount of time, traveling at a fixed or variable speed. If traveling takes $$T$$ amount of time, then the parameter t represents a time within $$T$$ that translates to a location (point) on the curve. If you have a straight path (line segment) between the two points $$A$$ and $$B$$, and $$\mathbf{\vec v}$$ were a vector from $$A$$ to $$B$$ ($$\mathbf{\vec v} = B - A$$), then you can use the parametric line equation to find all points $$M$$ between $$A$$ and $$B$$ as follows:   $$M = A + t*(B-A)$$ Where:   $$t$$ is a value between 0 and 1. The range of t values, 0 to 1 in this case, is referred to as the curve domain or interval. If t was a value outside the domain (less that 0 or more than 1), then the resulting point $$M$$ will be outside the linear curve $$\overline{AB}$$.
Figure (25): Parametric linear curve in 3-D space and parameter interval.
The same principle applies for any parametric curve. Any point on the curve can be calculated using the parameter t within the interval or domain of values that define the limits of the curve. The start parameter of the domain is usually referred to as $$t0$$ and the end of the domain as $$t1$$.
Figure (26): Curve in 3-D space (1). Curve domain (2).
### Curve domain or interval A curve *domain* or *interval* is defined as the range of parameters that evaluate into a point within that curve. The domain is usually described with two real numbers defining the domain limits expressed in the form (min to max)or (min, max). The domain limits can be any two values that may or may not be related to the actual length of the curve. In an increasing domain, the domain min parameter evaluates to the start point of the curve and the domain max evaluates to the end point ofthe curve.
Figure (27): Curve domain or interval can be between any two numbers.
Changing a curve domain is referred to as the process of reparameterizing the curve. For example, it is very common to change the domain to be (0 to 1). Reparameterizing a curve does not affect the shape of the 3-D curve. It is like changing the travel time on a path by running instead of walking, which does not change the shape of the path.
Figure (28): Normalized curve domain to be 0 to 1.
An increasing domain means that the minimum value of the domain points to the start of the curve. Domains are typically increasing, but not always. ### Curve evaluation We learned that a curve interval is the range of all parameter values that evaluate to points within the 3-D curve. There is, however, no guarantee that evaluating at the middle of the domain, for example, will give a point that is in the middle of the curve, as shown in Figure (29). We can think of uniform parameterization of a curve as traveling a path with constant speed. A degree-1 line between two points is one example where equal intervals or parameters translate into equal intervals of arc length on the line. This is a special case where equal intervals of parameters evaluate to equal intervals on the 3-D curve.
Figure (29): Equal parameter intervals in a degree-1 line evaluate to equal curve lengths.
It is, however, more likely that the speed decreases or increases along the path. For example, if it takes 30 minutes to travel a road, it is unlikely that you will be exactly half way through at minute 15. Figure (30) shows a typical case where equal parameter intervals evaluate to variable length on the 3-D curve.
Figure (30): Equal parameter intervals do not usually translate into equal distances on a curve.
You may need to evaluate points on a 3-D curve that are at a defined percentage of the curve length. For example, you might need to divide the curve into equal lengths. Typically, 3-D modelers provide tools to evaluate curves relative to arc length. ### Tangent vector to a curve A tangent to a curve at any parameter (or point on a curve) is the vector that touches the curve at that point, but does not cross over. The slope of the tangent vector equals the slope of the curve at the same point. The following example evaluates the tangent to a curve at two different parameters.
Figure (31): Tangents to a curve.
### Cubic polynomial curves Hermite and Bézier curves are two examples of cubic polynomial curves that are determined by four parameters. A Hermite curve is determined by two end points and two tangent vectors at these points, while a Bézier curve is defined by four points. While they differ mathematically, they share similar characteristics and limitations.
Figure (32): Cubic polynomial curves. The Bézier curve (left) is defined by four points. The Hermite curve (right) is defined by two points and two tangents..
In most cases, curves are made out of multiple segments. This requires making what is called a *piecewise* cubic curve. Here is an illustration of a piecewise Bézier curve that uses 7 storage points to create a two-segment cubic curve. Note that although the final curve is joined, it does not look smooth or continuous.
Figure (33): Two Bezier spans share one point.
Although Hermite curves use the same number of parameters as Bézier curves (four parameters to define one curve), they offer the additional information of the tangent curve that can also be shared with the next piece to create a smoother looking curve with less total storage, as shown in the following.
Figure (34): Two Hermite spans share one point and a tangent.
The non-uniform rational B-spline (NURBS) is a powerful curve representation that maintains even smoother and more continuous curves. Segments share more control points to achieve even smoother curves with less storage.
Figure (35): Two degree-3 NURBS spans share three control points.
NURBS curves and surfaces are the main mathematical representation used by Rhino to represent geometry. NURBS curve characteristics and components will be covered with some detail later in this chapter. ### Evaluating cubic Bézier curves Named after its inventor, Paul de Casteljau, the de Casteljau algorithm evaluates Bézier curves using a recursive method. The algorithm steps can be summarized as follows: **Input:**   Four points $$A$$, $$B$$, $$C$$, $$D$$ define a curve $$t$$, is any parameter within curve domain **Output:**   Point $$R$$ on curve that is at parameter $$t$$. **Solution:** 1. Find point $$M$$ at $$t$$ parameter on line $$\overline{AB}$$. 2.Find point $$N$$ at $$t$$ parameter on line $$\overline{BC}$$. 3.Find point $$O$$ at $$t$$ parameter on line $$\overline{CD}$$. 4.Find point $$P$$ at $$t$$ parameter on line $$\overline{MN}$$. 5.Find point $$Q$$ at $$t$$ parameter on line $$\overline{NO}$$. 6.Find point $$R$$ at $$t$$ parameter on line $$\overline{PQ}$$. ## 3.2 NURBS curves NURBS is an accurate mathematical representation of curves that is highly intuitive to edit. It is easy to represent free-form curves using NURBS and the control structure makes it easy and predictable to edit.
Figure (36): Non-uniform rational B-splines and their control structure.
There are many books and references for those of you interested in an in-depth reading about NURBS. A basic understanding of NURBS is however necessary to help use a NURBS modeler more effectively. There are four main attributes define the NURBS curve: degree, control points, knots, and evaluation rules. 1. [Wikipedia: De Boor's algorithm](http://en.wikipedia.org/wiki/De_Boor's_algorithm) 2. [Michigan Tech, Department of Computer Science, De Boor's algorithm](http://www.cs.mtu.edu/~shene/COURSES/cs3621/NOTES/spline/de-Boor.html) ### Degree Curve degree is a whole positive number. Rhino allows working with any degree curve starting with 1. Degrees 1, 2, 3, and 5 are the most useful but the degrees 4 and those above 5 are not used much in the real world. Following are a few examples of curves and their degree:
Lines and polylines are degree-1 NURBS curves.
Circles and ellipses are examples of degree-2 NURBS curves.
Free-form curves are usually represented as degree-3 or 5 NURBS curves.
### Control points The control points of a NURBS curve is a list of at least (degree+1) points. The most intuitive way to change the shape of a NURBS curve is through moving its control points. The number of control points that affect each span in a NURBS curve is defined by the degree of the curve. For example, each span in a degree-1 curve is affected only by the two end control points. In a degree-2 curve, three control points affect each span and so on.
Control points of degree-1 curves go through all curve control points. In a degree-1 NURBS curve, two (degree+1) control points define each span. Using five control points, the curve has four spans.
Circles and ellipses are examples of degree two curves. In a degree-2 NURBS curve, three (degree+1) control points define each span. Using five control points, the curve has three spans.
Control points of degree‑3 curves do not usually touch the curve, except at the end points in open curves. In a degree‑3 NURBS curve, four (degree+1) control points define each span. Using five control points, the curve has two spans
### Weights of control points Each control point has an associated number called *weight*. With a few exceptions, weights are positive numbers. When all control points have the same weight, usually 1, the curve is called non-rational. Intuitively, you can think of weights as the amount of gravity each control point has. The higher the relative weight a control point has, the closer the curve is pulled towards that control point. It is worth noting that it is best to avoid changing curve weights. Changing weights rarely gives desired result while it introduces a lot of calculation challenges in operations such as intersections. The only good reason for using rational curves is to represent curves that cannot otherwise be drawn, such as circles and ellipses.
Figure (37): The effect of varying weights of control points on the result curve. The left curve is non-rational with uniform control point weights. The circle on the right is a rational curve with corner control points having weights less than 1.
### Knots Each NURBS curve has a list of numbers associated with it called a *list of knots* (sometimes referred to as *knot vector*). Knots are a little harder to understand and set. While using a 3-D modeler, you will not need to manually set the knots for each curve you create; a few things will be useful to learn about knots. ### Knots are parameter values Knots are a non-decreasing list of parameter values that lie within the curve domain. In Rhino, there is degree-1 more knots than control points. That is the number of knots equals the number of control points plus curve degree minus 1: |knots| = |CVs| + Degree - 1 Usually, for non-periodic curves, the first degree many knots are equal to the domain minimum, and the last degree many knots are equal to the domain maximum. For example, the knots of an open degree-3 NURBS curve with 7 control points and a domain between 0 and 4 may look like <0, 0, 0, 1, 2, 3, 4, 4, 4>.
Figure (38): There are degree minus 1 more knots than control points. If the number of control points=7, and curve degree=3, then number of knots is 9. Knots values are parameters that evaluate to points on the 3D curve.
Scaling a knot list does not affect the 3D curve. If you change the domain of the curve in the above example from “0 to 4” to “0 to 1”, knot list get scaled, but the 3D curve does not change.
Figure (39): Scaling the knot list does not change the 3D curve.
We call a knot with value appearing only once as simple knot. Interior knots are typically simple as in the two examples above. ### Knot multiplicity The multiplicity of a knot is the number of times it is listed in the list of knots. The multiplicity of a knot cannot be more than the degree of the curve. Knot multiplicity is used to control continuity at the corresponding curve point. ### Fully-multiple knots A fully multiple knot has multiplicity equal to the curve degree. At a fully multiple knot there is a corresponding control point, and the curve goes through this point. For example, clamped or open curves have knots with full multiplicity at the ends of the curve. This is why the end control points coincide with the curve end points. Interior fully multiple knots allow a kink in the curve at the corresponding point. For example, the following two curves are both degree-3, and have the same number and location of control points. However they have different knots and their shape is also different. Fully multiplicity forces the curve through the associated control point. Here are two curves that have the same degree, and same number and location of control points, and yet have different knots vector that results in different curve shape:
Degree = 3 Number of control points = 7 knots = <0,0,0,1,2,3,4,4,4> = 9 knots Domain (0 to 4)
Degree = 3 Number of control points = 7 knots = <0,0,0,1,1,1,4,4,4> = 9 knots Domain (0 to 4) Note: A fully multiple knot in the middle creates a kink and the curve is forced to go through the associated control point.
### Uniform knots A uniform list of knots satisfies the following condition. Knots start with a fully-multiple knot, are followed by simple knots, and terminate with a fully-multiple knot. The values are increasing and equally spaced. This is typical of clamped or open curves. Periodic curves work differently as we will see later.
Figure (40): Uniform knot list means that spacing between knots is constant, with the exception of clamped curves where they can have full multiplicity knots at start and end, and still be considered uniform. The left curve is periodic (closed without kink), and the right is clamped (open).
### Non uniform knots NURBS curves are allowed to have non-uniform spacing between knots. This can help control the curvature along the curve to create more smooth curves. Take the following example interpolating through points using non-uniform knots list in the left, and uniform in the right. In general, if a NURBS curve spacing of knots is proportional to the spacing between control points, then the curve is smoother.
Figure (41): Non-uniform knot list can help produce smoother curves. The curve on the left interpolate through points with non-uniform knots, and produces smoother curvature. The curve on the right interpolate through the same points but forces a uniform spacing of knots, resulting curve is not as smooth.
An example of a curve that is both non-uniform and rational is a NURBS circle. The following is a degree 2 curve with 9 control points and 10 knots. Domain is 0-4, and the spacing alternate between 0 and 1. knots = <0,0,1,1,2,2,3,3,4,4> --- (full multiplicity in the interior knots) spacing between knots = [0,1,0,1,0,1,0,1,0] --- (non-uniform)
Figure (42): A NURBS approximation of a circle is rational and non-uniform NURBS.
### Evaluation rule The evaluation rule uses a mathematical formula that takes a number within the curve domain and assigns a point. The formula takes into account the degree, control points, and knots. Using this formula, specialized curve functions can take a curve parameter and produce the corresponding point on that curve. A parameter is a number that lies within the curve domain. Domains are usually increasing and consist of two numbers: the minimum domain parameter $$t(0)$$ that evaluates to the start point of the curve and the maximum $$t(1)$$ that evaluates to the end point of the curve.
Figure (43): Evaluate parameters to points on curve.
### Characteristics of NURBS curves In order tocreate a NURBS curve, you will need to provide the followinginformation: - Dimension, typically 3 - Degree, (sometimes use the *order*,which is equal to degree+1) - Control points (list of points) - Weight of the control point (listof numbers) - Knots (list of numbers) When you create a curve, you need to at least define the degree and locations of the control points. The rest of the information necessary to construct NURBS curves can be generated automatically. Selecting an end point to coincide with the start point would typically create a periodic smooth closed curve. The following table shows examples of open and closed curves:
Degree-1 open curve. The curve goes through all control points.
Degree-3 open curve. Both curve ends coincide with end control points.
Degree-3 closed periodic curve. The curve seam does not go through a control point.
Moving control points of a periodic curve does not affect curve smoothness.
Kinks are created when the curve is forced through some control points.
Moving the control points of a non-periodic curve does not guarantee smooth continuity of the curve, but enables more control over the outcome.
### Clamped vs. periodic NURBS curves The end points of closed clamped curves coincide with end control points. Periodic curves are smooth closed curves. The best way to understand the differences between the two is through comparing control points and knots. The following is an example of an open, clamped non-rational NURBS curve. This curve has four control points, uniform knots with full-multiplicity at the start and end knots and the weights of all control points equal to 1.
Figure (44): Analyze degree-3 open non-rational NURBS curve.
The following circular curve is an example of a degree-3 closed periodic NURBS curve. It is also non-rational because all weights are equal. Note that periodic curves require more control points with few overlapping. Also the knots are simple.
Figure (45): Analyze degree-3 closed (periodic) NURBS curve.
Notice that the periodic curve turned the four input points into seven control points (degree+4), while the clamped curve used only the four control points. The knots of the periodic curve uses only simple knots, while the clamped curve start and end knots have full multiplicity forcing the curve to go through the start and end control points. If we set the degree of the previous examples to 2 instead of 3, the knots become smaller, and the number of control points of periodic curves changes.
Figure (46): Analyze degree-2 open NURBS curve.
Figure (47): Analyze degree-2 closed (periodic) NURBS curve.
### Weights Weights of control points in a uniform NURBS curve are set to 1, but this number can vary in rational NURBS curves. The following example shows the effect of varying the weights of control points.
Figure (48): Analyze weights in open NURBS curve.
Figure (49): Analyze weights in closed NURBS curve.
### Evaluating NURBS curves Named after its inventor, Carl de Boor, the de Boor’s algorithmi is a generalization of the de Casteljau algorithm for Bézier curves. It is numerically stable and is widely used to evaluate points on NURBS curves in 3-D applications. The following is an example for evaluating a point on a degree-3 NURBS curve using de Boor’s algorithm. **Input:** Seven control points $$P0$$ to $$P6$$ Knots:   $$u_0 = 0.0$$   $$u_1 = 0.0$$   $$u_2 = 0.0$$   $$u_3= 0.25$$   $$u_4 = 0.5$$   $$u_5 = 0.75$$   $$u_6 = 1.0$$   $$u_7 = 1.0$$   $$u_8 = 1.0$$ **Output:** Point on curve that is at $$u=0.4$$ **Solution:** 1\. Calculate coefficients for the first iteration:   $$A_c = ((u – u_1)/(u_{1+3} – u_1) = 0.8$$   $$B_c = (u – u_2)/(u_{2+3} – u_2) = 0.53$$   $$C_c = (u – u_3)/(u_{3+3} – u_3) = 0.2$$ 2\. Calculate points using coefficient data:   $$A = 0.2P_1 + 0.8P_2$$   $$B = 0.47 P_2 + 0.53 P_3$$   $$C = 0.8 P_3 + 0.2 P_4$$ 3\. Calculate coefficients for the second iteration:   $$D_c = (u – u_2) / (u_{2+3-1} – u_2) = 0.8$$   $$E_c = (u – u_3) / (u_{3+3-1} – u_3) = 0.3$$ 4\. Calculate points using coefficient data:   $$D = 0.2A+ 0.8B$$   $$E = 0.7B + 0.3C$$ 5\. Calculate the last coefficient:   $$Fc = (u – u_3)/ (u_{3+3-2} – u_3) = 0.6$$ Find the point on curve at $$u=0.4$$ parameter:   $$F= 0.4D + 0.6E$$ ## 3.3 Curve geometric continuity Continuity is an important concept in 3‑D modeling. Continuity is important for achieving visual smoothness and for obtaining smooth light and airflow. The following table shows various continuities and their definitions: | **G0**| (Position continuous) | Two curve segments joined together | | **G1**| (Tangent continuous) | Direction of tangent at joint point is the same for both curve segments. | | **G2**| ( Curvature Continuous) | Curvatures as well as tangents agree for both curve segments at the common endpoint. | | **GN**|....... | The curves agree to higher order |
Figure (50): Examining curve continuity with curvature graph analysis.
## 3.4 Curve curvature Curvature is a widely used concept in modeling 3‑D curves and surfaces. Curvature is defined as the change in inclination of a tangent to a curve over unit length of arc. For a circle or sphere, it is the reciprocal of the radius and it is constant across the full domain. At any point on a curve in the plane, the line best approximating the curve that passes through this point is the tangent line. We can also find the best approximating circle that passes through this point and is tangent to the curve. The reciprocal of the radius of this circle is the curvature of the curve at this point.
Figure (51): Examining curve curvature at different points.
The best approximating circle can lie either to the left or to the right of the curve. If we care about this, we establish a convention, such as giving the curvature positive sign if the circle lies to the left and negative sign if the circle lies to the right of the curve. This is known as signed curvature. Curvature values of joined curves indicate continuity between these curves. ## 3.5 Parametric surfaces ### Surface parameters A parametric surface is a function of two independent parameters (usually denoted $$u$$, $$v$$) over some two-dimensional domain. Take for example a plane. If we have a point $$P$$ on the plane and two nonparallel vectors on the plane, $$\vec a$$ and $$\vec b$$, then we can define a parametric equation of the plane in terms of the two parameters $$u$$ and $$v$$ as follows: $$P = P’ + u * \mathbf{\vec a} + v * \mathbf{\vec b}$$ Where:   $$P’$$: is a known point on the plane   $$\mathbf{\vec a}$$: is the first vector on the plane   $$\mathbf{\vec b}$$: is the first vector on the plane   $$u$$: is the first parameter   $$v$$: is the first parameter
Figure (52): The parameter rectangle of a plane.
Another example is the sphere. The Cartesian equation of a sphere centered at the origin with radius $$R$$ is $$x^2 + y^2 + z^2 = R^2$$ That means for each point, there are three variables ($$x$$, $$y$$, $$z$$), which is not useful to use for a parametric representation that requires only two variables. However, in the spherical coordinate system, each point is found using the three values: $$r$$: radial distance between the point and the origin $$θ$$: the angle from the x-axis in the xy-plane $$ø$$: the angle from the z-axis and the point
Figure (53): Spherical coordinate system.
A conversion of points from spherical to Cartesian coordinate can be obtained as follows:   $$x = r * sin(ø) * cos(θ)$$   $$y = r * sin(ø) * sin(θ)$$   $$z = r * cos (ø)$$ Where:   $$r$$ is distance from origin $$≥ 0$$   $$θ$$ is running from $$0$$ to $$2π$$   $$ø$$ is running from $$0$$ to $$π$$ Since $$r$$ is constant in a sphere surface, we are left with only two variables, and hence we can use the above to create a parametric representation of a sphere surface:   $$u = θ$$   $$v = ø$$ So that:   $$x = r * sin(v) * cos(u)$$   $$y = r * sin(v) * sin(u)$$   $$z = r * cos(v)$$ Where ($$u$$, $$v$$) is within the domain ($$2 π$$, $$π$$)
Figure (54): The parameter rectangle of a sphere.
The parametric surface follows the general form:   $$x = x(u,v)$$   $$y = y(u,v)$$   $$z = z(u,v)$$ Where:   $$u$$ and $$v$$ are the two parameters within the surface domain or region. ### Surface domain A surface domain is defined as therange of ($$u,v$$) parameters that evaluate into a 3 D point on thatsurface. The domain in each dimension ($$u$$ or $$v$$) is usually describedas two real numbers ($$u_{min}$$ to $$u_{max}$$) and ($$v_{min}$$ to $$v_{max}$$) Changing a surface domain is referredto as *reparameterizing* the surface. An increasingdomain means that the minimum value of the domain points to theminimum point of the surface. Domains are typically increasing, butnot always.
Figure (55): NURBS surface in 3-D modeling space (left). The surface parameter rectangle with domain spanning from u0 to u1 in the first direction and v0 to v1 in the second direction (right).
### Surface evaluation Evaluating a surface at a parameter that is within the surface domain results in a point that is on the surface. Keep in mind that the middle of the domain ($$u_{mid}$$, $$v_{mid}$$) might not necessarily evaluate to the middle point of the 3-D surface. Also, evaluating $$u-$$ and $$v-$$ values that are outside the surface domain will not give a useful result.
Figure (56): Surface evaluation.
### Tangent plane of a surface The tangent plane to a surface at a given point is the plane that touches the surface at that point. The z-direction of the tangent plane represents the normal direction of the surface at that point.
Figure (57): Tangent and normal vectors to a surface.
## 3.6 Surface geometric continuity Many models cannot be constructed from one surface patch. Continuity between joined surface patches is important for visual smoothness, light reflection, and airflow. The following table shows various continuities and their definitions: | | | | |---|---|---| | **G0**| (Position continuous) | Two surfaces joined together. | | **G1**| (Tangent continuous) | The corresponding tangents of the two surfaces along their joint edge are parallel in both u‑ and v‑directions. | | **G2**| ( Curvature Continuous) | Curvatures as well as tangents agree for both surfaces at the common edge. | | **GN**|....... | The surfaces agree to higher order. |
Figure (58): Examining surface continuity with zebra analysis.
## 3.7 Surface curvature For surfaces, normal curvature is one generalization of curvature to surfaces. Given a point on the surface and a direction lying in the tangent plane of the surface at that point, the normal section curvature is computed by intersecting the surface with the plane spanned by the point, the normal to the surface at that point, and the direction. The normal section curvature is the signed curvature of this curve at the point of interest. If we look at all directions in the tangent plane to the surface at our point, and we compute the normal curvature in all these directions, there will be a maximum value and a minimum value.
Figure (59): Normal curvatures.
### Principal curvatures The principal curvatures of a surface at a point are the minimum and maximum of the normal curvatures at that point. They measure the maximum and minimum bend amount of the surface at that point. The principal curvatures are used to compute the Gaussian and mean curvatures of the surface. For example, in a cylindrical surface, there is no bend along the linear direction (curvature equals zero) while the maximum bend is when intersecting with a plane parallel to the end faces (curvature equals 1/radius). Those two extremes make the principle curvatures of that surface.
Figure (60): Principle curvatures at a surface point are the minimum and maximum curvatures at that point.
### Gaussian curvature The Gaussian curvature of a surface at a point is the product of the principal curvatures at that point. The tangent plane of any point with positive Gaussian curvature touches the surface locally at a single point, whereas the tangent plane of any point with negative Gaussian curvature cuts the surface. ![/images/math-image91.png](https://developer.rhino3d.com/images/math-image91.png) A: Positive curvature when surface is bowl-like. B: Negative curvature when surface is saddle-like. C: Zero curvature when surface is flat in at least one direction (plane, cylinder).
Figure (61): Analyzing the surface Gaussian curvature.
### Mean curvature The mean curvature of a surface at a point is one-half of the sums of the principal curvatures at that point. Any point with zero mean curvature has negative or zero Gaussian curvature. Surfaces with zero mean curvature everywhere are minimal surfaces. Physical processes which can be modeled by minimal surfaces include the formation of soap films spanning fixed objects, such as wire loops. A soap film is not distorted by air pressure (which is equal on both sides) and is free to minimize its area. This contrasts with a soap bubble, which encloses a fixed quantity of air and has unequal pressures on its inside and outside. Mean curvature is useful for finding areas of abrupt change in the surface curvature. Surfaces with constant mean curvature everywhere are often referred to as constant mean curvature (CMC) surfaces. CMC surfaces include the formation of soap bubbles, both free and attached to objects. A soap bubble, unlike a simple soap film, encloses a volume and exists in equilibrium where slightly greater pressure inside the bubble is balanced by the area-minimizing forces of the bubble itself. ## 3.8 NURBS surfaces You can think of NURBS surfaces as a grid of NURBS curves that go in two directions. The shape of a NURBS surface is defined by a number of control points and the degree of that surface in each one of the two directions (u- and v-directions). NURBS surfaces are efficient for storing and representing free-form surfaces with a high degree of accuracy. The mathematical equations and details of NURBS surfaces are beyond the scope of this text. We will only focus on the characteristics that are most useful for designers.
Figure (62): NURBS surface with red isocurves in the u-direction and green isocurves in the v-direction.
Figure (63): The control structure of a NURBS surface.
Figure (64): The parameter rectangle of a NURBS surface.
Evaluating parameters at equal intervals in the 2-D parameter rectangle does not translate into equal intervals in 3-D space in most cases.
Figure (65): Evaluating surfaces.
### Characteristics of NURBS surfaces NURBS surface characteristics are very similar to NURBS curves except there is one additional parameter. NURBS surfaces hold the following information: - Dimension, typically 3 - Degree in u‑and v‑directions: (sometimes use order which is degree + 1) - Control points (points) - Weights of control points (numbers) - Knots (numbers) As with the NURBS curves, you will probably not need to know the details of how to create a NURBS surface, since 3-D modelers will typically provide good set of tools for this. You can always rebuild surfaces (and curves for that matter) to a new degree and number of control points. Surface can be open, closed, or periodic. Here are few examples of surfaces:
Degree-1 surface in both u- and v-directions. All control points lie on the surface.
Degree-3 in the u-direction and degree‑1 in the v-direction open surface. The surface corners coincide with corner control points.
Degree-3 in the u-direction and degree 1 in the v-direction closed (non-periodic) surface. Some control points coincide with the surface seam.
Moving control points of a closed (non-periodic) surface causes a kink and the surface does not look smooth.
Degree 3 the u-direction and degree 1 in the v-direction periodic surface. The surface control points do not coincide with the surface seam.
Moving the control points of a periodic surface does not affect surface smoothness or create kinks.
### Singularity in NURBS surfaces For example, if you have a linear edge of a simple plane, and you drag the two end control points of an edge so they overlap (collapse) at the middle, you will get a singular edge. You will notice that the surface isocurves converge at the singular point.
Figure (66): Collapse two corner points of a rectangular NURBS surface to create a triangular surface with singularity. The parameter rectangle remains rectangular.
The above triangular shape can be created without singularity. You can trim a surface with a triangle polyline. When you examine the underlying NURBS structure, you see that it remains a rectangular shape.
Figure (67): Trim a rectangular NURBS surface to create a trimmed triangular surface.
Other common examples of surfaces that are hard to generate without singularity are the cone and the sphere. The top of a cone and top and bottom edges of a sphere are collapsed into one point. Whether there is singularity or not, the parameter rectangle maintains a more or less rectangular region. ### Trimmed NURBS surfaces NURBS surfaces can be trimmed or untrimmed. Trimmed surfaces use an underlying NURBS surface and closed curves to trim out part of that surface. Each surface has one closed curve that defines the outer border (*outer loop*) and can have non-intersecting closed inner curves to define holes (*inner loops*). A surface with an outer loop that is the same as that of its underlying NURBS surface and that has no holes is what we refer to as an *untrimmed* surface.
Figure (68): Trimmed surface in modeling space (left) and in parameter rectangle (right).
## 3.9 Polysurfaces A polysurface consists of two or more(possibly trimmed) NURBS surfaces joined together. Each surface hasits own structure, parameterization, and isocurve directions that donot have to match. Polysurfaces are represented using the boundaryrepresentation (*BRep*). The BRep structure describes surfaces,edges, and vertices with trimming data and connectivity amongdifferent parts. Trimmed surface are also represented using BRep datastructure.
Figure (69): Polysurfaces are made out of joined surfaces with common edges aligning perfectly within tolerance.
The BRep is a data structure that describes each face in terms of its underlying surface, surrounding 3-D edges, vertices, parameter space 2-D trims, and relationship between neighboring faces. BRep objects are also called solids when they are closed (watertight). An example polysurface is a simple box that is made out of six untrimmed surfaces joined together.
Figure (70): Box made out of six untrimmed surfaces joined in one polysurface.
The same box can be made using trimmed surfaces, such as the top one in the following example.
Figure (71): Box faces can be trimmed.
The top and bottom faces of the cylinder in the following example are trimmed from planar surfaces.
Figure (72) shows the control points of the underlying surfaces.
We saw that editing NURBS curves and untrimmed surfaces is intuitive and can be done interactively by moving control points. However, editing trimmed surfaces and polysurfaces can be challenging. The main challenge is to be able to maintain joined edges of different faces within the desired tolerance. Neighboring faces that share common edges can be trimmed and do not usually have matching NURBS structure, and therefore modifying the object in a way that deforms that common edge might result in a gap.
Figure (73): Two triangular faces joined in one polysurface but do not have matching joined edge. Moving one corner create a hole.
Another challenge is that there is typically less control over the outcome, especially when modifying trimmed geometry.
Figure (74): Once a trimmed surface is created, there is limited control to edit the result.
Figure (75): Use cage edit technique in Rhino to edit polysurfaces.
Trimmed surfaces are described in parameter space using the untrimmed underlying surface combined with the 2-D trim curves that evaluate to the 3-D edges within the 3-D surface. ## 3.10 Tutorials The following tutorials use the concepts learned in this chapter. They use Rhinoceros 5 and Grasshopper 0.9. ### 3.10.1 Continuity between curves Examine the continuity between two input curves. Continuity assumes that the curves meet at the end of the first curve and the start of the second curve. ![/images/math-image48.png](https://developer.rhino3d.com/images/math-image48.png) ##### Input: Two input curves. ##### Parameters: Calculate the following to be able to decide the continuity between two curves: ![/images/math-image46.png](https://developer.rhino3d.com/images/math-image46.png) - The end point of the first curve ($$P1$$) - The start point of the second curve ($$P2$$) - The tangent at the end of the first curve and at the start of the second curve ($$T1$$ and $$T2$$). - The curvature at the end of the first curve and at the start of the second curve ($$C1$$ and $$C2$$). ##### Solution: 1\. Reparameterize the input curves. We do that so that we know that the start of the curve evaluates at $$t=0$$ and the end at $$t=1$$. 2\. Extract the end and start points of the two curves, and check whether they coincide. If they do, the two curves are at least $$G0$$ continuous. ![/images/math-image36.png](https://developer.rhino3d.com/images/math-image36.png) 3\. Calculate tangents. 4\. Compare the tangents using the dot product. Make sure to unitize vectors. If the curves are parallel, then we have at least $$G1$$ continuity. ![/images/math-image34.png](https://developer.rhino3d.com/images/math-image34.png) 5\. Calculate curvature vectors. 6\. Compare curvature vectors, and if they agree, the two curves are $$G2$$ continuous. ![/images/math-image40.png](https://developer.rhino3d.com/images/math-image40.png) 7\. Create logic that filters through the three results (G1, G2, and G3) and select the highest continuity. ![/images/math-image38.png](https://developer.rhino3d.com/images/math-image38.png) Using the Grasshopper VBScript component: ![/images/math-image31.png](https://developer.rhino3d.com/images/math-image31.png) ```vb Private Sub RunScript(ByVal c1 As Curve, ByVal c2 As Curve, ByRef A As Object) 'declare variables Dim continuity As New String("") Dim t1, t2 As Double Dim v_c1, v_c2, c_c1, c_c2 As Vector3d 'extract start and end points Dim end_c1 = c1.PointAtEnd Dim start_c2 = c2.PointAtStart 'check G0 continuity If end_c1.DistanceTo(start_c2) = 0 Then continuity = "G0" End If 'check G1 continuity If continuity = "G0" Then 'calculate tangents v_c1 = c1.TangentAtEnd v_c2 = c2.TangentAtStart 'unitize tangent vectors v_c1.Unitize v_c2.Unitize 'compare tangents If v_c1 * v_c2 = 1.0 Then continuity = "G1" End If End If 'check G2 continuity If continuity = "G1" Then 'extract the parameter at start and end of the curves domain t1 = c1.Domain.Max t2 = c2.Domain.Min 'calculate curvature c_c1 = c1.CurvatureAt(t1) c_c2 = c2.CurvatureAt(t2) 'unitize curvature vectors c_c1.Unitize c_c2.Unitize 'compare vectors If c_c1 * c_c2 = 1.0 Then continuity = "G2" End If End If 'Assign output A = continuity End Sub ``` Using the Grasshopper Python component: ![/images/math-image69.png](https://developer.rhino3d.com/images/math-image69.png) ```python #decclare variables continuity = "" #extract start and end points end_c1 = c1.PointAtEnd start_c2 = c2.PointAtStart #check G0 continuity if end_c1.DistanceTo(start_c2) == 0: continuity = "G0" #check G1 continuity if continuity == "G0": #calculate tangents v_c1 = c1.TangentAtEnd v_c2 = c2.TangentAtStart #unitize tangent vectors v_c1.Unitize() v_c2.Unitize() #compare tangents dot = v_c1 * v_c2 if dot == 1.0: continuity = "G1" else: print("Failed G1") print(dot) #check G2 continuity if continuity == "G1": #extract the parameter at start and end of the curves domain t1 = c1.Domain.Max t2 = c2.Domain.Min #calculate curvature c_c1 = c1.CurvatureAt(t1) c_c2 = c2.CurvatureAt(t2) #unitize curvature vectors c_c1.Unitize() c_c2.Unitize() #compare vectors dot = c_c1 * c_c2 if dot == 1.0: continuity = "G2" else: print("Failed G2") print(dot) #assign output A = continuity ``` Using the Grasshopper C# component: ![/images/math-image70.png](https://developer.rhino3d.com/images/math-image70.png) ```cs Private Sub RunScript(ByVal c1 As Curve, ByVal c2 As Curve, ByRef A As Object) //decalre variables string continuity = (""); double t1, t2; Vector3d v_c1, v_c2, c_c1, c_c2; //extract start and end points Point3d end_c1 = c1.PointAtEnd; Point3d start_c2 = c2.PointAtStart; //check G0 continuity if( end_c1.DistanceTo(start_c2) == 0){ continuity = "G0"; } //check G1 continuity if( continuity == "G0") { //calculate tangents v_c1 = c1.TangentAtEnd; v_c2 = c2.TangentAtStart; //unitize tangent vectors v_c1.Unitize(); v_c2.Unitize(); //compare tangents if( v_c1 * v_c2 == 1.0 ){ continuity = "G1"; } } //check G2 continuity if( continuity == "G1" ) { //extract the parameter at start and end of the curves domain t1 = c1.Domain.Max; t2 = c2.Domain.Min; //calculate curvature c_c1 = c1.CurvatureAt(t1); c_c2 = c2.CurvatureAt(t2); //unitize curvature vectors c_c1.Unitize(); c_c2.Unitize(); //compare vectors if( c_c1 * c_c2 == 1.0 ){ continuity = "G2"; } } //assign output A = continuity; End Sub ``` ### 3.10.2 Surfaces with singularity Extract singular points in a sphere and a cone. **Input:** One sphere and one cone. ![/images/math-image61.png](https://developer.rhino3d.com/images/math-image61.png) **Parameters:** Singularity can be detected through analyzing the 2-D parameter space trims that have invalid or zero-length corresponding edges. Those trims ought to be singular. **Solution:** 1. Traverse through all trims in the input. 2. Check if any trim has an invalid edge and flag it as a singular trim. 3. Extract point locations in 3-D space. Using the Grasshopper VB component: ![/images/math-image59.png](https://developer.rhino3d.com/images/math-image59.png) ```vb Private Sub RunScript(ByVal srf As Brep, ByRef A As Object) 'Decalre a new list of points Dim singular_points As New List( Of Point3d) 'Examine all trims in the input For Each trim As BrepTrim In srf.Trims 'Null edge of a trim indicates a singularity If trim.Edge Is Nothing Then 'Find the 2D parameter space point of the start or end of the trim Dim pt2d = New Point2d(trim.PointAtStart) 'Evaluate trim end point on the object surface Dim pt3d = trim.Face.PointAt(pt2d.x, pt2d.y) 'Add 3D point to the list of singular points singular_points.Add(pt3d) End If Next 'Asign output A = singular_points End Sub ``` Using the Grasshopper Python component: ![/images/math-image53.png](https://developer.rhino3d.com/images/math-image53.png) ```python #Decalre a new list of points singular_points = [] #Examine all trims in the input brep for trim in srf.Trims: #Null edge of a trim indicates a singularity if trim.Edge == None: #Find the 2D parameter space point at trim start or end pt2d = trim.PointAtStart #Evaluate trim end point on the object surface pt3d = trim.Face.PointAt(pt2d.X, pt2d.Y) #Add 3D point to the list of singular points singular_points.append(pt3d) #Asign output A = singular_points ``` Using the Grasshopper C# component: ![/images/math-image63.png](https://developer.rhino3d.com/images/math-image63.png) ```cs private void RunScript(Brep srf, ref object A) { //Decalre a new list of points List < Point3d > singular_points = new List(); //Examine all trims in the input foreach( BrepTrim trim in srf.Trims) { //Null edge of a trim indicates a singularity if( trim.Edge == null) { //Find the 2D parameter space point of the start or end of the trim Point2d pt2d = new Point2d(trim.PointAtStart); //Evaluate trim end point on the object surface Point3d pt3d = trim.Face.PointAt(pt2d.X, pt2d.Y); //Add 3D point to the list of singular points singular_points.Add(pt3d); } } //Asign output A = singular_points } ``` ## Download Sample Files Download the [ ](https://www.rhino3d.com/download/rhino/6/essentialmathematics/) [download samplesand tutorials](https://www.rhino3d.com/download/rhino/6/essentialmathematics/) archive, containing all the example Grasshopper and code files in this guide. ## Next Steps If you would like to research more, check out the [References](/guides/general/essential-mathematics/references/) guide to learn more about the detailed structure of NURBS curves and surfaces. -------------------------------------------------------------------------------- # 4 References Source: https://developer.rhino3d.com/en/guides/general/essential-mathematics/references/ This guide includes the references for Essential Mathematics for Computational Design. ## References 1. Edward Angel, "InteractiveComputer Graphics with OpenGL,” Addison Wesley Longman, Inc., 2000. 2. James D Foley, Steven K Feiner, John FHughes, "Introduction to Computer Graphics" Addison-WesleyPublishing Company, Inc., 1997. 3. James Stewart, "Calculus,"Wadsworth, Inc., 1991. 4. Kenneth Hoffman, Ray Kunze, “LinearAlgebra”, Prentice-Hall, Inc., 1971 5. Rhinoceros® help document, RobertMcNeel and Associates, 2009. **Footnotes** 1. [Wikipedia: Projection (linear algebra)](http://en.wikipedia.org/wiki/Projection_(linear_algebra)) 2. [Wikipedia: Cubic Hermite spline](http://en.wikipedia.org/wiki/Cubic_Hermite_spline). 3. [Wikipedia: Bézier curve](http://en.wikipedia.org/wiki/B%25C3%25A9zier_curve). 4. [Wikipedia: Non-uniform rational B-spline](http://en.wikipedia.org/wiki/Non-uniform_rational_B-spline). 5. [Wikipedia: De Casteljau's algorithm](http://en.wikipedia.org/wiki/De_Casteljau%27s_algorithm). 1. [Wikipedia: NURBS](http://en.wikipedia.org/wiki/NURBS). 6. [Wikipedia: De Boor's algorithm](http://en.wikipedia.org/wiki/De_Boor's_algorithm). 7. [MichiganTech, Department of Computer Science, De Boor's algorithm](http://www.cs.mtu.edu/~shene/COURSES/cs3621/NOTES/spline/de-Boor.html). -------------------------------------------------------------------------------- # Accessing Databases Source: https://developer.rhino3d.com/en/guides/rhinoscript/accessing-databases/ This guide demonstrates how to access databases from VBScript using RhinoScript. ## Overview Probably the most popular use for VBScript is connecting to databases. It's incredibly useful and surprisingly easy. The first thing you need is the database, of course. A variety of programs can be used to create it, but probably the most popular is Microsoft Access. You can also use FoxPro or create it directly in an SQL Server using whichever utilities are supplied with the server. In this example, we will connect to a simple Microsoft Access database. You can download the database used in this demonstration [here](/files/test_access_mdb.zip). Most VBScript developers use Microsoft's ADO (ActiveX database objects) to get data from database. ADODB is comprised of 3 main objects: Connection, RecordSet, and Command. We will demonstrate the first two objects. ## Connecting to a Database The Datasource is essentially a connection from the server or workstation to a database, which can either be on a dedicated machine running SQL server or a database file sitting somewhere on the web server. To specify what database you would like to use, you need to add a DSN. That is short for Data Source Name. Data Source Name provides connectivity to a database through an ODBC driver. The DSN contains database name, directory, database driver, UserID, password, and other information. Once you create a DSN for a particular database, you can use the DSN in an application to call information from the database. There are essentially two types of Datasources (DSN's): 1. **System DSN** - A datasource created on the web server by the administrator of the server. T he most popular type of DSN and generally a lot more reliable. 2. **File DSN** - Essentially a connection that your script makes itself each time access to the database is required, specifying the path to and name of the database. The database must reside on the server in a directory that your script can access for this to work. The code below is designed around a System DSN named “test” that points to the above database. You can create System DSNs using the Data Sources (OBDC) applet found in Control Panel. In Windows, the shortcut to the ODBC control panel can be found in the following location: *Start* > *Control Panels* > *Administrative Tools* > *Data Sources (ODBC)* ## Working with Recordsets In order to read information from a Datasource, you need to open a 'Recordset' - a set of database records based on some type of criteria, either all of the records in a table or those matching some condition or set of conditions. ## Sample The following example RhinoScript code demonstrates how to connect to a system DSN named “test” and read point coordinate records from a table named “points.” ```vbnet Sub Test Const adOpenStatic = 3 Const adLockOptimistic = 3 Const adUseClient = 3 Dim objConnection, objRecordset Set objConnection = CreateObject("ADODB.Connection") Set objRecordset = CreateObject("ADODB.Recordset") objConnection.Open "DSN=test;" objRecordset.CursorLocation = adUseClient objRecordset.Open "SELECT * FROM points" , objConnection, adOpenStatic, adLockOptimistic objRecordSet.MoveFirst Dim x, y, z Do Until objRecordset.EOF x = objRecordset.Fields.Item("x") y = objRecordset.Fields.Item("y") z = objRecordset.Fields.Item("z") Rhino.AddPoint Array(x,y,z) objRecordset.MoveNext Loop objRecordset.Close objConnection.Close End Sub ``` -------------------------------------------------------------------------------- # Add a Brep Box Source: https://developer.rhino3d.com/en/samples/cpp/add-brep-box/ Demonstrates how to add a Brep Box from a Rhino C++ plugin. ```cpp CRhinoCommand::result CCommandTestSdk::RunCommand(const CRhinoCommandContext& context) { CRhinoCommand::result rc = CRhinoCommand::nothing; // define the corners of the box ON_3dPointArray corners; corners.Append( ON_3dPoint( 0.0, 0.0, 0.0) ); corners.Append( ON_3dPoint(10.0, 0.0, 0.0) ); corners.Append( ON_3dPoint(10.0, 10.0, 0.0) ); corners.Append( ON_3dPoint( 0.0, 10.0, 0.0) ); corners.Append( ON_3dPoint( 0.0, 0.0, 10.0) ); corners.Append( ON_3dPoint(10.0, 0.0, 10.0) ); corners.Append( ON_3dPoint(10.0, 10.0, 10.0) ); corners.Append( ON_3dPoint( 0.0, 10.0, 10.0) ); // Build the brep ON_Brep* pBrep = ON_BrepBox( corners ); if( pBrep ) { CRhinoBrepObject* pObject = new CRhinoBrepObject(); pObject->SetBrep( pBrep ); if( context.m_doc.AddObject(pObject) ) { context.m_doc.Redraw(); rc = CRhinoCommand::success; } else { delete pObject; pObject = 0; rc = CRhinoCommand::failure; } } return rc; } ``` -------------------------------------------------------------------------------- # Add a Cone Surface Source: https://developer.rhino3d.com/en/samples/cpp/add-a-cone-surface/ Demonstrates how to create a cone using ON_BrepCone. ```cpp CRhinoCommand::result CCommandTest::RunCommand( const CRhinoCommandContext& context ) { ON_Plane plane = ON_xy_plane; double height = 10.0; double radius = 5.0; BOOL bCapBottom = FALSE; ON_Cone cone( plane, height, radius ); if( cone.IsValid() ) { ON_Brep* cone_brep = ON_BrepCone( cone, bCapBottom ); if( cone_brep ) { CRhinoBrepObject* cone_object = new CRhinoBrepObject(); cone_object->SetBrep( cone_brep ); context.m_doc.AddObject( cone_object ); context.m_doc.Redraw(); } } return CRhinoCommand::success; } ``` -------------------------------------------------------------------------------- # Add a Cylinder Source: https://developer.rhino3d.com/en/samples/cpp/add-cylinder/ Demonstrates how to create a cylinder using ON_BrepCylinder and add it to Rhino. ```cpp CRhinoCommand::result CCommandTest::RunCommand( const CRhinoCommandContext& context ) { ON_3dPoint center_point( 0.0, 0.0, 0.0 ); double radius = 5.0; ON_3dPoint height_point( 0.0, 0.0, 10.0 ); ON_3dVector zaxis = height_point - center_point; ON_Plane plane( center_point, zaxis ); ON_Circle circle( plane, radius ); ON_Cylinder cylinder( circle, zaxis.Length() ); ON_Brep* brep = ON_BrepCylinder( cylinder, TRUE, TRUE ); if( brep ) { CRhinoBrepObject* cylinder_object = new CRhinoBrepObject(); cylinder_object->SetBrep( brep ); if( context.m_doc.AddObject(cylinder_object) ) context.m_doc.Redraw(); else delete cylinder_object; } return CRhinoCommand::success; } ``` -------------------------------------------------------------------------------- # Add a Line Curve Object Source: https://developer.rhino3d.com/en/samples/cpp/add-line-curve/ Demonstrates how to add a line curve. ```cpp CRhinoCommand::result CCommandTest::RunCommand(const CRhinoCommandContext& context) { CRhinoGetPoint gp; gp.SetCommandPrompt( L"Start of line" ); gp.GetPoint(); if( gp.CommandResult() != CRhinoCommand::success ) return gp.CommandResult(); ON_3dPoint pt_start = gp.Point(); gp.SetCommandPrompt( L"End of line" ); gp.SetBasePoint( pt_start ); gp.DrawLineFromPoint( pt_start, TRUE ); gp.GetPoint(); if( gp.CommandResult() != CRhinoCommand::success ) return gp.CommandResult(); ON_3dPoint pt_end = gp.Point(); ON_3dVector v = pt_end - pt_start; if( v.IsTiny() ) return CRhinoCommand::nothing; ON_Line line( pt_start, pt_end ); context.m_doc.AddCurveObject( line ); context.m_doc.Redraw(); return CRhinoCommand::success; } ``` -------------------------------------------------------------------------------- # Add a Linear Dimension Source: https://developer.rhino3d.com/en/samples/cpp/add-linear-dimension/ Demonstrates how to add a linear dimension object. ```cpp // The following is a demonstration of how to interactively add a linear dimension object to Rhino. CRhinoCommand::result CCommandTest::RunCommand( const CRhinoCommandContext& context ) { CRhinoLinearDimension* pDim = 0; CArgsRhinoDimLinear args; args.SetFirstPointPrompt( L"First dimension point" ); args.SetSecondPointPrompt( L"Second dimension point" ); args.SetDragPointPrompt( L"Dimension location" ); args.SetIsInteractive( context.IsInteractive() ? true : false ); CRhinoCommand::result rc = RhinoGetDimLinear( args, pDim, 0 ); if( rc == success && pDim ) { context.m_doc.AddObject( pDim, FALSE); context.m_doc.Redraw(); } return rc; } ``` -------------------------------------------------------------------------------- # Add a New Layer Source: https://developer.rhino3d.com/en/samples/cpp/add-new-layer/ Demonstrates how to add a new layer to Rhino. ```cpp CRhinoCommand::result CCommandTest::RunCommand( const CRhinoCommandContext& context ) { // Get reference to the document's layer table CRhinoLayerTable& layer_table = context.m_doc.m_layer_table; // Cook up an unused layer name ON_wString unused_name; layer_table.GetUnusedLayerName( unused_name ); // Prompt the user to enter a layer name CRhinoGetString gs; gs.SetCommandPrompt( L"Name of layer to add" ); gs.SetDefaultString( unused_name ); gs.AcceptNothing( TRUE ); gs.GetString(); if( gs.CommandResult() != CRhinoCommand::success ) return gs.CommandResult(); // Was a layer named entered? ON_wString layer_name = gs.String(); layer_name.TrimLeftAndRight(); if( layer_name.IsEmpty() ) { RhinoApp().Print( L"Layer name cannot be blank.\n" ); return CRhinoCommand::cancel; } // Is the layer name valid? if( !RhinoIsValidName(layer_name) ) { RhinoApp().Print( L"\"%s\" is not a valid layer name.\n", layer_name ); return CRhinoCommand::cancel; } // Does a layer with the same name already exist? int layer_index = layer_table.FindLayer( layer_name ); if( layer_index >= 0 ) { RhinoApp().Print( L"A layer with the name \"%s\" already exists.\n", layer_name ); return CRhinoCommand::cancel; } // Create a new layer ON_Layer layer; layer.SetLayerName( layer_name ); // Add the layer to the layer table layer_index = layer_table.AddLayer( layer ); if( layer_index < 0 ) { RhinoApp().Print( L"Unable to add \"%s\" layer.\n", layer_name ); return CRhinoCommand::failure; } return CRhinoCommand::success; } ``` -------------------------------------------------------------------------------- # Add a NURBS Circle Source: https://developer.rhino3d.com/en/samples/cpp/add-nurbs-circle/ Demonstrates how to use ON_NurbsCurve to create a circle. ```cpp CRhinoCommand::result CCommandTest::RunCommand(const CRhinoCommandContext& context ) { int dimension = 3; BOOL bIsRational = TRUE; int order = 3; int cv_count = 9; ON_NurbsCurve nc( dimension, bIsRational, order, cv_count ); nc.SetCV( 0, ON_4dPoint(1.0, 0.0, 0.0, 1.0) ); nc.SetCV( 1, ON_4dPoint(0.707107, 0.707107, 0.0, 0.707107) ); nc.SetCV( 2, ON_4dPoint(0.0, 1.0, 0.0, 1.0) ); nc.SetCV( 3, ON_4dPoint(-0.707107, 0.707107, 0.0, 0.707107) ); nc.SetCV( 4, ON_4dPoint(-1.0, 0.0, 0.0, 1.0) ); nc.SetCV( 5, ON_4dPoint(-0.707107, -0.707107, 0.0, 0.707107) ); nc.SetCV( 6, ON_4dPoint(0.0, -1.0, 0.0, 1.0) ); nc.SetCV( 7, ON_4dPoint(0.707107, -0.707107, 0.0, 0.707107) ); nc.SetCV( 8, ON_4dPoint(1.0, 0.0, 0.0, 1.0) ); nc.SetKnot( 0, 0.0 ); nc.SetKnot( 1, 0.0 ); nc.SetKnot( 2, 0.5*ON_PI ); nc.SetKnot( 3, 0.5*ON_PI ); nc.SetKnot( 4, ON_PI ); nc.SetKnot( 5, ON_PI ); nc.SetKnot( 6, 1.5*ON_PI ); nc.SetKnot( 7, 1.5*ON_PI ); nc.SetKnot( 8, 2.0*ON_PI ); nc.SetKnot( 9, 2.0*ON_PI ); if( nc.IsValid() ) { context.m_doc.AddCurveObject( nc ); context.m_doc.Redraw(); } return CRhinoCommand::success; } ``` -------------------------------------------------------------------------------- # Add Annotation Text Source: https://developer.rhino3d.com/en/samples/rhinocommon/add-annotation-text/ Demonstrates how to add annotation text to a Rhino model at a specific location. ```cs partial class Examples { public static Rhino.Commands.Result AddAnnotationText(Rhino.RhinoDoc doc) { Rhino.Geometry.Point3d pt = new Rhino.Geometry.Point3d(10, 0, 0); const string text = "Hello RhinoCommon"; const double height = 2.0; const string font = "Arial"; Rhino.Geometry.Plane plane = doc.Views.ActiveView.ActiveViewport.ConstructionPlane(); plane.Origin = pt; Guid id = doc.Objects.AddText(text, plane, height, font, false, false); if( id != Guid.Empty ) { doc.Views.Redraw(); return Rhino.Commands.Result.Success; } return Rhino.Commands.Result.Failure; } } ``` ```python import Rhino import scriptcontext import System.Guid def AddAnnotationText(): pt = Rhino.Geometry.Point3d(10, 0, 0) text = "Hello RhinoCommon" height = 2.0 font = "Arial" plane = scriptcontext.doc.Views.ActiveView.ActiveViewport.ConstructionPlane() plane.Origin = pt id = scriptcontext.doc.Objects.AddText(text, plane, height, font, False, False) if id!=System.Guid.Empty: scriptcontext.doc.Views.Redraw() return Rhino.Commands.Result.Success return Rhino.Commands.Result.Failure if __name__=="__main__": AddAnnotationText() ``` -------------------------------------------------------------------------------- # Add Arrowheads to Curves Source: https://developer.rhino3d.com/en/samples/cpp/add-arrowheads-to-curves/ Demonstrates how arrowheads can be added to any curve object my modifying attributes. ```cpp CRhinoCommand::result CCommandTest::RunCommand( const CRhinoCommandContext& context ) { // Define a line ON_Line line; line.from = ON_3dPoint(0, 0, 0); line.to = ON_3dPoint(10, 0, 0); // Make a copy of Rhino's default object attributes ON_3dmObjectAttributes attribs; context.m_doc.GetDefaultObjectAttributes( attribs ); // Modify the object decoration style //attribs.m_object_decoration = ON::no_object_decoration; //attribs.m_object_decoration = ON::start_arrowhead; //attribs.m_object_decoration = ON::end_arrowhead; attribs.m_object_decoration = ON::both_arrowhead; // Create a new curve object with our attributes context.m_doc.AddCurveObject( line, &attribs ); context.m_doc.Redraw(); return CRhinoCommand::success; } ``` -------------------------------------------------------------------------------- # Add Arrowheads to Curves Source: https://developer.rhino3d.com/en/samples/rhinocommon/add-arrowheads-to-curves/ Demonstrates how to decorate curves in a Rhino model with arrowheads. ```cs partial class Examples { public static Rhino.Commands.Result ObjectDecoration(Rhino.RhinoDoc doc) { // Define a line var line = new Rhino.Geometry.Line(new Rhino.Geometry.Point3d(0, 0, 0), new Rhino.Geometry.Point3d(10, 0, 0)); // Make a copy of Rhino's default object attributes var attribs = doc.CreateDefaultAttributes(); // Modify the object decoration style attribs.ObjectDecoration = Rhino.DocObjects.ObjectDecoration.BothArrowhead; // Create a new curve object with our attributes doc.Objects.AddLine(line, attribs); doc.Views.Redraw(); return Rhino.Commands.Result.Success; } } ``` ```python import Rhino import scriptcontext def ObjectDecoration(): # Define a line line = Rhino.Geometry.Line(Rhino.Geometry.Point3d(0, 0, 0), Rhino.Geometry.Point3d(10, 0, 0)) # Make a copy of Rhino's default object attributes attribs = scriptcontext.doc.CreateDefaultAttributes() # Modify the object decoration style attribs.ObjectDecoration = Rhino.DocObjects.ObjectDecoration.BothArrowhead # Create a new curve object with our attributes scriptcontext.doc.Objects.AddLine(line, attribs) scriptcontext.doc.Views.Redraw() if __name__ == "__main__": ObjectDecoration() ``` -------------------------------------------------------------------------------- # Add Background Bitmap Source: https://developer.rhino3d.com/en/samples/rhinocommon/add-background-bitmap/ Demonstrates how to add a background bitmap to a Rhino model at a user-specified location. ```cs partial class Examples { public static Rhino.Commands.Result AddBackgroundBitmap(Rhino.RhinoDoc doc) { Rhino.RhinoApp.WriteLine ("hey"); // Allow the user to select a bitmap file Rhino.UI.OpenFileDialog fd = new Rhino.UI.OpenFileDialog(); fd.Filter = "Image Files (*.bmp;*.png;*.jpg)|*.bmp;*.png;*.jpg"; if (!fd.ShowDialog()) return Rhino.Commands.Result.Cancel; // Verify the file that was selected System.Drawing.Image image; try { image = System.Drawing.Image.FromFile(fd.FileName); } catch (Exception) { return Rhino.Commands.Result.Failure; } // Allow the user to pick the bitmap origin Rhino.Input.Custom.GetPoint gp = new Rhino.Input.Custom.GetPoint(); gp.SetCommandPrompt("Bitmap Origin"); gp.ConstrainToConstructionPlane(true); gp.Get(); if (gp.CommandResult() != Rhino.Commands.Result.Success) return gp.CommandResult(); // Get the view that the point was picked in. // This will be the view that the bitmap appears in. Rhino.Display.RhinoView view = gp.View(); if (view == null) { view = doc.Views.ActiveView; if (view == null) return Rhino.Commands.Result.Failure; } // Allow the user to specify the bitmap with in model units Rhino.Input.Custom.GetNumber gn = new Rhino.Input.Custom.GetNumber(); gn.SetCommandPrompt("Bitmap width"); gn.SetLowerLimit(1.0, false); gn.Get(); if (gn.CommandResult() != Rhino.Commands.Result.Success) return gn.CommandResult(); // Cook up some scale factors double w = gn.Number(); double image_width = image.Width; double image_height = image.Height; double h = w * (image_height / image_width); Rhino.Geometry.Plane plane = view.ActiveViewport.ConstructionPlane(); plane.Origin = gp.Point(); view.ActiveViewport.SetTraceImage(fd.FileName, plane, w, h, false, false); view.Redraw(); return Rhino.Commands.Result.Success; } } ``` ```python import Rhino import scriptcontext import System.Windows.Forms.DialogResult import System.Drawing.Image def AddBackgroundBitmap(): # Allow the user to select a bitmap file fd = Rhino.UI.OpenFileDialog() fd.Filter = "Image Files (*.bmp;*.png;*.jpg)|*.bmp;*.png;*.jpg" if not fd.ShowDialog(): return Rhino.Commands.Result.Cancel # Verify the file that was selected image = None try: image = System.Drawing.Image.FromFile(fd.FileName) except: return Rhino.Commands.Result.Failure # Allow the user to pick the bitmap origin gp = Rhino.Input.Custom.GetPoint() gp.SetCommandPrompt("Bitmap Origin") gp.ConstrainToConstructionPlane(True) gp.Get() if gp.CommandResult()!=Rhino.Commands.Result.Success: return gp.CommandResult() # Get the view that the point was picked in. # This will be the view that the bitmap appears in. view = gp.View() if view is None: view = scriptcontext.doc.Views.ActiveView if view is None: return Rhino.Commands.Result.Failure # Allow the user to specify the bitmap with in model units gn = Rhino.Input.Custom.GetNumber() gn.SetCommandPrompt("Bitmap width") gn.SetLowerLimit(1.0, False) gn.Get() if gn.CommandResult()!=Rhino.Commands.Result.Success: return gn.CommandResult() # Cook up some scale factors w = gn.Number() h = w * (image.Width / image.Height) plane = view.ActiveViewport.ConstructionPlane() plane.Origin = gp.Point() view.ActiveViewport.SetTraceImage(fd.FileName, plane, w, h, False, False) view.Redraw() return Rhino.Commands.Result.Success if __name__=="__main__": AddBackgroundBitmap() ``` -------------------------------------------------------------------------------- # Add Background Bitmaps to Viewports Source: https://developer.rhino3d.com/en/samples/cpp/add-background-bitmaps-to-viewports/ Demonstrates how to add a background bitmap to a viewport. ```cpp CRhinoCommand::result CCommandTest::RunCommand( const CRhinoCommandContext& context ) { // Allow the user to select a bitmap file ON_wString bitmap_filename; CRhinoGetFileDialog gf; gf.SetScriptMode( context.IsInteractive() ? FALSE : TRUE ); BOOL rc = gf.DisplayFileDialog( CRhinoGetFileDialog::open_bitmap_dialog, bitmap_filename, CWnd::FromHandle( RhinoApp().MainWnd() ) ); if( !rc ) return nothing; // Verify the file that was selected bitmap_filename = gf.FileName(); bitmap_filename.TrimLeftAndRight(); if( bitmap_filename.IsEmpty() ) return nothing; // Verify that the bitmap file is valid CRhinoDib dib; if( !dib.ReadFromFile(bitmap_filename) ) { RhinoApp().Print( L"The specified file cannot be identifed as a supported type.\n" ); return nothing; } // Allow the user to pick the bitmap origin CRhinoGetPoint gp; gp.SetCommandPrompt( L"Bitmap origin" ); gp.ConstrainToConstructionPlane(); gp.GetPoint(); if( gp.CommandResult() != success ) return gp.CommandResult(); // Get the view that the point was picked in. // This will be the view that the bitmap appears in. CRhinoView* view = gp.View(); if( !view ) { view = RhinoApp().ActiveView(); if( !view ) return failure; } // Allow the user to specify the bitmap with in model units CRhinoGetNumber gn; gn.SetCommandPrompt( L"Bitmap width" ); gn.SetLowerLimit( 1.0 ); gn.GetNumber(); if( gn.CommandResult() != success ) return gn.CommandResult(); // Cook up some scale factors double w = gn.Number(); double dib_width = (double)dib.Width(); double dib_height = (double)dib.Height(); double h = w * ( dib_height / dib_width ); // Calculate the 3-D points that bound the bitmap ON_3dPoint rect[4]; rect[0] = gp.Point(); rect[1] = ON_3dPoint(rect[0].x+w, rect[0].y, rect[0].z); rect[2] = ON_3dPoint(rect[0].x+w, rect[0].y+h, rect[0].z); rect[3] = ON_3dPoint(rect[0].x, rect[0].y+h, rect[0].z); // Get Rhino's bitmap table CRhinoBitmapTable& bitmap_table = context.m_doc.m_bitmap_table; // Search the bitmap table to see if the bitmap has already // been used. If so, delete the bitmap so Rhino will not just // reuse the one it already has (if we want to re-read the bitmap // file const CRhinoBitmap* rhino_bitmap = bitmap_table.Bitmap( bitmap_filename ); if( rhino_bitmap ) { view->ActiveViewport().SetTraceImage( ON_3dmViewTraceImage() ); bitmap_table.DeleteBitmap( bitmap_filename ); } rhino_bitmap = 0; // Create the trace image (background bitmap) object ON_3dmViewTraceImage trace_image; trace_image.m_bitmap_filename = bitmap_filename; trace_image.m_bGrayScale = false; trace_image.m_plane = view->ActiveViewport().ConstructionPlane().m_plane; trace_image.m_plane.SetOrigin( rect[0] ); trace_image.m_width = fabs( (rect[0] - rect[1]).Length() ); trace_image.m_height = fabs( (rect[0] - rect[3]).Length() ); trace_image.m_bHidden = false; // Add the trace image to the viewport view->ActiveViewport().SetTraceImage( trace_image ); view->Redraw(); return success; } ``` -------------------------------------------------------------------------------- # Add Brep Box Source: https://developer.rhino3d.com/en/samples/rhinocommon/add-brep-box/ Demonstrates how to add a Brep Box to a Rhino model by specifying two points. ```cs partial class Examples { public static Rhino.Commands.Result AddBrepBox(Rhino.RhinoDoc doc) { Rhino.Geometry.Point3d pt0 = new Rhino.Geometry.Point3d(0, 0, 0); Rhino.Geometry.Point3d pt1 = new Rhino.Geometry.Point3d(10, 10, 10); Rhino.Geometry.BoundingBox box = new Rhino.Geometry.BoundingBox(pt0, pt1); Rhino.Geometry.Brep brep = box.ToBrep(); Rhino.Commands.Result rc = Rhino.Commands.Result.Failure; if( doc.Objects.AddBrep(brep) != System.Guid.Empty ) { rc = Rhino.Commands.Result.Success; doc.Views.Redraw(); } return rc; } } ``` ```python import Rhino import scriptcontext import System.Guid def AddBrepBox(): pt0 = Rhino.Geometry.Point3d(0, 0, 0) pt1 = Rhino.Geometry.Point3d(10, 10, 10) box = Rhino.Geometry.BoundingBox(pt0, pt1) brep = box.ToBrep() rc = Rhino.Commands.Result.Failure if( scriptcontext.doc.Objects.AddBrep(brep) != System.Guid.Empty ): rc = Rhino.Commands.Result.Success scriptcontext.doc.Views.Redraw() return rc if( __name__ == "__main__" ): AddBrepBox() ``` -------------------------------------------------------------------------------- # Add Child Layer Source: https://developer.rhino3d.com/en/samples/rhinocommon/add-child-layer/ Demonstrates how to add a child (or sub) layer to a parent layer in a Rhino model. ```cs partial class Examples { public static Rhino.Commands.Result AddChildLayer(Rhino.RhinoDoc doc) { // Get an existing layer string default_name = doc.Layers.CurrentLayer.Name; // Prompt the user to enter a layer name Rhino.Input.Custom.GetString gs = new Rhino.Input.Custom.GetString(); gs.SetCommandPrompt("Name of existing layer"); gs.SetDefaultString(default_name); gs.AcceptNothing(true); gs.Get(); if (gs.CommandResult() != Rhino.Commands.Result.Success) return gs.CommandResult(); // Was a layer named entered? string layer_name = gs.StringResult().Trim(); int index = doc.Layers.Find(layer_name, true); if (index<0) return Rhino.Commands.Result.Cancel; Rhino.DocObjects.Layer parent_layer = doc.Layers[index]; // Create a child layer string child_name = parent_layer.Name + "_child"; Rhino.DocObjects.Layer childlayer = new Rhino.DocObjects.Layer(); childlayer.ParentLayerId = parent_layer.Id; childlayer.Name = child_name; childlayer.Color = System.Drawing.Color.Red; index = doc.Layers.Add(childlayer); if (index < 0) { Rhino.RhinoApp.WriteLine("Unable to add {0} layer.", child_name); return Rhino.Commands.Result.Failure; } return Rhino.Commands.Result.Success; } } ``` ```python import Rhino import scriptcontext import System.Drawing def AddChildLayer(): # Get an existing layer default_name = scriptcontext.doc.Layers.CurrentLayer.Name # Prompt the user to enter a layer name gs = Rhino.Input.Custom.GetString() gs.SetCommandPrompt("Name of existing layer") gs.SetDefaultString(default_name) gs.AcceptNothing(True) gs.Get() if gs.CommandResult()!=Rhino.Commands.Result.Success: return gs.CommandResult() # Was a layer named entered? layer_name = gs.StringResult().strip() index = scriptcontext.doc.Layers.FindByFullPath(layer_name, -1) if index<0: return Rhino.Commands.Result.Cancel parent_layer = scriptcontext.doc.Layers[index] # Create a child layer child_name = parent_layer.Name + "_child" childlayer = Rhino.DocObjects.Layer() childlayer.ParentLayerId = parent_layer.Id childlayer.Name = child_name childlayer.Color = System.Drawing.Color.Red index = scriptcontext.doc.Layers.Add(childlayer) if index<0: print("Unable to add", child_name, "layer.") return Rhino.Commands.Result.Failure return Rhino.Commands.Result.Success if __name__=="__main__": AddChildLayer() ``` -------------------------------------------------------------------------------- # Add Circle Source: https://developer.rhino3d.com/en/samples/rhinocommon/add-circle/ Demonstrates how to add a circle from a center point and radius. ```cs partial class Examples { public static Rhino.Commands.Result AddCircle(Rhino.RhinoDoc doc) { Rhino.Geometry.Point3d center = new Rhino.Geometry.Point3d(0, 0, 0); const double radius = 10.0; Rhino.Geometry.Circle c = new Rhino.Geometry.Circle(center, radius); if (doc.Objects.AddCircle(c) != Guid.Empty) { doc.Views.Redraw(); return Rhino.Commands.Result.Success; } return Rhino.Commands.Result.Failure; } } ``` ```python import Rhino import scriptcontext from System import Guid def AddCircle(): center = Rhino.Geometry.Point3d(0, 0, 0) radius = 10.0 c = Rhino.Geometry.Circle(center, radius) if scriptcontext.doc.Objects.AddCircle(c)!= Guid.Empty: scriptcontext.doc.Views.Redraw() return Rhino.Commands.Result.Success return Rhino.Commands.Result.Failure if __name__=="__main__": AddCircle() ``` -------------------------------------------------------------------------------- # Add Clipping Plane Source: https://developer.rhino3d.com/en/samples/rhinocommon/add-clipping-plane/ Demonstrates how to add a clipping plane from an array or corner points. ```cs partial class Examples { public static Rhino.Commands.Result AddClippingPlane(Rhino.RhinoDoc doc) { // Define the corners of the clipping plane Rhino.Geometry.Point3d[] corners; Rhino.Commands.Result rc = Rhino.Input.RhinoGet.GetRectangle(out corners); if (rc != Rhino.Commands.Result.Success) return rc; // Get the active view Rhino.Display.RhinoView view = doc.Views.ActiveView; if (view == null) return Rhino.Commands.Result.Failure; Rhino.Geometry.Point3d p0 = corners[0]; Rhino.Geometry.Point3d p1 = corners[1]; Rhino.Geometry.Point3d p3 = corners[3]; // Create a plane from the corner points Rhino.Geometry.Plane plane = new Rhino.Geometry.Plane(p0, p1, p3); // Add a clipping plane object to the document Guid id = doc.Objects.AddClippingPlane(plane, p0.DistanceTo(p1), p0.DistanceTo(p3), view.ActiveViewportID); if (id != Guid.Empty) { doc.Views.Redraw(); return Rhino.Commands.Result.Success; } return Rhino.Commands.Result.Failure; } } ``` ```python import Rhino import scriptcontext import System.Guid def AddClippingPlane(): # Define the corners of the clipping plane rc, corners = Rhino.Input.RhinoGet.GetRectangle() if rc!=Rhino.Commands.Result.Success: return rc # Get the active view view = scriptcontext.doc.Views.ActiveView if view is None: return Rhino.Commands.Result.Failure p0, p1, p2, p3 = corners # Create a plane from the corner points plane = Rhino.Geometry.Plane(p0, p1, p3) # Add a clipping plane object to the document id = scriptcontext.doc.Objects.AddClippingPlane(plane, p0.DistanceTo(p1), p0.DistanceTo(p3), view.ActiveViewportID) if id!=System.Guid.Empty: scriptcontext.doc.Views.Redraw() return Rhino.Commands.Result.Success return Rhino.Commands.Result.Failure if __name__=="__main__": AddClippingPlane() ``` -------------------------------------------------------------------------------- # Add Clipping Planes Source: https://developer.rhino3d.com/en/samples/cpp/add-clipping-planes/ Demonstrates how to add a viewport clipping plane object. ```cpp CRhinoCommand::result CCommandTest::RunCommand( const CRhinoCommandContext& context ) { // Define the corners of the clipping plane CArgsRhinoGetPlane args; ON_3dPoint corners[4]; CRhinoHistory history( *this ); CRhinoCommand::result rc = RhinoGetRectangle( args, corners, &history ); if( rc != CRhinoCommand::success ) return rc; // Get the active view CRhinoView* view = RhinoApp().ActiveView(); if( 0 == view ) return CRhinoCommand::failure; const ON_3dPoint& p0 = corners[0]; const ON_3dPoint& p1 = corners[1]; const ON_3dPoint& p2 = corners[2]; const ON_3dPoint& p3 = corners[3]; ON_Interval domain0, domain1; domain0.Set( 0.0, p0.DistanceTo(p1) ); domain1.Set( 0.0, p0.DistanceTo(p3) ); // Create a plane from the corner points ON_Plane plane( p0, p1, p3); // Create a plane surface ON_PlaneSurface plane_srf( plane ); plane_srf.SetExtents( 0, domain0, true ); plane_srf.SetExtents( 1, domain1, true ); plane_srf.SetDomain( 0, domain0.Min(), domain0.Max() ); plane_srf.SetDomain( 1, domain1.Min(), domain1.Max() ); // Create a clipping plane object rc = CRhinoCommand::failure; CRhinoClippingPlaneObject* plane_obj = new CRhinoClippingPlaneObject(); if( plane_obj ) { plane_obj->SetPlaneSurface( plane_srf ); // Add the clipping plane if( plane_obj->AddClipViewport(view->ActiveViewport().ViewportId()) ) { context.m_doc.AddObject( plane_obj ); context.m_doc.Regen(); rc = CRhinoCommand::success; } else { delete plane_obj; plane_obj = 0; } } return rc; } ``` -------------------------------------------------------------------------------- # Add Command Line Options Source: https://developer.rhino3d.com/en/samples/rhinocommon/add-command-line-options/ Demonstrates how to add command-line options as inputs to your command. ```cs partial class Examples { public static Rhino.Commands.Result CommandLineOptions(Rhino.RhinoDoc doc) { // For this example we will use a GetPoint class, but all of the custom // "Get" classes support command line options. Rhino.Input.Custom.GetPoint gp = new Rhino.Input.Custom.GetPoint(); gp.SetCommandPrompt("GetPoint with options"); // set up the options Rhino.Input.Custom.OptionInteger intOption = new Rhino.Input.Custom.OptionInteger(1, 1, 99); Rhino.Input.Custom.OptionDouble dblOption = new Rhino.Input.Custom.OptionDouble(2.2, 0, 99.9); Rhino.Input.Custom.OptionToggle boolOption = new Rhino.Input.Custom.OptionToggle(true, "Off", "On"); string[] listValues = new string[] { "Item0", "Item1", "Item2", "Item3", "Item4" }; gp.AddOptionInteger("Integer", ref intOption); gp.AddOptionDouble("Double", ref dblOption); gp.AddOptionToggle("Boolean", ref boolOption); int listIndex = 3; int opList = gp.AddOptionList("List", listValues, listIndex); while (true) { // perform the get operation. This will prompt the user to input a point, but also // allow for command line options defined above Rhino.Input.GetResult get_rc = gp.Get(); if (gp.CommandResult() != Rhino.Commands.Result.Success) return gp.CommandResult(); if (get_rc == Rhino.Input.GetResult.Point) { doc.Objects.AddPoint(gp.Point()); doc.Views.Redraw(); Rhino.RhinoApp.WriteLine("Command line option values are"); Rhino.RhinoApp.WriteLine(" Integer = {0}", intOption.CurrentValue); Rhino.RhinoApp.WriteLine(" Double = {0}", dblOption.CurrentValue); Rhino.RhinoApp.WriteLine(" Boolean = {0}", boolOption.CurrentValue); Rhino.RhinoApp.WriteLine(" List = {0}", listValues[listIndex]); } else if (get_rc == Rhino.Input.GetResult.Option) { if (gp.OptionIndex() == opList) listIndex = gp.Option().CurrentListOptionIndex; continue; } break; } return Rhino.Commands.Result.Success; } } ``` ```python import Rhino import scriptcontext def CommandLineOptions(): # For this example we will use a GetPoint class, but all of the custom # "Get" classes support command line options. gp = Rhino.Input.Custom.GetPoint() gp.SetCommandPrompt("GetPoint with options") # set up the options intOption = Rhino.Input.Custom.OptionInteger(1, 1, 99) dblOption = Rhino.Input.Custom.OptionDouble(2.2, 0, 99.9) boolOption = Rhino.Input.Custom.OptionToggle(True, "Off", "On") listValues = "Item0", "Item1", "Item2", "Item3", "Item4" gp.AddOptionInteger("Integer", intOption) gp.AddOptionDouble("Double", dblOption) gp.AddOptionToggle("Boolean", boolOption) listIndex = 3 opList = gp.AddOptionList("List", listValues, listIndex) while True: # perform the get operation. This will prompt the user to # input a point, but also allow for command line options # defined above get_rc = gp.Get() if gp.CommandResult()!=Rhino.Commands.Result.Success: return gp.CommandResult() if get_rc==Rhino.Input.GetResult.Point: point = gp.Point() scriptcontext.doc.Objects.AddPoint(point) scriptcontext.doc.Views.Redraw() print("Command line option values are") print(" Integer =", intOption.CurrentValue) print(" Double =", dblOption.CurrentValue) print(" Boolean =", boolOption.CurrentValue) print(" List =", listValues[listIndex]) elif get_rc==Rhino.Input.GetResult.Option: if gp.OptionIndex()==opList: listIndex = gp.Option().CurrentListOptionIndex continue break return Rhino.Commands.Result.Success if __name__ == "__main__": CommandLineOptions() ``` -------------------------------------------------------------------------------- # Add Cone Source: https://developer.rhino3d.com/en/samples/rhinocommon/add-cone/ Demonstrates how to construct a cone using a plane, height, and radius. ```cs partial class Examples { public static Rhino.Commands.Result AddCone(Rhino.RhinoDoc doc) { Rhino.Geometry.Plane plane = Rhino.Geometry.Plane.WorldXY; const double height = 10; const double radius = 5; Rhino.Geometry.Cone cone = new Rhino.Geometry.Cone(plane, height, radius); if (cone.IsValid) { const bool cap_bottom = true; Rhino.Geometry.Brep cone_brep = cone.ToBrep(cap_bottom); if (cone_brep!=null) { doc.Objects.AddBrep(cone_brep); doc.Views.Redraw(); } } return Rhino.Commands.Result.Success; } } ``` ```python import Rhino import scriptcontext def AddCone(): plane = Rhino.Geometry.Plane.WorldXY height = 10 radius = 5 cone = Rhino.Geometry.Cone(plane, height, radius) if cone.IsValid: cap_bottom = True cone_brep = cone.ToBrep(cap_bottom) if cone_brep: scriptcontext.doc.Objects.AddBrep(cone_brep) scriptcontext.doc.Views.Redraw() return Rhino.Commands.Result.Success if __name__=="__main__": AddCone() ``` -------------------------------------------------------------------------------- # Add Cylinder Source: https://developer.rhino3d.com/en/samples/rhinocommon/add-cylinder/ Demonstrates how to construct a cylinder using a center-point, height and axis. ```cs partial class Examples { public static Rhino.Commands.Result AddCylinder(Rhino.RhinoDoc doc) { Rhino.Geometry.Point3d center_point = new Rhino.Geometry.Point3d(0, 0, 0); Rhino.Geometry.Point3d height_point = new Rhino.Geometry.Point3d(0, 0, 10); Rhino.Geometry.Vector3d zaxis = height_point - center_point; Rhino.Geometry.Plane plane = new Rhino.Geometry.Plane(center_point, zaxis); const double radius = 5; Rhino.Geometry.Circle circle = new Rhino.Geometry.Circle(plane, radius); Rhino.Geometry.Cylinder cylinder = new Rhino.Geometry.Cylinder(circle, zaxis.Length); Rhino.Geometry.Brep brep = cylinder.ToBrep(true, true); if (brep != null) { doc.Objects.AddBrep(brep); doc.Views.Redraw(); } return Rhino.Commands.Result.Success; } } ``` ```python import Rhino import scriptcontext import System.Guid def AddCylinder(): center_point = Rhino.Geometry.Point3d(0, 0, 0) height_point = Rhino.Geometry.Point3d(0, 0, 10) zaxis = height_point-center_point plane = Rhino.Geometry.Plane(center_point, zaxis) radius = 5 circle = Rhino.Geometry.Circle(plane, radius) cylinder = Rhino.Geometry.Cylinder(circle, zaxis.Length) brep = cylinder.ToBrep(True, True) if brep: if scriptcontext.doc.Objects.AddBrep(brep)!=System.Guid.Empty: scriptcontext.doc.Views.Redraw() return Rhino.Commands.Result.Success return Rhino.Commands.Result.Failure if __name__=="__main__": AddCylinder() ``` -------------------------------------------------------------------------------- # Add Layer Source: https://developer.rhino3d.com/en/samples/rhinocommon/add-layer/ Demonstrates how to add a layer to a Rhino model and validate that it does not already exist. ```cs partial class Examples { public static Rhino.Commands.Result AddLayer(Rhino.RhinoDoc doc) { // Cook up an unused layer name string unused_name = doc.Layers.GetUnusedLayerName(false); // Prompt the user to enter a layer name Rhino.Input.Custom.GetString gs = new Rhino.Input.Custom.GetString(); gs.SetCommandPrompt("Name of layer to add"); gs.SetDefaultString(unused_name); gs.AcceptNothing(true); gs.Get(); if (gs.CommandResult() != Rhino.Commands.Result.Success) return gs.CommandResult(); // Was a layer named entered? string layer_name = gs.StringResult().Trim(); if (string.IsNullOrEmpty(layer_name)) { Rhino.RhinoApp.WriteLine("Layer name cannot be blank."); return Rhino.Commands.Result.Cancel; } // Is the layer name valid? if (!Rhino.DocObjects.Layer.IsValidName(layer_name)) { Rhino.RhinoApp.WriteLine(layer_name + " is not a valid layer name."); return Rhino.Commands.Result.Cancel; } // Does a layer with the same name already exist? int layer_index = doc.Layers.Find(layer_name, true); if (layer_index >= 0) { Rhino.RhinoApp.WriteLine("A layer with the name {0} already exists.", layer_name); return Rhino.Commands.Result.Cancel; } // Add a new layer to the document layer_index = doc.Layers.Add(layer_name, System.Drawing.Color.Black); if (layer_index < 0) { Rhino.RhinoApp.WriteLine("Unable to add {0} layer.", layer_name); return Rhino.Commands.Result.Failure; } return Rhino.Commands.Result.Success; } } ``` ```python import Rhino import scriptcontext import System.Drawing def AddLayer(): # Cook up an unused layer name unused_name = scriptcontext.doc.Layers.GetUnusedLayerName(False) # Prompt the user to enter a layer name gs = Rhino.Input.Custom.GetString() gs.SetCommandPrompt("Name of layer to add") gs.SetDefaultString(unused_name) gs.AcceptNothing(True) gs.Get() if gs.CommandResult()!=Rhino.Commands.Result.Success: return gs.CommandResult() # Was a layer named entered? layer_name = gs.StringResult().strip() if not layer_name: print("Layer name cannot be blank.") return Rhino.Commands.Result.Cancel # Is the layer name valid? if not Rhino.DocObjects.Layer.IsValidName(layer_name): print(layer_name, "is not a valid layer name.") return Rhino.Commands.Result.Cancel # Does a layer with the same name already exist? layer_index = scriptcontext.doc.Layers.FindByFullPath(layer_name, -1) if layer_index>=0: print("A layer with the name", layer_name, "already exists.") return Rhino.Commands.Result.Cancel # Add a new layer to the document layer_index = scriptcontext.doc.Layers.Add(layer_name, System.Drawing.Color.Black) if layer_index<0: print("Unable to add", layer_name, "layer.") return Rhino.Commands.Result.Failure return Rhino.Commands.Result.Success if __name__=="__main__": AddLayer() ``` -------------------------------------------------------------------------------- # Add Layout Source: https://developer.rhino3d.com/en/samples/rhinocommon/add-layout/ Demonstrates how to generate a layout with a single detail view that zooms to a list of objects. ```cs partial class Examples { /// /// Generate a layout with a single detail view that zooms to a list of objects /// /// /// public static Rhino.Commands.Result AddLayout(Rhino.RhinoDoc doc) { doc.PageUnitSystem = Rhino.UnitSystem.Millimeters; var page_views = doc.Views.GetPageViews(); int page_number = (page_views==null) ? 1 : page_views.Length + 1; var pageview = doc.Views.AddPageView(string.Format("A0_{0}",page_number), 1189, 841); if( pageview!=null ) { Rhino.Geometry.Point2d top_left = new Rhino.Geometry.Point2d(20,821); Rhino.Geometry.Point2d bottom_right = new Rhino.Geometry.Point2d(1169, 20); var detail = pageview.AddDetailView("ModelView", top_left, bottom_right, Rhino.Display.DefinedViewportProjection.Top); if (detail != null) { pageview.SetActiveDetail(detail.Id); detail.Viewport.ZoomExtents(); detail.DetailGeometry.IsProjectionLocked = true; detail.DetailGeometry.SetScale(1, doc.ModelUnitSystem, 10, doc.PageUnitSystem); // Commit changes tells the document to replace the document's detail object // with the modified one that we just adjusted detail.CommitChanges(); } pageview.SetPageAsActive(); doc.Views.ActiveView = pageview; doc.Views.Redraw(); return Rhino.Commands.Result.Success; } return Rhino.Commands.Result.Failure; } } ``` ```python # No Python sample available ``` -------------------------------------------------------------------------------- # Add Leader Source: https://developer.rhino3d.com/en/samples/rhinocommon/add-leader/ Demonstrates how to add a leaders to your Rhino model from an array of points. ```cs partial class Examples { public static Result Leader(RhinoDoc doc) { var points = new Point3d[] { new Point3d(1, 1, 0), new Point3d(5, 1, 0), new Point3d(5, 5, 0), new Point3d(9, 5, 0) }; var xy_plane = Plane.WorldXY; var points2d = new List(); foreach (var point3d in points) { double x, y; if (xy_plane.ClosestParameter(point3d, out x, out y)) { var point2d = new Point2d(x, y); if (points2d.Count < 1 || point2d.DistanceTo(points2d.Last()) > RhinoMath.SqrtEpsilon) points2d.Add(point2d); } } doc.Objects.AddLeader(xy_plane, points2d); doc.Views.Redraw(); return Result.Success; } } ``` ```python import rhinoscriptsyntax as rs def RunCommand(): points = [(1,1,0), (5,1,0), (5,5,0), (9,5,0)] rs.AddLeader(points) if __name__ == "__main__": RunCommand() ``` -------------------------------------------------------------------------------- # Add Line Source: https://developer.rhino3d.com/en/samples/rhinocommon/add-line/ Demonstrates how to construct a line from two points. ```cs partial class Examples { public static Rhino.Commands.Result AddLine(Rhino.RhinoDoc doc) { Rhino.Input.Custom.GetPoint gp = new Rhino.Input.Custom.GetPoint(); gp.SetCommandPrompt("Start of line"); gp.Get(); if (gp.CommandResult() != Rhino.Commands.Result.Success) return gp.CommandResult(); Rhino.Geometry.Point3d pt_start = gp.Point(); gp.SetCommandPrompt("End of line"); gp.SetBasePoint(pt_start, false); gp.DrawLineFromPoint(pt_start, true); gp.Get(); if (gp.CommandResult() != Rhino.Commands.Result.Success) return gp.CommandResult(); Rhino.Geometry.Point3d pt_end = gp.Point(); Rhino.Geometry.Vector3d v = pt_end - pt_start; if (v.IsTiny(Rhino.RhinoMath.ZeroTolerance)) return Rhino.Commands.Result.Nothing; if (doc.Objects.AddLine(pt_start, pt_end) != Guid.Empty) { doc.Views.Redraw(); return Rhino.Commands.Result.Success; } return Rhino.Commands.Result.Failure; } } ``` ```python import Rhino import scriptcontext import System.Guid def AddLine(): gp = Rhino.Input.Custom.GetPoint() gp.SetCommandPrompt("Start of line") gp.Get() if gp.CommandResult()!=Rhino.Commands.Result.Success: return gp.CommandResult() pt_start = gp.Point() gp.SetCommandPrompt("End of line") gp.SetBasePoint(pt_start, False) gp.DrawLineFromPoint(pt_start, True) gp.Get() if gp.CommandResult() != Rhino.Commands.Result.Success: return gp.CommandResult() pt_end = gp.Point() v = pt_end - pt_start if v.IsTiny(Rhino.RhinoMath.ZeroTolerance): return Rhino.Commands.Result.Nothing id = scriptcontext.doc.Objects.AddLine(pt_start, pt_end) if id!=System.Guid.Empty: scriptcontext.doc.Views.Redraw() return Rhino.Commands.Result.Success return Rhino.Commands.Result.Failure if __name__=="__main__": AddLine() ``` -------------------------------------------------------------------------------- # Add Linear Dimension Source: https://developer.rhino3d.com/en/samples/rhinocommon/add-linear-dimension/ Demonstrates how to add a linear dimension to a Rhino model. ```cs partial class Examples { public static Rhino.Commands.Result AddLinearDimension(Rhino.RhinoDoc doc) { Rhino.Geometry.LinearDimension dimension; Rhino.Commands.Result rc = Rhino.Input.RhinoGet.GetLinearDimension(out dimension); if (rc == Rhino.Commands.Result.Success && dimension != null) { if (doc.Objects.AddLinearDimension(dimension) == Guid.Empty) rc = Rhino.Commands.Result.Failure; else doc.Views.Redraw(); } return rc; } } ``` ```python import Rhino import scriptcontext import System.Guid def AddLinearDimension(): rc, dimension = Rhino.Input.RhinoGet.GetLinearDimension() if rc==Rhino.Commands.Result.Success: if scriptcontext.doc.Objects.AddLinearDimension(dimension)==System.Guid.Empty: rc = Rhino.Commands.Result.Failure else: scriptcontext.doc.Views.Redraw() return rc if __name__=="__main__": AddLinearDimension() ``` -------------------------------------------------------------------------------- # Add Linear Dimension2 Source: https://developer.rhino3d.com/en/samples/rhinocommon/add-linear-dimension2/ Demonstrates how to add a linear dimension from two points given an offset point. ```cs partial class Examples { public static Rhino.Commands.Result AddLinearDimension2(Rhino.RhinoDoc doc) { Point3d origin = new Point3d(1,1,0); Point3d offset = new Point3d(11,1,0); Point3d pt = new Point3d((offset.X-origin.X)/2,3,0); Plane plane = Plane.WorldXY; plane.Origin = origin; double u,v; plane.ClosestParameter(origin, out u, out v); Point2d ext1 = new Point2d(u, v); plane.ClosestParameter(offset, out u, out v); Point2d ext2 = new Point2d(u, v); plane.ClosestParameter(pt, out u, out v); Point2d linePt = new Point2d(u, v); LinearDimension dimension = new LinearDimension(plane, ext1, ext2, linePt); if (doc.Objects.AddLinearDimension(dimension) != Guid.Empty) { doc.Views.Redraw(); return Rhino.Commands.Result.Success; } return Rhino.Commands.Result.Failure; } } ``` ```python import Rhino import scriptcontext import System.Guid def AddLinearDimension2(): origin = Rhino.Geometry.Point3d(1,1,0) offset = Rhino.Geometry.Point3d(11,1,0) pt = Rhino.Geometry.Point3d((offset.X-origin.X)/2.0,3,0) plane = Rhino.Geometry.Plane.WorldXY plane.Origin = origin rc, u, v = plane.ClosestParameter(origin) ext1 = Rhino.Geometry.Point2d(u,v) rc, u, v = plane.ClosestParameter(offset) ext2 = Rhino.Geometry.Point2d(u,v) rc, u, v = plane.ClosestParameter(pt) linePt = Rhino.Geometry.Point2d(u,v) dimension = Rhino.Geometry.LinearDimension(plane, ext1, ext2, linePt) if scriptcontext.doc.Objects.AddLinearDimension(dimension)!=System.Guid.Empty: scriptcontext.doc.Views.Redraw() return Rhino.Commands.Result.Success return Rhino.Commands.Result.Failure if __name__=="__main__": AddLinearDimension2() ``` -------------------------------------------------------------------------------- # Add Material Source: https://developer.rhino3d.com/en/samples/rhinocommon/add-material/ Demonstrates how to add a material to the document and apply it to a sphere object. ```cs partial class Examples { public static Rhino.Commands.Result AddMaterial(Rhino.RhinoDoc doc) { // Create a Rhino material with a texture. var rhino_material = new Material { Name = "Chocolate", DiffuseColor = System.Drawing.Color.Chocolate, SpecularColor = System.Drawing.Color.CadetBlue }; var texture = new Texture { FileName = "my_image.jpg" }; rhino_material.SetTexture(texture, TextureType.Bitmap); // Use the Rhino material to create a Render material. var render_material = RenderMaterial.CreateBasicMaterial(rhino_material, doc); doc.RenderMaterials.Add(render_material); // Create a sphere. var sphere = new Rhino.Geometry.Sphere(Rhino.Geometry.Plane.WorldXY, 5); // Add the sphere to the document. var id = doc.Objects.AddSphere(sphere); var obj = doc.Objects.FindId(id); if (obj != null) { // Assign the render material to the sphere object. obj.RenderMaterial = render_material; obj.CommitChanges(); } doc.Views.Redraw(); return Rhino.Commands.Result.Success; } } ``` ```python import Rhino import scriptcontext import System.Drawing def AddMaterial(): # Create a Rhino material. rhino_material = Rhino.DocObjects.Material(); rhino_material.Name = "Chocolate"; rhino_material.DiffuseColor = System.Drawing.Color.Chocolate; rhino_material.SpecularColor = System.Drawing.Color.CadetBlue; texture = Rhino.DocObjects.Texture() texture.FileName = "my_image.jpg" rhino_material.SetTexture(texture, Rhino.DocObjects.TextureType.Bitmap) # Use the Rhino material to create a Render material. render_material = Rhino.Render.RenderMaterial.CreateBasicMaterial(rhino_material, scriptcontext.doc) scriptcontext.doc.RenderMaterials.Add(render_material); # Create a sphere. sphere = Rhino.Geometry.Sphere(Rhino.Geometry.Plane.WorldXY, 5); # Add the sphere to the document with a material. id = scriptcontext.doc.Objects.AddSphere(sphere); obj = scriptcontext.doc.Objects.FindId(id); if obj is not None: # Assign the render material to the sphere object. obj.RenderMaterial = render_material; obj.CommitChanges(); scriptcontext.doc.Views.Redraw(); return Rhino.Commands.Result.Success; if __name__=="__main__": AddMaterial() ``` -------------------------------------------------------------------------------- # Add Mesh Source: https://developer.rhino3d.com/en/samples/rhinocommon/add-mesh/ Demonstrates how to construct a mesh from a list of vertices and faces. ```cs partial class Examples { public static Rhino.Commands.Result AddMesh(Rhino.RhinoDoc doc) { Rhino.Geometry.Mesh mesh = new Rhino.Geometry.Mesh(); mesh.Vertices.Add(0.0, 0.0, 1.0); //0 mesh.Vertices.Add(1.0, 0.0, 1.0); //1 mesh.Vertices.Add(2.0, 0.0, 1.0); //2 mesh.Vertices.Add(3.0, 0.0, 0.0); //3 mesh.Vertices.Add(0.0, 1.0, 1.0); //4 mesh.Vertices.Add(1.0, 1.0, 2.0); //5 mesh.Vertices.Add(2.0, 1.0, 1.0); //6 mesh.Vertices.Add(3.0, 1.0, 0.0); //7 mesh.Vertices.Add(0.0, 2.0, 1.0); //8 mesh.Vertices.Add(1.0, 2.0, 1.0); //9 mesh.Vertices.Add(2.0, 2.0, 1.0); //10 mesh.Vertices.Add(3.0, 2.0, 1.0); //11 mesh.Faces.AddFace(0, 1, 5, 4); mesh.Faces.AddFace(1, 2, 6, 5); mesh.Faces.AddFace(2, 3, 7, 6); mesh.Faces.AddFace(4, 5, 9, 8); mesh.Faces.AddFace(5, 6, 10, 9); mesh.Faces.AddFace(6, 7, 11, 10); mesh.Normals.ComputeNormals(); mesh.Compact(); if (doc.Objects.AddMesh(mesh) != Guid.Empty) { doc.Views.Redraw(); return Rhino.Commands.Result.Success; } return Rhino.Commands.Result.Failure; } } ``` ```python import Rhino import scriptcontext import System.Guid def AddMesh(): mesh = Rhino.Geometry.Mesh() mesh.Vertices.Add(0.0, 0.0, 1.0) #0 mesh.Vertices.Add(1.0, 0.0, 1.0) #1 mesh.Vertices.Add(2.0, 0.0, 1.0) #2 mesh.Vertices.Add(3.0, 0.0, 0.0) #3 mesh.Vertices.Add(0.0, 1.0, 1.0) #4 mesh.Vertices.Add(1.0, 1.0, 2.0) #5 mesh.Vertices.Add(2.0, 1.0, 1.0) #6 mesh.Vertices.Add(3.0, 1.0, 0.0) #7 mesh.Vertices.Add(0.0, 2.0, 1.0) #8 mesh.Vertices.Add(1.0, 2.0, 1.0) #9 mesh.Vertices.Add(2.0, 2.0, 1.0) #10 mesh.Vertices.Add(3.0, 2.0, 1.0) #11 mesh.Faces.AddFace(0, 1, 5, 4) mesh.Faces.AddFace(1, 2, 6, 5) mesh.Faces.AddFace(2, 3, 7, 6) mesh.Faces.AddFace(4, 5, 9, 8) mesh.Faces.AddFace(5, 6, 10, 9) mesh.Faces.AddFace(6, 7, 11, 10) mesh.Normals.ComputeNormals() mesh.Compact() if scriptcontext.doc.Objects.AddMesh(mesh)!=System.Guid.Empty: scriptcontext.doc.Views.Redraw() return Rhino.Commands.Result.Success return Rhino.Commands.Result.Failure if __name__=="__main__": AddMesh() ``` -------------------------------------------------------------------------------- # Add Mesh Box Source: https://developer.rhino3d.com/en/samples/rhinocommon/add-mesh-box/ Demonstrates how to construct a mesh box from a Brep box. ```cs partial class Examples { public static Rhino.Commands.Result AddMeshBox(Rhino.RhinoDoc doc) { Rhino.Geometry.Box box; Rhino.Commands.Result rc = Rhino.Input.RhinoGet.GetBox(out box); if (rc == Rhino.Commands.Result.Success) { Rhino.Geometry.Mesh mesh = Rhino.Geometry.Mesh.CreateFromBox(box, 2, 2, 2); if (null != mesh) { doc.Objects.AddMesh(mesh); doc.Views.Redraw(); return Rhino.Commands.Result.Success; } } return Rhino.Commands.Result.Failure; } } ``` ```python # No Python sample available ``` -------------------------------------------------------------------------------- # Add Named View Source: https://developer.rhino3d.com/en/samples/rhinocommon/add-named-view/ Demonstrates how to add a named view to a Rhino model from a user-specified view and camera location. ```cs partial class Examples { public static Rhino.Commands.Result AddNamedView(Rhino.RhinoDoc doc) { Rhino.Display.RhinoView view; Rhino.Commands.Result rc = Rhino.Input.RhinoGet.GetView("Select view to adjust", out view); if (rc != Rhino.Commands.Result.Success) return rc; Rhino.Geometry.Point3d location; rc = Rhino.Input.RhinoGet.GetPoint("Camera Location", false, out location); if (rc != Rhino.Commands.Result.Success) return rc; Rhino.Input.Custom.GetPoint gp = new Rhino.Input.Custom.GetPoint(); gp.SetCommandPrompt("Look At Location"); gp.DrawLineFromPoint(location, false); gp.Get(); if (gp.CommandResult() != Rhino.Commands.Result.Success) return gp.CommandResult(); Rhino.Geometry.Point3d lookat = gp.Point(); string name = view.ActiveViewport.Name; rc = Rhino.Input.RhinoGet.GetString("Name", true, ref name); if (rc != Rhino.Commands.Result.Success) return rc; Rhino.Display.RhinoViewport vp = view.ActiveViewport; // save the current viewport projection vp.PushViewProjection(); vp.CameraUp = Rhino.Geometry.Vector3d.ZAxis; vp.SetCameraLocation(location, false); vp.SetCameraDirection(lookat - location, true); vp.Name = name; doc.NamedViews.Add(name, vp.Id); view.Redraw(); return Rhino.Commands.Result.Success; } } ``` ```python import Rhino import scriptcontext import System.Guid def AddNamedView(): rc, view = Rhino.Input.RhinoGet.GetView("Select view to adjust") if rc!=Rhino.Commands.Result.Success: return rc rc, location = Rhino.Input.RhinoGet.GetPoint("Camera Location", False) if rc!=Rhino.Commands.Result.Success: return rc gp = Rhino.Input.Custom.GetPoint() gp.SetCommandPrompt("Look At Location") gp.DrawLineFromPoint(location, False) gp.Get() if gp.CommandResult()!=Rhino.Commands.Result.Success: return gp.CommandResult() lookat = gp.Point() name = view.ActiveViewport.Name rc, name = Rhino.Input.RhinoGet.GetString("Name", True, name) if rc!=Rhino.Commands.Result.Success: return rc vp = view.ActiveViewport # save the current viewport projection vp.PushViewProjection() vp.CameraUp = Rhino.Geometry.Vector3d.ZAxis vp.SetCameraLocation(location, False) vp.SetCameraDirection(lookat - location, True) vp.Name = name scriptcontext.doc.NamedViews.Add(name, vp.Id) view.Redraw() return Rhino.Commands.Result.Success if __name__=="__main__": AddNamedView() ``` -------------------------------------------------------------------------------- # Add Nested Block Source: https://developer.rhino3d.com/en/samples/rhinocommon/add-nested-block/ Demonstrates how to add a nested block to an instance definition. ```cs partial class Examples { public static Rhino.Commands.Result AddNestedBlock(RhinoDoc doc) { var circle = new Circle(Point3d.Origin, 5); Curve[] curveList = { new ArcCurve(circle) }; var circleIndex = doc.InstanceDefinitions.Add("Circle", "Circle with radius of 5", Point3d.Origin, curveList); var transform = Transform.Identity; var irefId = doc.InstanceDefinitions[circleIndex].Id; var iref = new InstanceReferenceGeometry(irefId, transform); circle.Radius = circle.Radius * 2.0; GeometryBase[] blockList = { iref, new ArcCurve(circle) }; var circle2Index = doc.InstanceDefinitions.Add("TwoCircles", "Nested block test", Point3d.Origin, blockList); doc.Objects.AddInstanceObject(circle2Index, transform); doc.Views.Redraw(); return Rhino.Commands.Result.Success; } } ``` ```python # No Python sample available ``` -------------------------------------------------------------------------------- # Add NURBS Circle Source: https://developer.rhino3d.com/en/samples/rhinocommon/add-nurbs-circle/ Demonstrates how to create a NURBS circle from scratch using points and knots. ```cs partial class Examples { public static Rhino.Commands.Result AddNurbsCircle(Rhino.RhinoDoc doc) { // The easy way to get a NURBS curve from a circle is with // the following two lines of code. // // Rhino.Geometry.Circle c = new Rhino.Geometry.Circle(20); // Rhino.Geometry.NurbsCurve nc = c.ToNurbsCurve(); // // This sample demonstrates creating a NURBS curve from scratch. const int dimension = 3; const bool isRational = true; const int order = 3; const int cv_count = 9; Rhino.Geometry.NurbsCurve nc = new Rhino.Geometry.NurbsCurve(dimension, isRational, order, cv_count); nc.Points.SetPoint(0, 1.0, 0.0, 0.0, 1.0); nc.Points.SetPoint(1, 0.707107, 0.707107, 0.0, 0.707107); nc.Points.SetPoint(2, 0.0, 1.0, 0.0, 1.0); nc.Points.SetPoint(3, -0.707107, 0.707107, 0.0, 0.707107); nc.Points.SetPoint(4, -1.0, 0.0, 0.0, 1.0); nc.Points.SetPoint(5, -0.707107, -0.707107, 0.0, 0.707107); nc.Points.SetPoint(6, 0.0, -1.0, 0.0, 1.0); nc.Points.SetPoint(7, 0.707107, -0.707107, 0.0, 0.707107); nc.Points.SetPoint(8, 1.0, 0.0, 0.0, 1.0); nc.Knots[0] = 0.0; nc.Knots[1] = 0.0; nc.Knots[2] = 0.5 * Math.PI; nc.Knots[3] = 0.5 * Math.PI; nc.Knots[4] = Math.PI; nc.Knots[5] = Math.PI; nc.Knots[6] = 1.5 * Math.PI; nc.Knots[7] = 1.5 * Math.PI; nc.Knots[8] = 2.0 * Math.PI; nc.Knots[9] = 2.0 * Math.PI; if (nc.IsValid) { doc.Objects.AddCurve(nc); doc.Views.Redraw(); return Rhino.Commands.Result.Success; } return Rhino.Commands.Result.Failure; } } ``` ```python import System from Rhino.Geometry import NurbsCurve from Rhino.Commands import Result from scriptcontext import doc def AddNurbsCircle(): # The easy way to get a NURBS curve from a circle is with # the following two lines of code. # # Circle c = new Circle(20) # NurbsCurve nc = c.ToNurbsCurve() # # This sample demonstrates creating a NURBS curve from scratch. dimension = 3 isRational = True order = 3 cv_count = 9 nc = NurbsCurve(dimension, isRational, order, cv_count) nc.Points.SetPoint(0, 1.0, 0.0, 0.0, 1.0) nc.Points.SetPoint(1, 1.0, 1.0, 0.0, 0.707107) nc.Points.SetPoint(2, 0.0, 1.0, 0.0, 1.0) nc.Points.SetPoint(3, -1.0, 1.0, 0.0, 0.707107) nc.Points.SetPoint(4, -1.0, 0.0, 0.0, 1.0) nc.Points.SetPoint(5, -1.0, -1.0, 0.0, 0.707107) nc.Points.SetPoint(6, 0.0, -1.0, 0.0, 1.0) nc.Points.SetPoint(7, 1.0, -1.0, 0.0, 0.707107) nc.Points.SetPoint(8, 1.0, 0.0, 0.0, 1.0) nc.Knots[0] = 0.0 nc.Knots[1] = 0.0 nc.Knots[2] = 0.5 * System.Math.PI nc.Knots[3] = 0.5 * System.Math.PI nc.Knots[4] = System.Math.PI nc.Knots[5] = System.Math.PI nc.Knots[6] = 1.5 * System.Math.PI nc.Knots[7] = 1.5 * System.Math.PI nc.Knots[8] = 2.0 * System.Math.PI nc.Knots[9] = 2.0 * System.Math.PI if nc.IsValid: doc.Objects.AddCurve(nc) doc.Views.Redraw() return Result.Success return Result.Failure if __name__=="__main__": AddNurbsCircle() ``` -------------------------------------------------------------------------------- # Add NURBS Curve Source: https://developer.rhino3d.com/en/samples/cpp/add-nurbs-curve/ Demonstrates how to add a NURBS curve to Rhino. ```cpp void TestNurbsCurve(CRhinoDoc& doc) { const int degree = 3; const int cv_count = 6; const int knot_count = cv_count + degree - 1; const int order = degree + 1; ON_3dPoint cvs[cv_count]; cvs[0] = ON_3dPoint(0.0, 0.0, 0.0); cvs[1] = ON_3dPoint(5.0, 10.0, 0.0); cvs[2] = ON_3dPoint(10.0, 0.0, 0.0); cvs[3] = ON_3dPoint(15.0, 10.0, 0.0); cvs[4] = ON_3dPoint(20.0, 0.0, 0.0); cvs[5] = ON_3dPoint(25.0, 10.0, 0.0); double knots[knot_count]; knots[0] = 0.0; knots[1] = 0.0; knots[2] = 0.0; knots[3] = 1.0; knots[4] = 2.0; knots[5] = 3.0; knots[6] = 3.0; knots[7] = 3.0; ON_NurbsCurve curve(3, FALSE, order, cv_count); for (int i = 0; i < cv_count; i++) curve.SetCV(i, cvs[i]); for (int i = 0; i < knot_count; i++) curve.m_knot[i] = knots[i]; if( curve.IsValid() ) { doc.AddCurveObject(curve); doc.Redraw(); } } ``` -------------------------------------------------------------------------------- # Add NURBS Curve Source: https://developer.rhino3d.com/en/samples/rhinocommon/add-nurbs-curve/ Demonstrates how to create a NURBS curve from a list of points. ```cs partial class Examples { public static Rhino.Commands.Result AddNurbsCurve(Rhino.RhinoDoc doc) { Rhino.Collections.Point3dList points = new Rhino.Collections.Point3dList(5); points.Add(0, 0, 0); points.Add(0, 2, 0); points.Add(2, 3, 0); points.Add(4, 2, 0); points.Add(4, 0, 0); Rhino.Geometry.NurbsCurve nc = Rhino.Geometry.NurbsCurve.Create(false, 3, points); Rhino.Commands.Result rc = Rhino.Commands.Result.Failure; if (nc != null && nc.IsValid) { if (doc.Objects.AddCurve(nc) != Guid.Empty) { doc.Views.Redraw(); rc = Rhino.Commands.Result.Success; } } return rc; } } ``` ```python import Rhino import scriptcontext import System.Guid def AddNurbsCurve(): points = Rhino.Collections.Point3dList(5) points.Add(0, 0, 0) points.Add(0, 2, 0) points.Add(2, 3, 0) points.Add(4, 2, 0) points.Add(4, 0, 0) nc = Rhino.Geometry.NurbsCurve.Create(False, 3, points) rc = Rhino.Commands.Result.Failure if nc and nc.IsValid: if scriptcontext.doc.Objects.AddCurve(nc)!=System.Guid.Empty: scriptcontext.doc.Views.Redraw() rc = Rhino.Commands.Result.Success return rc if __name__=="__main__": AddNurbsCurve() ``` -------------------------------------------------------------------------------- # Add NURBS Curve Source: https://developer.rhino3d.com/en/samples/rhinoscript/add-nurbs-curve/ Demonstrates how to add a NURBS curve to Rhino using RhinoScript. ```vbnet Sub TestNurbsCurve Dim degree : degree = 3 Dim cv_count : cv_count = 6 Dim knot_count : knot_count = cv_count + degree - 1 Dim cvs() : ReDim cvs(cv_count - 1) cvs(0) = Array(0.0, 0.0, 0.0) cvs(1) = Array(5.0, 10.0, 0.0) cvs(2) = Array(10.0, 0.0, 0.0) cvs(3) = Array(15.0, 10.0, 0.0) cvs(4) = Array(20.0, 0.0, 0.0) cvs(5) = Array(25.0, 10.0, 0.0) Dim knots() : ReDim knots(knot_count - 1) knots(0) = 0.0 knots(1) = 0.0 knots(2) = 0.0 knots(3) = 1.0 knots(4) = 2.0 knots(5) = 3.0 knots(6) = 3.0 knots(7) = 3.0 Call Rhino.AddNurbsCurve(cvs, knots, degree) End Sub ``` -------------------------------------------------------------------------------- # Add Objects to a Group Source: https://developer.rhino3d.com/en/samples/cpp/add-objects-to-a-group/ Demonstrates how to add selected objects to an object group. ```cpp CRhinoCommand::result CCommandTest::RunCommand(const CRhinoCommandContext& context) { CRhinoGetObject go; go.SetCommandPrompt( L"Select objects to group" ); go.EnableGroupSelect(); go.GetObjects(1,0); if( go.CommandResult() != CRhinoCommand::success ) return go.CommandResult(); int i = 0, count = go.ObjectCount(); ON_SimpleArray members( count ); for( i = 0; i < count; i++ ) members.Append( go.Object(i).Object() ); int index = context.m_doc.m_group_table.AddGroup( ON_Group(), members ); context.m_doc.Redraw(); return (index >= 0) ? CRhinoCommand::success : CRhinoCommand::failure; } ``` -------------------------------------------------------------------------------- # Add Objects to Group Source: https://developer.rhino3d.com/en/samples/rhinocommon/add-objects-to-group/ Demonstrates how to group objects from a user-specified selection set of objects. ```cs partial class Examples { public static Rhino.Commands.Result AddObjectsToGroup(Rhino.RhinoDoc doc) { Rhino.Input.Custom.GetObject go = new Rhino.Input.Custom.GetObject(); go.SetCommandPrompt("Select objects to group"); go.GroupSelect = true; go.GetMultiple(1, 0); if (go.CommandResult() != Rhino.Commands.Result.Success) return go.CommandResult(); List ids = new List(); for (int i = 0; i < go.ObjectCount; i++) { ids.Add(go.Object(i).ObjectId); } int index = doc.Groups.Add(ids); doc.Views.Redraw(); if (index >= 0) return Rhino.Commands.Result.Success; return Rhino.Commands.Result.Failure; } } ``` ```python import Rhino import scriptcontext def AddObjectsToGroup(): go = Rhino.Input.Custom.GetObject() go.SetCommandPrompt("Select objects to group") go.GroupSelect = True go.GetMultiple(1, 0) if go.CommandResult()!=Rhino.Commands.Result.Success: return go.CommandResult() ids = [go.Object(i).ObjectId for i in range(go.ObjectCount)] index = scriptcontext.doc.Groups.Add(ids) scriptcontext.doc.Views.Redraw() if index>=0: return Rhino.Commands.Result.Success return Rhino.Commands.Result.Failure if __name__ == "__main__": AddObjectsToGroup() ``` -------------------------------------------------------------------------------- # Add Points at Curve Endpoints Source: https://developer.rhino3d.com/en/samples/rhinoscript/add-points-at-curve-endpoints/ Demonstrates how to add point at the starting and ending locations of curves. ```vbnet Option Explicit Sub AddCurveEndPoints() Const rhCurve = 4 ' Get all the curve objects in the document Dim arrCurves arrCurves = Rhino.ObjectsByType(rhCurve) If IsNull(arrCurves) Then Exit Sub ' For better performance, turn off screen redrawing Call Rhino.EnableRedraw(False) ' Process each curve Dim strCurve For Each strCurve In arrCurves ' Add a point at the start of the curve Call Rhino.AddPoint(Rhino.CurveStartPoint(strCurve)) ' If not closed, add a point at the end of the curve If Not Rhino.IsCurveClosed(strCurve) Then Call Rhino.AddPoint(Rhino.CurveEndPoint(strCurve)) End If Next ' Turn screen redrawing back on Call Rhino.EnableRedraw(True) End Sub ``` -------------------------------------------------------------------------------- # Add Radial Dimension Source: https://developer.rhino3d.com/en/samples/rhinocommon/add-radial-dimension/ Demonstrates how to add radial dimensions to a selected curve. ```cs partial class Examples { public static Rhino.Commands.Result AddRadialDimension(Rhino.RhinoDoc doc) { ObjRef obj_ref; var rc = RhinoGet.GetOneObject("Select curve for radius dimension", true, ObjectType.Curve, out obj_ref); if (rc != Result.Success) return rc; double curve_parameter; var curve = obj_ref.CurveParameter(out curve_parameter); if (curve == null) return Result.Failure; if (curve.IsLinear() || curve.IsPolyline()) { RhinoApp.WriteLine("Curve must be non-linear."); return Result.Nothing; } // in this example just deal with planar curves if (!curve.IsPlanar()) { RhinoApp.WriteLine("Curve must be planar."); return Result.Nothing; } var point_on_curve = curve.PointAt(curve_parameter); var curvature_vector = curve.CurvatureAt(curve_parameter); var len = curvature_vector.Length; if (len < RhinoMath.SqrtEpsilon) { RhinoApp.WriteLine("Curve is almost linear and therefore has no curvature."); return Result.Nothing; } var center = point_on_curve + (curvature_vector/(len*len)); Plane plane; curve.TryGetPlane(out plane); var radial_dimension = new RadialDimension(center, point_on_curve, plane.XAxis, plane.Normal, 5.0); doc.Objects.AddRadialDimension(radial_dimension); doc.Views.Redraw(); return Result.Success; } } ``` ```python from Rhino import RhinoMath from Rhino.DocObjects import ObjectType from Rhino.Commands import Result from Rhino.Geometry import RadialDimension, AnnotationType from Rhino.Input import RhinoGet from scriptcontext import doc def RunCommand(): rc, obj_ref = RhinoGet.GetOneObject("Select curve for radius dimension", True, ObjectType.Curve) if rc != Result.Success: return rc curve, curve_parameter = obj_ref.CurveParameter() if curve == None: return Result.Failure if curve.IsLinear() or curve.IsPolyline(): print("Curve must be non-linear.") return Result.Nothing # in this example just deal with planar curves if not curve.IsPlanar(): print("Curve must be planar.") return Result.Nothing point_on_curve = curve.PointAt(curve_parameter) curvature_vector = curve.CurvatureAt(curve_parameter) len = curvature_vector.Length if len < RhinoMath.SqrtEpsilon: print("Curve is almost linear and therefore has no curvature.") return Result.Nothing center = point_on_curve + (curvature_vector/(len*len)) _, plane = curve.TryGetPlane() indicator_point = point_on_curve + (point_on_curve - center) * 0.5 radial_dimension = RadialDimension(AnnotationType.Radius, plane, center, point_on_curve, indicator_point) doc.Objects.AddRadialDimension(radial_dimension) doc.Views.Redraw() return Result.Success if __name__=="__main__": RunCommand() ``` -------------------------------------------------------------------------------- # Add Sphere Source: https://developer.rhino3d.com/en/samples/rhinocommon/add-sphere/ Demonstrates how to create a sphere from a center point and radius. ```cs partial class Examples { public static Rhino.Commands.Result AddSphere(Rhino.RhinoDoc doc) { Rhino.Geometry.Point3d center = new Rhino.Geometry.Point3d(0, 0, 0); const double radius = 5.0; Rhino.Geometry.Sphere sphere = new Rhino.Geometry.Sphere(center, radius); if( doc.Objects.AddSphere(sphere) != Guid.Empty ) { doc.Views.Redraw(); return Rhino.Commands.Result.Success; } return Rhino.Commands.Result.Failure; } } ``` ```python import Rhino import scriptcontext import System.Guid def AddSphere(): center = Rhino.Geometry.Point3d(0, 0, 0) radius = 5.0 sphere = Rhino.Geometry.Sphere(center, radius) if scriptcontext.doc.Objects.AddSphere(sphere)!=System.Guid.Empty: scriptcontext.doc.Views.Redraw() return Rhino.Commands.Result.Success return Rhino.Commands.Result.Failure if __name__ == "__main__": AddSphere() ``` -------------------------------------------------------------------------------- # Add Spherical Surface Source: https://developer.rhino3d.com/en/samples/cpp/add-spherical-surface/ Demonstrates how to create a sphere using ON_RevSurface and add it to Rhino. ```cpp CRhinoCommand::result CCommandTest::RunCommand( const CRhinoCommandContext& context ) { CRhinoCommand::result rc = CRhinoCommand::cancel; ON_3dPoint center( 0.0, 0.0, 0.0 ); double radius = 5.0; ON_Sphere sphere( center, radius ); ON_RevSurface* sphere_srf = sphere.RevSurfaceForm(); if( !sphere_srf ) return rc; CRhinoSurfaceObject* sphere_obj = new CRhinoSurfaceObject(); sphere_obj->SetSurface( sphere_srf ); if( context.m_doc.AddObject(sphere_obj) ) { context.m_doc.Redraw(); rc = CRhinoCommand::success; } else { delete sphere_obj; sphere_obj = NULL; } return rc; } ``` -------------------------------------------------------------------------------- # Add Text Source: https://developer.rhino3d.com/en/samples/cpp/add-text/ Demonstrates how to use ON_TextEntity2 to add text to Rhino. ```cpp bool AddAnnotationText( CRhinoDoc& doc, // reference to active document const ON_3dPoint& pt, // location of anotation text const wchar_t* text, // the text double height, // height of text item const wchar_t* font, // font or face name int style // style 0=normal, 1=bold, ) // 2=italic, 3=bold and italic { bool rc = false; ON_wString wText( text ); if( wText.IsEmpty() ) return rc; if( height <= 0 ) height = 1.0; ON_wString wFont( font ); if( wFont.IsEmpty() ) wFont = L"Arial"; if( style < 0 | style > 3 ) style = 0; ON_Plane plane = RhinoActiveCPlane(); plane.SetOrigin( pt ); ON_TextEntity2* text_entity = new ON_TextEntity2; CRhinoAnnotationText* text_object = new CRhinoAnnotationText; text_object->SetAnnotation( text_entity ); text_object->SetTextHeight( height ); text_object->SetString( wText ); text_object->SetPlane( plane ); int idx = doc.m_font_table.FindOrCreateFont( wFont, style & 1, style & 2 ); text_object->SetFontIndex( idx ); if( doc.AddObject(text_object) ) { rc = true; doc.Redraw(); } else { delete text_object; text_object = 0; } return rc; } ``` -------------------------------------------------------------------------------- # Add Texture Source: https://developer.rhino3d.com/en/samples/rhinocommon/add-texture/ Demonstrates how to add a texture to an object from a user-specified bitmap file. ```cs partial class Examples { public static Rhino.Commands.Result AddTexture(Rhino.RhinoDoc doc) { // Select object to add texture to. const ObjectType filter = Rhino.DocObjects.ObjectType.Surface | Rhino.DocObjects.ObjectType.PolysrfFilter | Rhino.DocObjects.ObjectType.Mesh; var rc = Rhino.Input.RhinoGet.GetOneObject("Select object to add texture", false, filter, out ObjRef objref); if (rc != Rhino.Commands.Result.Success) return rc; var rhino_object = objref.Object(); if (rhino_object == null) return Rhino.Commands.Result.Failure; // Choose a texture file. var fd = new Rhino.UI.OpenFileDialog { Filter = "Image Files (*.bmp;*.png;*.jpg)|*.bmp;*.png;*.jpg" }; if (!fd.ShowOpenDialog()) return Rhino.Commands.Result.Cancel; // Verify that the file exists. string bitmap_filename = fd.FileName; if (string.IsNullOrEmpty(bitmap_filename) || !System.IO.File.Exists(bitmap_filename)) return Rhino.Commands.Result.Nothing; // Make sure the object has a render material assigned. if (rhino_object.RenderMaterial == null) { // Create a Rhino material. var rhino_material = new Rhino.DocObjects.Material() { DiffuseColor = System.Drawing.Color.White }; // Create a basic Render material from the Rhino material. var render_material = Rhino.Render.RenderMaterial.CreateBasicMaterial(rhino_material, doc); // Create a Rhino texture for the filename. var tex = new Rhino.DocObjects.Texture { FileName = bitmap_filename }; // Create a bitmap texture from the Rhino texture. var sim = new Rhino.Render.SimulatedTexture(doc, tex); var render_texture = Rhino.Render.RenderTexture.NewBitmapTexture(sim, doc); // Set the texture as a child of the material in the bump slot. var child_slot_name = "bump-texture"; render_material.SetChild(render_texture, child_slot_name); render_material.SetChildSlotOn(child_slot_name, true, Rhino.Render.RenderContent.ChangeContexts.Program); render_material.SetChildSlotAmount(child_slot_name, 100.0, Rhino.Render.RenderContent.ChangeContexts.Program); // Add the basic Render material to the document. doc.RenderMaterials.Add(render_material); // Assign the render material to the object. rhino_object.RenderMaterial = render_material; // Don't forget to update the object, if necessary. rhino_object.CommitChanges(); } doc.Views.Redraw(); return Rhino.Commands.Result.Success; } } ``` ```python import Rhino import scriptcontext import System.Guid import System.Windows.Forms.DialogResult import System.IO.File def AddTexture(): # Select object to add texture to. filter = Rhino.DocObjects.ObjectType.Surface | Rhino.DocObjects.ObjectType.PolysrfFilter | Rhino.DocObjects.ObjectType.Mesh rc, objref = Rhino.Input.RhinoGet.GetOneObject("Select object to add texture", False, filter) if rc != Rhino.Commands.Result.Success: return rc rhino_object = objref.Object() if rhino_object is None: return Rhino.Commands.Result.Failure # Choose a texture file. fd = Rhino.UI.OpenFileDialog() fd.Filter = "Image Files (*.bmp;*.png;*.jpg)|*.bmp;*.png;*.jpg" if not fd.ShowDialog(): return Rhino.Commands.Result.Cancel # Verify that the file exists. bitmap_filename = fd.FileName if not System.IO.File.Exists(bitmap_filename): return Rhino.Commands.Result.Nothing # Make sure the object has a render material assigned. if rhino_object.RenderMaterial is None: # Create a Rhino material. rhino_material = Rhino.DocObjects.Material() rhino_material.DiffuseColor = System.Drawing.Color.White # Create a basic Render material from the Rhino material. render_material = Rhino.Render.RenderMaterial.CreateBasicMaterial(rhino_material, scriptcontext.doc); # Create a Rhino texture for the filename. tex = Rhino.DocObjects.Texture() tex.FileName = bitmap_filename # Create a bitmap texture from the Rhino texture. sim = Rhino.Render.SimulatedTexture(scriptcontext.doc, tex); render_texture = Rhino.Render.RenderTexture.NewBitmapTexture(sim, scriptcontext.doc); # Set the texture as a child of the material in the bump slot. child_slot_name = "bump-texture"; render_material.SetChild(render_texture, child_slot_name); render_material.SetChildSlotOn(child_slot_name, True, Rhino.Render.RenderContent.ChangeContexts.Program); render_material.SetChildSlotAmount(child_slot_name, 100.0, Rhino.Render.RenderContent.ChangeContexts.Program); # Add the basic Render material to the document. scriptcontext.doc.RenderMaterials.Add(render_material); # Assign the render material to the object. rhino_object.RenderMaterial = render_material; # Don't forget to update the object, if necessary. rhino_object.CommitChanges(); scriptcontext.doc.Views.Redraw(); return Rhino.Commands.Result.Success; if __name__=="__main__": AddTexture() ``` -------------------------------------------------------------------------------- # Add Torus Source: https://developer.rhino3d.com/en/samples/cpp/add-torus/ Demonstrates how to create a torus using ON_BrepTorus and add it to Rhino. ```cpp CRhinoCommand::result CCommandTest::RunCommand( const CRhinoCommandContext& context ) { double major_radius = 4.0; double minor_radius = 2.0; ON_Plane plane( ON_origin, ON_zaxis ); ON_Circle circle( plane, major_radius ); ON_Torus torus( circle, minor_radius ); ON_Brep* brep = ON_BrepTorus( torus ); if( brep ) { CRhinoBrepObject* torus_object = new CRhinoBrepObject(); torus_object->SetBrep( brep ); if( context.m_doc.AddObject(torus_object) ) context.m_doc.Redraw(); else delete torus_object; } return CRhinoCommand::success; } ``` -------------------------------------------------------------------------------- # Add Torus Source: https://developer.rhino3d.com/en/samples/rhinocommon/add-torus/ Demonstrates how to construct a torus from a set of radii and a plane. ```cs partial class Examples { public static Rhino.Commands.Result AddTorus(Rhino.RhinoDoc doc) { const double major_radius = 4.0; const double minor_radius = 2.0; Rhino.Geometry.Plane plane = Rhino.Geometry.Plane.WorldXY; Rhino.Geometry.Torus torus = new Rhino.Geometry.Torus(plane, major_radius, minor_radius); Rhino.Geometry.RevSurface revsrf = torus.ToRevSurface(); if (doc.Objects.AddSurface(revsrf) != Guid.Empty) { doc.Views.Redraw(); return Rhino.Commands.Result.Success; } return Rhino.Commands.Result.Failure; } } ``` ```python import Rhino import scriptcontext import System.Guid def AddTorus(): major_radius = 4.0 minor_radius = 2.0 plane = Rhino.Geometry.Plane.WorldXY torus = Rhino.Geometry.Torus(plane, major_radius, minor_radius) revsrf = torus.ToRevSurface() if scriptcontext.doc.Objects.AddSurface(revsrf)!=System.Guid.Empty: scriptcontext.doc.Views.Redraw() return Rhino.Commands.Result.Success return Rhino.Commands.Result.Failure if __name__=="__main__": AddTorus() ``` -------------------------------------------------------------------------------- # Add Truncated Cone Source: https://developer.rhino3d.com/en/samples/cpp/add-truncated-cone/ Demonstrates how to create a truncated cone ON_BrepRevSurface and add it to Rhino. ```cpp CRhinoCommand::result CCommandTest::RunCommand(const CRhinoCommandContext& context) { ON_3dPoint bottom_pt( 0.0, 0.0, 0.0 ); double bottom_radius = 2; ON_Circle bottom_circle( bottom_pt, bottom_radius ); ON_3dPoint top_pt( 0.0, 0.0, 10.0 ); double top_radius = 6; ON_Circle top_circle( top_pt, top_radius ); ON_RevSurface* revsrf = new ON_RevSurface; ON_LineCurve* pShapeCurve = new ON_LineCurve; revsrf->m_curve = pShapeCurve; pShapeCurve->m_dim = 3; pShapeCurve->m_line.from = bottom_circle.PointAt(0); pShapeCurve->m_line.to = top_circle.PointAt(0); pShapeCurve->m_t.Set(0, pShapeCurve->m_line.from.DistanceTo(pShapeCurve->m_line.to)); revsrf->m_axis.from = bottom_circle.Center(); revsrf->m_axis.to = top_circle.Center(); revsrf->m_angle[0] = revsrf->m_t[0] = 0.0; revsrf->m_angle[1] = revsrf->m_t[1] = 2.0*ON_PI; ON_Brep* tcone_brep = ON_BrepRevSurface(revsrf, TRUE, TRUE ); if( tcone_brep ) { CRhinoBrepObject* tcone_object = new CRhinoBrepObject(); tcone_object->SetBrep( tcone_brep ); if( context.m_doc.AddObject(tcone_object) ) context.m_doc.Redraw(); else delete tcone_object; } return CRhinoCommand::success; } ``` -------------------------------------------------------------------------------- # Add Truncated Cone Source: https://developer.rhino3d.com/en/samples/rhinocommon/add-truncated-cone/ Demonstrates how to construct a truncated cone (TCone) from two circles. ```cs partial class Examples { public static Rhino.Commands.Result AddTruncatedCone(Rhino.RhinoDoc doc) { Point3d bottom_pt = new Point3d(0,0,0); const double bottom_radius = 2; Circle bottom_circle = new Circle(bottom_pt, bottom_radius); Point3d top_pt = new Point3d(0,0,10); const double top_radius = 6; Circle top_circle = new Circle(top_pt, top_radius); LineCurve shapeCurve = new LineCurve(bottom_circle.PointAt(0), top_circle.PointAt(0)); Line axis = new Line(bottom_circle.Center, top_circle.Center); RevSurface revsrf = RevSurface.Create(shapeCurve, axis); Brep tcone_brep = Brep.CreateFromRevSurface(revsrf, true, true); if( doc.Objects.AddBrep(tcone_brep) != Guid.Empty ) { doc.Views.Redraw(); return Rhino.Commands.Result.Success; } return Rhino.Commands.Result.Failure; } } ``` ```python import Rhino import scriptcontext import System.Guid def AddTruncatedCone(): bottom_pt = Rhino.Geometry.Point3d(0,0,0) bottom_radius = 2 bottom_circle = Rhino.Geometry.Circle(bottom_pt, bottom_radius) top_pt = Rhino.Geometry.Point3d(0,0,10) top_radius = 6 top_circle = Rhino.Geometry.Circle(top_pt, top_radius) shapeCurve = Rhino.Geometry.LineCurve(bottom_circle.PointAt(0), top_circle.PointAt(0)) axis = Rhino.Geometry.Line(bottom_circle.Center, top_circle.Center) revsrf = Rhino.Geometry.RevSurface.Create(shapeCurve, axis) tcone_brep = Rhino.Geometry.Brep.CreateFromRevSurface(revsrf, True, True) if scriptcontext.doc.Objects.AddBrep(tcone_brep)!=System.Guid.Empty: scriptcontext.doc.Views.Redraw() return Rhino.Commands.Result.Success return Rhino.Commands.Result.Failure if __name__=="__main__": AddTruncatedCone() ``` -------------------------------------------------------------------------------- # Add Twisted Cube Source: https://developer.rhino3d.com/en/samples/rhinocommon/add-twisted-cube/ Demonstrates how to construct a twisted cube object from an array of points and a list of curves. ```cs partial class Examples { // Symbolic vertex index constants to make code more readable const int A = 0, B = 1, C = 2, D = 3, E = 4, F = 5, G = 6, H = 7; // Symbolic edge index constants to make code more readable const int AB = 0, BC = 1, CD = 2, AD = 3, EF = 4, FG = 5, GH = 6, EH = 7, AE = 8, BF = 9, CG = 10, DH = 11; // Symbolic face index constants to make code more readable const int ABCD = 0, BCGF = 1, CDHG = 2, ADHE = 3, ABFE = 4, EFGH = 5; public static Rhino.Commands.Result AddTwistedCube(Rhino.RhinoDoc doc) { Point3d[] points = new Point3d[8]; points[0] = new Point3d(0.0, 0.0, 0.0); // point A = geometry for vertex 0 points[1] = new Point3d(10.0, 0.0, 0.0); // point B = geometry for vertex 1 points[2] = new Point3d(10.0, 8.0, -1.0); // point C = geometry for vertex 2 points[3] = new Point3d(0.0, 6.0, 0.0); // point D = geometry for vertex 3 points[4] = new Point3d(1.0, 2.0, 11.0); // point E = geometry for vertex 4 points[5] = new Point3d(10.0, 0.0, 12.0); // point F = geometry for vertex 5 points[6] = new Point3d(10.0, 7.0, 13.0); // point G = geometry for vertex 6 points[7] = new Point3d(0.0, 6.0, 12.0); // point H = geometry for vertex 7 Brep brep = new Brep(); // Create eight vertices located at the eight points for (int vi = 0; vi < 8; vi++) brep.Vertices.Add(points[vi], 0.0); // Create 3d curve geometry - the orientations are arbitrarily chosen // so that the end vertices are in alphabetical order. brep.Curves3D.Add(TwistedCubeEdgeCurve(points[A], points[B])); // line AB brep.Curves3D.Add(TwistedCubeEdgeCurve(points[B], points[C])); // line BC brep.Curves3D.Add(TwistedCubeEdgeCurve(points[C], points[D])); // line CD brep.Curves3D.Add(TwistedCubeEdgeCurve(points[A], points[D])); // line AD brep.Curves3D.Add(TwistedCubeEdgeCurve(points[E], points[F])); // line EF brep.Curves3D.Add(TwistedCubeEdgeCurve(points[F], points[G])); // line FG brep.Curves3D.Add(TwistedCubeEdgeCurve(points[G], points[H])); // line GH brep.Curves3D.Add(TwistedCubeEdgeCurve(points[E], points[H])); // line EH brep.Curves3D.Add(TwistedCubeEdgeCurve(points[A], points[E])); // line AE brep.Curves3D.Add(TwistedCubeEdgeCurve(points[B], points[F])); // line BF brep.Curves3D.Add(TwistedCubeEdgeCurve(points[C], points[G])); // line CG brep.Curves3D.Add(TwistedCubeEdgeCurve(points[D], points[H])); // line DH // Create the 12 edges that connect the corners of the cube. MakeTwistedCubeEdges(ref brep); // Create 3d surface geometry - the orientations are arbitrarily chosen so // that some normals point into the cube and others point out of the cube. brep.AddSurface(TwistedCubeSideSurface(points[A], points[B], points[C], points[D])); // ABCD brep.AddSurface(TwistedCubeSideSurface(points[B], points[C], points[G], points[F])); // BCGF brep.AddSurface(TwistedCubeSideSurface(points[C], points[D], points[H], points[G])); // CDHG brep.AddSurface(TwistedCubeSideSurface(points[A], points[D], points[H], points[E])); // ADHE brep.AddSurface(TwistedCubeSideSurface(points[A], points[B], points[F], points[E])); // ABFE brep.AddSurface(TwistedCubeSideSurface(points[E], points[F], points[G], points[H])); // EFGH // Create the CRhinoBrepFaces MakeTwistedCubeFaces(ref brep); if (brep.IsValid) { doc.Objects.AddBrep(brep); doc.Views.Redraw(); return Rhino.Commands.Result.Success; } return Rhino.Commands.Result.Failure; } static Curve TwistedCubeEdgeCurve(Point3d from, Point3d to) { // Create a 3d line segment to be used as a 3d curve in a Brep return new LineCurve(from, to) { Domain = new Interval(0, 1) }; } static void MakeTwistedCubeEdges(ref Brep brep) { // In this simple example, the edge indices exactly match the 3d curve // indices. In general,the correspondence between edge and curve indices // can be arbitrary. It is permitted for multiple edges to use different // portions of the same 3d curve. The orientation of the edge always // agrees with the natural parametric orientation of the curve. brep.Edges.Add(A, B, AB, 0); // Edge that runs from A to B brep.Edges.Add(B, C, BC, 0); // Edge that runs from B to C brep.Edges.Add(C, D, CD, 0); // Edge that runs from C to D brep.Edges.Add(A, D, AD, 0); // Edge that runs from A to D brep.Edges.Add(E, F, EF, 0); // Edge that runs from E to F brep.Edges.Add(F, G, FG, 0); // Edge that runs from F to G brep.Edges.Add(G, H, GH, 0); // Edge that runs from G to H brep.Edges.Add(E, H, EH, 0); // Edge that runs from E to H brep.Edges.Add(A, E, AE, 0); // Edge that runs from A to E brep.Edges.Add(B, F, BF, 0); // Edge that runs from B to F brep.Edges.Add(C, G, CG, 0); // Edge that runs from C to G brep.Edges.Add(D, H, DH, 0); // Edge that runs from D to H } static Surface TwistedCubeSideSurface(Point3d southwest, Point3d southeast, Point3d northeast, Point3d northwest) { var ns = NurbsSurface.Create(3, false, 2, 2, 2, 2); // Corner CVs in counter clockwise order starting in the south west ns.Points.SetControlPoint(0, 0, southwest); ns.Points.SetControlPoint(1, 0, southeast); ns.Points.SetControlPoint(1, 1, northeast); ns.Points.SetControlPoint(0, 1, northwest); // "u" knots ns.KnotsU[0] = 0.0; ns.KnotsU[1] = 1.0; // "v" knots ns.KnotsV[0] = 0; ns.KnotsV[1] = 1; return ns; } static void MakeTwistedCubeFaces(ref Brep brep) { MakeTwistedCubeFace(ref brep, ABCD, // Index of surface ABCD +1, // Indices of vertices listed in SW,SE,NW,NE order AB, +1, // South side edge and its orientation with respect to // to the trimming curve. (AB) BC, +1, // South side edge and its orientation with respect to // to the trimming curve. (BC) CD, +1, // South side edge and its orientation with respect to // to the trimming curve (CD) AD, -1 // South side edge and its orientation with respect to // to the trimming curve (AD) ); MakeTwistedCubeFace(ref brep, BCGF, // Index of surface BCGF -1, // Indices of vertices listed in SW,SE,NW,NE order BC, +1, // South side edge and its orientation with respect to // to the trimming curve. (BC) CG, +1, // South side edge and its orientation with respect to // to the trimming curve. (CG) FG, -1, // South side edge and its orientation with respect to // to the trimming curve (FG) BF, -1 // South side edge and its orientation with respect to // to the trimming curve (BF) ); MakeTwistedCubeFace(ref brep, CDHG, // Index of surface CDHG -1, // Indices of vertices listed in SW,SE,NW,NE order CD, +1, // South side edge and its orientation with respect to // to the trimming curve. (CD) DH, +1, // South side edge and its orientation with respect to // to the trimming curve. (DH) GH, -1, // South side edge and its orientation with respect to // to the trimming curve (GH) CG, -1 // South side edge and its orientation with respect to // to the trimming curve (CG) ); MakeTwistedCubeFace(ref brep, ADHE, // Index of surface ADHE +1, // Indices of vertices listed in SW,SE,NW,NE order AD, +1, // South side edge and its orientation with respect to // to the trimming curve. (AD) DH, +1, // South side edge and its orientation with respect to // to the trimming curve. (DH) EH, -1, // South side edge and its orientation with respect to // to the trimming curve (EH) AE, -1 // South side edge and its orientation with respect to // to the trimming curve (AE) ); MakeTwistedCubeFace(ref brep, ABFE, // Index of surface ABFE -1, // Indices of vertices listed in SW,SE,NW,NE order AB, +1, // South side edge and its orientation with respect to // to the trimming curve. (AB) BF, +1, // South side edge and its orientation with respect to // to the trimming curve. (BF) EF, -1, // South side edge and its orientation with respect to // to the trimming curve (EF) AE, -1 // South side edge and its orientation with respect to // to the trimming curve (AE) ); MakeTwistedCubeFace(ref brep, EFGH, // Index of surface EFGH -1, // Indices of vertices listed in SW,SE,NW,NE order EF, +1, // South side edge and its orientation with respect to // to the trimming curve. (EF) FG, +1, // South side edge and its orientation with respect to // to the trimming curve. (FG) GH, +1, // South side edge and its orientation with respect to // to the trimming curve (GH) EH, -1 // South side edge and its orientation with respect to // to the trimming curve (EH) ); } static void MakeTwistedCubeFace( ref Brep brep, int surfaceIndex, int s_dir, // Indices of corner vertices listed in SW, SE, NW, NE order int southEdgeIndex, int eS_dir, // orientation of edge with respect to surface trim int eastEdgeIndex, int eE_dir, // orientation of edge with respect to surface trim int northEdgeIndex, int eN_dir, // orientation of edge with respect to surface trim int westEdgeIndex, int eW_dir // orientation of edge with respect to surface trim ) { var face = brep.Faces.Add(surfaceIndex); MakeTwistedCubeTrimmingLoop( ref brep, ref face, southEdgeIndex, eS_dir, eastEdgeIndex, eE_dir, northEdgeIndex, eN_dir, westEdgeIndex, eW_dir ); face.OrientationIsReversed = (s_dir == -1); } static void MakeTwistedCubeTrimmingLoop( ref Brep brep, ref BrepFace face, // Indices of corner vertices listed in SW, SE, NW, NE order int eSi, // index of edge on south side of surface int eS_dir, // orientation of edge with respect to surface trim int eEi, // index of edge on south side of surface int eE_dir, // orientation of edge with respect to surface trim int eNi, // index of edge on south side of surface int eN_dir, // orientation of edge with respect to surface trim int eWi, // index of edge on south side of surface int eW_dir // orientation of edge with respect to surface trim ) { Surface srf = brep.Surfaces[face.SurfaceIndex]; var loop = brep.Loops.Add(BrepLoopType.Outer, face); // Create trimming curves running counter clockwise around the surface's domain. // Start at the south side // side: 0=south, 1=east, 2=north, 3=west for (int side = 0; side < 4; side++) { Curve trimming_curve = TwistedCubeTrimmingCurve(srf, side); int curve_index = brep.Curves2D.Add(trimming_curve); int ei = 0; bool reverse = false; IsoStatus iso = IsoStatus.None; switch (side) { case 0: // south ei = eSi; reverse = (eS_dir == -1); iso = IsoStatus.South; break; case 1: // east ei = eEi; reverse = (eE_dir == -1); iso = IsoStatus.East; break; case 2: // north ei = eNi; reverse = (eN_dir == -1); iso = IsoStatus.North; break; case 3: // west ei = eWi; reverse = (eW_dir == -1); iso = IsoStatus.West; break; } BrepEdge edge = brep.Edges[ei]; BrepTrim trim = brep.Trims.Add(edge, reverse, loop, curve_index); trim.IsoStatus = iso; trim.TrimType = BrepTrimType.Mated; // This b-rep is closed, so all trims have mates. trim.SetTolerances(0, 0); // This simple example is exact - for models with // non-exact data, set tolerance as explained in // definition of BrepTrim. } } static Curve TwistedCubeTrimmingCurve(Surface s, int side // 0 = SW to SE // 1 = SE to NE // 2 = NE to NW // 3 = NW to SW ) { // A trimming curve is a 2d curve whose image lies in the surface's domain. // The "active" portion of the surface is to the left of the trimming curve. // An outer trimming loop consists of a simple closed curve running // counter-clockwise around the region it trims. var u_domain = s.Domain(0); var v_domain = s.Domain(1); double u0 = u_domain[0]; double u1 = u_domain[1]; double v0 = v_domain[0]; double v1 = v_domain[1]; Point2d from; Point2d to; switch (side) { case 0: // SW to SE from = new Point2d(u0, v0); to = new Point2d(u1, v0); break; case 1: // SE to NE from = new Point2d(u1, v0); to = new Point2d(u1, v1); break; case 2: // NE to NW from = new Point2d(u1, v1); to = new Point2d(u0, v1); break; case 3: // NW to SW from = new Point2d(u0, v1); to = new Point2d(u0, v0); break; default: return null; } return new LineCurve(from, to) { Domain = new Interval(0, 1) }; } } ``` ```python # No Python sample available ``` -------------------------------------------------------------------------------- # Adding a Custom Menu Source: https://developer.rhino3d.com/en/guides/cpp/adding-a-custom-menu/ This short guide demonstrates how to add a custom menu to Rhino's menu using C/C++. ## Problem Imagine you would like to add a submenu to Rhino's File menu. You might start fiddling around with the `Insert­Plug­In­Menu­To­Rhino­Menu()` and ­`Insert­Plug­In­Item­To­Rhino­Menu()` functions but not seem to be getting anywhere. `Insert­Plug­In­Menu­To­Rhino­Menu()` adds a menu into the Rhino's main menu bar. `Insert­Plug­In­Item­To­RhinoMenu()` adds a menu item anywhere in the Rhino menu. To solve this problem, you want a little of both... ## Solution To insert a menu item, or a submenu, into Rhino's menu, do the following: 1. Use `CRhinoApp::FindMenuItem` to search through Rhino's menu structure for an existing menu item that's where you want to insert your menu item. 1. Use `CRhinoPlugIn::InsertPlugInItemToRhinoMenu` to insert your menu into Rhino's menu. ## Sample The following example command demonstrates how to add and remove a custom menu from Rhino's menu: ```cpp //////////////////////////////////////////////////////////////// // cmdMyMenu.cpp #include "StdAfx.h" #include "MyTestPlugIn.h" #include "Resource.h" //////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////// // // BEGIN MyMenu command // class CCommandMyMenu : public CRhinoCommand { public: CCommandMyMenu() {} ~CCommandMyMenu() {} UUID CommandUUID() { static const GUID MyMenuCommand_UUID = { }; return MyMenuCommand_UUID; } const wchar_t* EnglishCommandName() { return L"MyMenu"; } const wchar_t* LocalCommandName() { return L"MyMenu"; } CRhinoCommand::result RunCommand( const CRhinoCommandContext& ); BOOL LoadMyMenu(); BOOL UnloadMyMenu(); private: CMenu m_menu; }; // The one and only CCommandMyMenu object static class CCommandMyMenu theMyMenuCommand; CRhinoCommand::result CCommandMyMenu::RunCommand( const CRhinoCommandContext& context ) { bool bVisible = ( m_menu.GetSafeHmenu() ) ? true : false; ON_wString prompt; prompt.Format( L"%s is %s. New value", EnglishCommandName(), bVisible ? L"visible" : L"hidden" ); CRhinoGetOption go; go.SetCommandPrompt( prompt ); int s_opt = go.AddCommandOption( RHCMDOPTNAME(L"Show") ); int h_opt = go.AddCommandOption( RHCMDOPTNAME(L"Hide") ); int t_opt = go.AddCommandOption( RHCMDOPTNAME(L"Toggle") ); go.GetOption(); if( go.CommandResult() != success ) return go.CommandResult(); const CRhinoCommandOption* opt = go.Option(); if( 0 == opt ) return failure; if( opt->m_option_index == s_opt ) { if( false == bVisible ) LoadMyMenu(); } else if( opt->m_option_index == h_opt ) { if( true == bVisible ) UnloadMyMenu(); } else { if( true == bVisible ) UnloadMyMenu(); else LoadMyMenu(); } return success; } BOOL CCommandMyMenu::LoadMyMenu() { // Switch the module state so resources are read // from our plugin (DLL), not Rhino. AFX_MANAGE_STATE( AfxGetStaticModuleState() ); // Try to load our menu resource from our plugin. // Note, m_my_menu is a CMenu member variable. if( 0 == m_menu.GetSafeHmenu() ) { if( !m_menu.LoadMenu(IDR_MY_MENU) ) return FALSE; } // Find a location in Rhino's menu to insert our // menu item. For this example, we will insert our // menu on the "Tools" menu just below the "Commands" // item. HMENU hParent = 0; int index = 0; //if( !RhinoApp().FindRhinoMenuItem(L"&File::&Print...Ctrl+P", hParent, index) ) if( !RhinoApp().FindRhinoMenuItem(L"Too&ls::&Commands", hParent, index) ) { m_menu.DestroyMenu(); return FALSE; } // Create and initialize a MENUITEMINFO struct. MENUITEMINFO mi; memset( &mi, 0, sizeof(mi) ); mi.cbSize = sizeof(mi); // Fill in our menu info mi.fMask = MIIM_ID | MIIM_TYPE | MIIM_STATE | MIIM_SUBMENU; mi.wID = MF_POPUP; mi.fType = MFT_STRING; ON_wString wstr = L"MyMenu"; mi.dwTypeData = wstr.Array(); mi.fState = MFS_ENABLED; mi.hSubMenu = m_menu.GetSafeHmenu(); mi.hSubMenu = ::GetSubMenu( mi.hSubMenu, 0 ); mi.wID = IDR_MY_MENU; // Add our menu to Rhino's menu BOOL rc = MyTestPlugIn().InsertPlugInItemToRhinoMenu( hParent, index + 1, &mi ); if( !rc ) m_menu.DestroyMenu(); return rc; } BOOL CCommandMyMenu::UnloadMyMenu() { BOOL rc = FALSE; // Find our menu item in Rhino's menu. HMENU hParent = 0; int index = 0; if( RhinoApp().FindRhinoMenuItem(L"Too&ls::MyMenu", hParent, index) ) { // Remove our menu item. if( ::RemoveMenu(hParent, index, MF_BYPOSITION) ) { // Redraw Rhino's menu bar. DrawMenuBar( RhinoApp().MainWnd() ); m_menu.DestroyMenu(); rc = TRUE; } } return rc; } // // END MyMenu command // //////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////// ``` -------------------------------------------------------------------------------- # Adding a NURBS Curve from Control Points Source: https://developer.rhino3d.com/en/guides/cpp/adding-nurbs-curve-from-control-points/ This guide demonstrates two ways to create a clamped NURBS curve from a set of control points using C/C++. ## Overview Imagine you would like to create a NURBS curve from a set of control points, such that it looks like this: ![NURBS Curve Control Points](https://developer.rhino3d.com/images/adding-a-nurbs-curve-from-control-points-01.png) There are two methods to achieve this... ## Method 1 ```cpp CRhinoCommand::result CCommandTest::RunCommand( const CRhinoCommandContext& context ) { ON_3dPointArray points; points.Append( ON_3dPoint(0, 0, 0) ); points.Append( ON_3dPoint(0, 2, 0) ); points.Append( ON_3dPoint(2, 4, 0) ); points.Append( ON_3dPoint(4, 2, 0) ); points.Append( ON_3dPoint(4, 0, 0) ); CRhinoCommand::result res; ON_NurbsCurve nc; if ( nc.CreateClampedUniformNurbs( 3, 4, points.Count(), points) && nc.IsValid()) { context.m_doc.AddCurveObject( nc ); res = CRhinoCommand::success; } else res = CRhinoCommand::failure; context.m_doc.Redraw(); return res; } ``` ## Method 2 ```cpp CRhinoCommand::result CCommandTest::RunCommand( const CRhinoCommandContext& context ) { ON_3dPointArray points; points.Append( ON_3dPoint(0, 0, 0) ); points.Append( ON_3dPoint(0, 2, 0) ); points.Append( ON_3dPoint(2, 4, 0) ); points.Append( ON_3dPoint(4, 2, 0) ); points.Append( ON_3dPoint(4, 0, 0) ); int dimension = 3; bool bIsRat = false; int order = 4; int cv_count = points.Count(); ON_NurbsCurve nc(dimension, bIsRat, order, cv_count); if( !nc.IsValid() ) { return CRhinoCommand::failure; } //Set CV points nc.ReserveCVCapacity( cv_count ); for( int i = 0; i < cv_count; i++ ) { nc.SetCV(i, points[i] ); } //Set Knots nc.ReserveKnotCapacity( order+cv_count-2 ); ON_MakeClampedUniformKnotVector( order, cv_count, nc.m_knot ); CRhinoCommand::result res; if( nc.IsValid() ) { context.m_doc.AddCurveObject( nc ); res = CRhinoCommand::success; } else res = CRhinoCommand::failure; context.m_doc.Redraw(); return res; } ``` -------------------------------------------------------------------------------- # Adding Command Line Options Source: https://developer.rhino3d.com/en/guides/cpp/adding-command-line-options/ This guide discusses how to add a different type of command line options to a custom command. ## Overview The Rhino C/C++ SDK has a number of CRhinoGet derived classes that you can use to interactively get information from the user. Some of these classes include: - `CRhinoGetObject`: SDK user interface tool used to get objects. - `CRhinoGetPoint`: SDK user interface tool used to get points. - `CRhinoGetString`: SDK user interface tool used to get strings. - `CRhinoGetNumber`: SDK user interface tool used to get floating point values. - `CRhinoGetInteger`: SDK user interface tool used to get integer values. - `CRhinoGetAngle`: SDK user interface tool used to get angles. - `CRhinoGetDistance`: SDK user interface tool used to get distances. - `CRhinoGetOption`: SDK user interface tool used to get command line options. Each `CRhinoGet` derived classes can, in addition to its primary function, prompt the user for additional options. These options display on the command following the developer specified prompt, and appear as clickable hyperlinks. ## Sample The following example code demonstrates how to add command line options to some user interaction. In this example, we use the `CRhinoGetOption` class, which is only capable of displaying command line options. But, we could have used any of the above `CRhinoGet` derived classes. ```cpp CRhinoCommand::result CCommandTest::RunCommand(const CRhinoCommandContext& context) { int nVal = 2; double dVal = 30.0; bool bVal = false; int list_index = 3; CRhinoCommandOptionValue list_items[5]; list_items[0] = RHCMDOPTVALUE(L"Item0"); list_items[1] = RHCMDOPTVALUE(L"Item1"); list_items[2] = RHCMDOPTVALUE(L"Item2"); list_items[3] = RHCMDOPTVALUE(L"Item3"); list_items[4] = RHCMDOPTVALUE(L"Item4"); CRhinoGetOption go; go.SetCommandPrompt(L"Command options"); go.AcceptNothing(); for (;;) { go.ClearCommandOptions(); int nval_option_index = go.AddCommandOptionInteger( RHCMDOPTNAME(L"Integer"), &nVal, L"integer value", 1, 99 ); int dval_option_index = go.AddCommandOptionNumber( RHCMDOPTNAME(L"Double"), &dVal, L"double value", FALSE, 0.1, 99.9 ); int bval_option_index = go.AddCommandOptionToggle( RHCMDOPTNAME(L"Boolean"), RHCMDOPTVALUE(L"False"), RHCMDOPTVALUE(L"True"), bVal, &bVal ); int list_option_index = go.AddCommandOptionList( RHCMDOPTNAME(L"List"), 5, list_items, list_index ); int test_option_index = go.AddCommandOption( RHCMDOPTNAME(L"Test") ); CRhinoGet::result res = go.GetOption(); if (res == CRhinoGet::nothing) break; if (res == CRhinoGet::cancel) return CRhinoCommand::cancel; if (res != CRhinoGet::option) return CRhinoCommand::failure; const CRhinoCommandOption* option = go.Option(); if (nullptr == option) return CRhinoCommand::failure; int option_index = option->m_option_index; if (option_index == nval_option_index) continue; // nothing to do if (option_index == dval_option_index) continue; // nothing to do if (option_index == bval_option_index) continue; // nothing to do if (option_index == list_option_index) { list_index = option->m_list_option_current; continue; } if (option_index == test_option_index) { // TODO... } } // TODO... return CRhinoCommand::success; } ``` -------------------------------------------------------------------------------- # Adding Curvature Circles Source: https://developer.rhino3d.com/en/guides/rhinoscript/adding-curvature-circles/ This guide demonstrates how to add curvature circles using RhinoScript. ## Overview Rhino's Curvature command is very useful for analyzing the curvature at a point on a curve. This script shows how to add the circle to the document when the curve is picked (instead of just drawing it dynamically). There is no option on the Curvature command for leaving the circle that it draw dynamically. But, with the help of a script, you can write a subroutine that will. The following example demonstrates how to do just this. ## Sample ```vbnet Option Explicit Sub CurvatureCircle Dim crv, crv_pt, crv_t Dim arr, crv_pl, pl crv = Rhino.GetObject("Select curve for curvature measurement", 4, True) If IsNull(crv) Then Exit Sub Do crv_pt = Rhino.GetPointOnCurve(crv, "Select point on curve for curvature measurement") If IsNull(crv_pt) Then Exit Do crv_t = Rhino.CurveClosestPoint(crv, crv_pt) If IsNull(crv_t) Then Exit Do arr = Rhino.CurveCurvature(crv, crv_t) If IsNull(arr) Then Rhino.Print("Unable to compute curve curvature.") Exit Do End If crv_pl = Rhino.PlaneFromFrame(arr(0), arr(1), arr(4)) pl = Rhino.MovePlane(crv_pl, arr(2)) Rhino.AddCircle pl, arr(3) Rhino.AddPoint arr(0) Loop While Not IsNull(crv_pt) End Sub ``` -------------------------------------------------------------------------------- # Adding Curve Objects Source: https://developer.rhino3d.com/en/guides/cpp/adding-curve-objects/ This guide discusses how to add curve objects to Rhino using the Rhino C/C++ SDK. ## Overview Curve objects can be added to Rhino by using the following functions found on `CRhinoDoc`: 1. `CRhinoDoc::AddCurveObject` 1. `CRhinoDoc::AddObject` For `CRhinoDoc::AddCurveObject`, there are six overridden versions that will make curve objects from a variety of inputs, including: 1. `ON_Line`: line definition objects 1. `ON_Polyline`: polyline definition objects 1. `ON_Arc`: arc definition objects 1. `ON_Circle`: circle definition objects 1. `ON_BezierCurve`: bezier curve objects 1. `ON_Curve`: `ON_Curve`-derived curve objects ## Examples The following code samples will demonstrate three different ways of adding curves to Rhino. In these examples, we will create circle curves; but, there is no reason that we could create lines, polylines, arcs or any other type of curve. ### Example 1 In this example, we define a circle, using `ON_Circle`, and pass the definition off to `CRhinoDoc::AddCurveObject`. ```cpp ON_3dPoint center(0.0, 0.0, 0.0); double radius = 10.0; ON_Circle circle( center, radius ); CRhinoCurveObject* curve_object = context.m_doc.AddCurveObject( circle ); context.m_doc.Redraw(); ``` ### Example 2 In this example, we first define a circle. Then we create an `ON_ArcCurve` object from the circle definition. `ON_ArcCurve` is one of the many curve classes this is derived from `ON_Curve`. We then pass the `ON_ArcCurve` object off to `CRhinoDoc::AddCurveObject`. ```cpp ON_3dPoint center(0, 0, 0); double radius = 10.0; ON_Circle circle( center, radius ); ON_ArcCurve arc_curve( circle ); CRhinoCurveObject* curve_object = context.m_doc.AddCurveObject( arc_curve ); context.m_doc.Redraw(); ``` ### Example 3 In this example, we will add a circle curve the brute force way. We first define a circle. Then we allocate a new `ON_ArcCurve` and pass the circle definition to its constructor. We then allocate a new `CRhinoCurveObject` and assign our `ON_ArcCurve` object pointer to it. Finally, we pass the pointer to the `CRhinoCurveObject` to `CRhinoDoc::AddObject`. ```cpp ON_3dPoint center(0.0, 0.0, 0.0); double radius = 10.0; ON_Circle circle( center, radius ); ON_ArcCurve* arc_curve = new ON_ArcCurve( circle ); if( arc_curve ) { CRhinoCurveObject* curve_object = new CRhinoCurveObject(); if( curve_object ) { // Set the curve to the curve object. Note, // curve_object will delete arc_curve. curve_object->SetCurve( arc_curve ); if( context.m_doc.AddObject(curve_object) ) context.m_doc.Redraw(); else delete curve_object; } } ``` ## Discussion What is interesting to note is that the code found in [Example 3](#example-3) is essentially what Rhino does inside of `CRhinoDoc::AddCurveObject`. All of the `CRhinoDoc::AddCurveObject` overrides are simply provided to make it easier for SDK developers to add curves to Rhino. -------------------------------------------------------------------------------- # Adding Mesh Objects Source: https://developer.rhino3d.com/en/guides/cpp/adding-mesh-objects/ This guide demonstrates how to add a simple mesh object to Rhino in C/C++. ## Overview To create an `ON_Mesh`: 1. Create the `ON_Mesh` object. The constructor requires the number of faces and vertices, and whether or not you have vertex normals or texture coordinates. 1. Fill in the mesh vertex array, `ON_Mesh::m_V`. You can also use `ON_Mesh::SetVertex`. 1. Fill in the mesh faces array, `ON_Mesh::m_F`. You can also use `ON_Mesh::SetTriangle` and `ON_Mesh::SetQuad`. 1. If you have vertex normals, fill in the normals array, `ON_Mesh::m_N`. You can also use `ON_Mesh::SetVertexNormal`. 1. If you have texture coordinates, fill in the texture coordinate array, `ON_Mesh::m_T`. You can also use `ON_Mesh::SetTextureCoordinate`. 1. If you did not specify vertex normals, have Rhino compute them for you using `ON_Mesh::ComputeVertexNormals()`. 1. Clean up everything using `ON_Mesh::Compact`. You are now ready to add this mesh object to the document. The *opennurbs_mesh.h* header file is well documented. It's worth reading through at least once. ## Example ```cpp CRhinoCommand::result CCommandTest::RunCommand( const CRhinoCommandContext& context ) { // Example demonstrates how to create a mesh and add it to Rhino // Create a mesh to write. // The mesh is a pyramid with 4 triangular sides and a quadranglar // base. The mesh has 5 vertices and 5 faces. // The side faces share normals at their common vertices. The // quadrangular base has normals different from the side normal. // Coincident vertices that have distinct normals must be // duplicated in the vertex list. // // The apex will be at (1,1.5,4) with normal (0,0,1). // The base corners will be at (0,0,0), (0,2,0), (2,3,0), (0,3,0). bool bHasVertexNormals = true; // we will specify vertex normals bool bHasTexCoords = false; // we will not specify texture coordinates const int vertex_count = 5+4; // 4 duplicates for different base normals const int face_count = 5; // 4 triangle sides and a quad base ON_Mesh mesh( face_count, vertex_count, bHasVertexNormals, bHasTexCoords ); // The SetVertex(), SetNormal(), SetTCoord() and SetFace() functions // return true if successful and false if input is illegal. It is // a good idea to inspect this returned value. // vertex #0: apex location and normal mesh.SetVertex( 0, ON_3dPoint(1.0, 1.5, 5.0) ); mesh.SetVertexNormal( 0, ON_3dVector(0.0, 0.0, 1.0) ); // vertex #1: SW corner vertex for sides mesh.SetVertex( 1, ON_3dPoint(0.0, 0.0, 0.0) ); mesh.SetVertexNormal( 1, ON_3dVector(-1.0, -1.0, 0.0) ); // set normal will unitize if needed // vertex #2: SE corner vertex for sides mesh.SetVertex( 2, ON_3dPoint(2.0, 0.0, 0.0) ); mesh.SetVertexNormal( 2, ON_3dVector(+1.0, -1.0, 0.0) ); // vertex #3: NE corner vertex for sides mesh.SetVertex( 3, ON_3dPoint(2.0, 3.0, 0.0) ); mesh.SetVertexNormal( 3, ON_3dVector(+1.0, +1.0, 0.0) ); // vertex #4: NW corner vertex for sides mesh.SetVertex( 4, ON_3dPoint(0.0, 3.0, 0.0) ); mesh.SetVertexNormal( 4, ON_3dVector(-1.0, +1.0, 0.0) ); // vertex #5: SW corner vertex for base mesh.SetVertex( 5, ON_3dPoint(0.0, 0.0, 0.0) ); // == location of v1 mesh.SetVertexNormal( 5, ON_3dVector(0.0, 0.0, -1.0) ); // vertex #6: SE corner vertex for base mesh.SetVertex( 6, ON_3dPoint(2.0, 0.0, 0.0) ); // == location of v2 mesh.SetVertexNormal( 6, ON_3dVector(0.0, 0.0, -1.0) ); // vertex #7: SW corner vertex for base mesh.SetVertex( 7, ON_3dPoint(2.0, 3.0, 0.0) ); // == location of v3 mesh.SetVertexNormal( 7, ON_3dVector(0.0, 0.0, -1.0) ); // vertex #8: SW corner vertex for base mesh.SetVertex( 8, ON_3dPoint(0.0, 3.0, 0.0) ); // == location of v4 mesh.SetVertexNormal( 8, ON_3dVector(0.0, 0.0, -1.0) ); // Faces have vertices ordered counter-clockwise // South side triangle mesh.SetTriangle( 0, 1, 2, 0 ); // East side triangle mesh.SetTriangle( 1, 2, 3, 0 ); // North side triangle mesh.SetTriangle( 2, 3, 4, 0 ); // West side triangle mesh.SetTriangle( 3, 4, 1, 0 ); // last face is quadrangular base mesh.SetQuad( 4, 5, 8, 7, 6 ); ////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////// if( mesh.IsValid() ) { // Most applications expect vertex normals. // If they are not present, ComputeVertexNormals sets // them by averaging face normals. if ( !mesh.HasVertexNormals() ) mesh.ComputeVertexNormals(); context.m_doc.AddMeshObject( mesh ); context.m_doc.Redraw(); } return success; } ``` Here is another example: ```cpp CRhinoCommand::result CCommandTest::RunCommand(const CRhinoCommandContext& context) { int face_count = 6; int vertex_count = 12; BOOL bVertexNormals = FALSE; BOOL bTextureCoordinates = FALSE; ON_Mesh mesh( face_count, vertex_count, bVertexNormals, bTextureCoordinates ); mesh.SetVertex( 0, ON_3fPoint(0.0f, 0.0f, 1.0f) ); mesh.SetVertex( 1, ON_3fPoint(1.0f, 0.0f, 1.0f) ); mesh.SetVertex( 2, ON_3fPoint(2.0f, 0.0f, 1.0f) ); mesh.SetVertex( 3, ON_3fPoint(3.0f, 0.0f, 0.0f) ); mesh.SetVertex( 4, ON_3fPoint(0.0f, 1.0f, 1.0f) ); mesh.SetVertex( 5, ON_3fPoint(1.0f, 1.0f, 2.0f) ); mesh.SetVertex( 6, ON_3fPoint(2.0f, 1.0f, 1.0f) ); mesh.SetVertex( 7, ON_3fPoint(3.0f, 1.0f, 0.0f) ); mesh.SetVertex( 8, ON_3fPoint(0.0f, 2.0f, 1.0f) ); mesh.SetVertex( 9, ON_3fPoint(1.0f, 2.0f, 1.0f) ); mesh.SetVertex( 10, ON_3fPoint(2.0f, 2.0f, 1.0f) ); mesh.SetVertex( 11, ON_3fPoint(3.0f, 2.0f, 1.0f) ); mesh.SetQuad( 0, 0, 1, 5, 4 ); mesh.SetQuad( 1, 1, 2, 6, 5 ); mesh.SetQuad( 2, 2, 3, 7, 6 ); mesh.SetQuad( 3, 4, 5, 9, 8 ); mesh.SetQuad( 4, 5, 6, 10, 9 ); mesh.SetQuad( 5, 6, 7, 11, 10 ); mesh.ComputeVertexNormals(); mesh.Compact(); context.m_doc.AddMeshObject( mesh ); context.m_doc.Redraw(); return CRhinoCommand::success; } ``` -------------------------------------------------------------------------------- # Adding Online Help to Your Plugin Source: https://developer.rhino3d.com/en/guides/cpp/adding-online-help-to-your-plugin/ Discusses how to add online help support to your Rhino plugin using C/C++. ## Overview Once you have your Rhino plugin completed, you may want to add online help support to help your customers use your plugin efficiently and properly. Most Windows applications provide online help in the form of an HTML help file. ## Authoring Tools HTML help files are made with help authoring tools. Microsoft provides a free utility called the [HTML Help Workshop](http://www.microsoft.com/downloads/details.aspx?displaylang=en&FamilyID=00535334-c8a6-452f-9aa0-d597d16580cc) that can compile existing HTML files into an HTML help file (.chm). This is a fairly low-level utility in that it will only build HTML help files - it will not create or edit content. Thus, most who are looking to produce online help are looking for something a bit more full featured. MadCap make a popular help authoring tool called [MadCap Flare](http://www.madcapsoftware.com/products/flare/) that will help you create, edit, and publish professional quality topic based technical content. This is the tool used to create the help files for Rhino. There are several other tools available on the market. Google "Create HTML Help File" to see the list. ## Plugin Support You can add your plugin to Rhino's *Help* > *Plug-ins* menu by overriding the following two virtual functions: 1. `CRhinoPlugIn::AddToPlugInHelpMenu`: Called by Rhino to determine if the plugin name should be added to the Rhino *Help* > *Plug-ins* menu. 1. `CRhinoPlugIn::OnDisplayPlugInHelp`: Called by Rhino if `CRhinoPlugIn::AddToPlugInHelpMenu` returns true and the menu item associated with this plugin is selected. Details on both of these virtual function can be found in *rhinoSdkPlugIn.h*. ## Command Support While running a Rhino command, you can press F1 to bring up online help for that command. Your plugin commands can do the same. Simply override the `CRhinoCommand::DoHelp` virtual function. If your command is running when the user presses F1, this member will be called. Also, if your want your command's help to appear in Rhino's command help dockable window (*Help* > *Command Help*), then override the `CRhinoCommand::ContextHelpURL` virtual function. Details on both of these virtual function can be found in *rhinoSdkCommand.h*. ## Dialog Support There are a couple of ways you can add help support to dialog boxes. The first is to simply place a "Help" button somewhere on the form. Also, if your dialog box has focus and the user presses F1, Windows will send a WM_HELPINFO message to it. So, by adding a message handler to your dialog box, you can capture these notifications and display the requested information. And, if you add the "Context Help" style to your dialog box resource, a ? icon will appear in the upper right corner of the window. Now, if the user clicks the ? icon and then clicks a control, your dialog's `OnHelpInfo` implementation will and passed information about the control that was clicked. MSDN had lots of information on how to add context help to dialog boxes. ## Related Topics - [HTML Help Workshop (on Microsoft.com)](http://www.microsoft.com/downloads/details.aspx?displaylang=en&FamilyID=00535334-c8a6-452f-9aa0-d597d16580cc) - [MadCap Flare](http://www.madcapsoftware.com/products/flare/) -------------------------------------------------------------------------------- # Adding RhinoScript Support Source: https://developer.rhino3d.com/en/guides/cpp/adding-rhinoscript-support/ This guide demonstrates how to add RhinoScript support to C++ plugins. ## Overview With the Rhino C/C++ SDK, it is possible to write plugins that extend the RhinoScript scripting tool. In order to do this, a plugin must do the following: 1. Create a COM object that implements the `IDispatch` interface. 1. Override the `CRhinoPlugIn::GetPlugInObjectInterface()` to give plugins, such as RhinoScript, access to this object. ## Step-by-Step Let's create a new plugin named *TestScript* that supports RhinoScript scripting. **NOTE**: The purpose of this guide is not to teach COM Automation or to explain how the .IDL file work or what Variants are. There is plenty of information in MSDN and on the Internet that explain these areas. ### Create a plugin that supports Automation 1. Launch Visual Studio C/C++ and create a new Rhino Plugin project named *TestScript*. 1. On the *Plug-in Settings* page of the *Rhino Plug-in Appwizard* dialog box, make sure to check the *Automation* check box... ![AppWizard Automation](https://developer.rhino3d.com/images/adding-rhinoscript-support-01.png) Once the Appwizard has completed creating the skeleton project, build the project. ### Create a COM object that supports IDispatch 1. Create a new MFC class by running the MFC Class Wizard. 1. Name the class *CTestScriptObject*. This class should be derived from `CCmdTarget`. Also, under *Automation*, select the *Automation* radio button. **NOTE**: this COM object will not be creatable by type ID because Rhino plugins (DLLs) are dependent on Rhino. ![MFC Wizard](https://developer.rhino3d.com/images/adding-rhinoscript-support-02.png) ### Add methods to your object An object is not useful unless you expose methods or properties. In this example, we will create a new method that allows the scripter to add point objects to Rhino. ![Add methods](https://developer.rhino3d.com/images/adding-rhinoscript-support-03.png) Within Class View, select your new object's interface and add a new method using the *Add Method Wizard*. Give the new method the name of *AddPoint*. It should have a single VARIANT* argument and return a VARIANT. ![Add methods 2](https://developer.rhino3d.com/images/adding-rhinoscript-support-04.png) ### Implement your methods The *Add Method Wizard* will create a placeholder for our `AddPoint` method in *TestScriptObject.cpp*. Add the following code to your new method: ```cpp VARIANT CTestScriptObject::AddPoint(VARIANT* vaPoint) { VARIANT vaResult; VariantInit(&vaResult); V_VT(&vaResult) = VT_NULL; if( vaPoint ) { ON_3dPoint pt; if( VariantToPoint(*vaPoint, pt) ) { CRhinoDoc* doc = RhinoApp().ActiveDoc(); if( doc ) { CRhinoPointObject* obj = doc->AddPointObject( pt ); if( obj ) { doc->Redraw(); ON_wString wstr; ON_UuidToString( obj->Attributes().m_uuid, wstr ); CString str(wstr); V_VT(&vaResult) = VT_BSTR; vaResult.bstrVal = str.AllocSysString(); } } } } return vaResult; } ``` In the above code, the `VARIANT*` argument is converted to an `ON_3dPoint` using the `VariantToPoint()` function. One of the biggest challenges to creating RhinoScript accessible objects is converting the COM Variant data type to an opennurbs data type and back. Fortunately, we have done all of the work for you. Once the Variant has been converted to an `ON_3dPoint`, the code simply adds the point to Rhino. But, just like RhinoScript's `AddPoint` method, this method also returns the object's unique identifier. ### Allow access to your object Now that you have a COM object that implements `IDispatch` and has at least one method, we can allow access to it. The easiest way is to put a copy of your new COM object on your plugin object as a data member. For example: ```cpp class CTestScriptPlugIn : public CRhinoUtilityPlugIn { public: CTestScriptPlugIn(); ~CTestScriptPlugIn(); // Required overrides const wchar_t* PlugInName() const; const wchar_t* PlugInVersion() const; GUID PlugInID() const; BOOL OnLoadPlugIn(); void OnUnloadPlugIn(); // Optional overrides LPUNKNOWN GetPlugInObjectInterface( const ON_UUID& iid ); private: ON_wString m_plugin_version; CTestScriptObject m_object; }; ``` Then, allow access to the object by overriding the `GetPlugInObjectInterface()` virtual function. For example: ```cpp LPUNKNOWN CTestScriptPlugIn::GetPlugInObjectInterface( const ON_UUID& iid ) { LPUNKNOWN lpUnknown = 0; if( iid == IID_IUnknown ) lpUnknown = m_object.GetInterface( &IID_IUnknown ); else if( iid == IID_IDispatch ) lpUnknown = m_object.GetInterface( &IID_IDispatch ); if( lpUnknown ) m_object.ExternalAddRef(); return lpUnknown; } ``` **NOTE**: RhinoScript will only request an `IDispatch` object from your plugin. Also because our object is a data member of our plugin object, we must increment our object's reference counter. Otherwise, when VBScript releases our object, which will decrement the reference counter, our object will be destroyed. This will cause your plugin to crash. ### Implement your RhinoScript Methods Once you have implemented your methods, you can begin to test them. Launch Rhino and use the *PlugInManager* command to install your new plugin. Then, use RhinoScript's EditScript dialog to test the methods in your plugin's object. The following RhinoScript demonstrates how to get our plugins scriptable object and run the `AddPoint` method... ```vbnet Sub MyTest Dim objPlugIn, arrPoint, idPoint On Error Resume Next Set objPlugIn = Rhino.GetPluginObject("TestScript") If Err Then MsgBox Err.Description Exit Sub End If arrPoint = Rhino.GetPoint("Base point") If IsArray(arrPoint) Then idPoint = objPlugIn.AddPoint(arrPoint) End If Set objPlugIn = Nothing End Sub ``` -------------------------------------------------------------------------------- # Adding to Rhino's File Search Path Source: https://developer.rhino3d.com/en/guides/cpp/adding-to-rhinos-file-search-path/ This brief guide demonstrates how to add a file path to Rhino's file search path using C/C++. ## Problem You would like to add a file path to Rhino's file search path without having to script the Options command. ## Solution Rhino's file search path is held by Rhino's `CRhinoDirectoryManager` object. You can get the one and only `CRhinoDirectoryManager` singleton as follows: ```cpp CRhinoDirectoryManager& dm = RhinoApp().RhinoDirectoryManager(); ``` ## Example The following utility function demonstrates how to add (insert append or insert) to Rhino's file search path: ```cpp int RhAddSearchPath( const wchar_t* pszFolder, int index = -1 ) { int rc = -1; if( 0 == pszFolder || 0 == pszFolder[0] ) return -1; int rc = -1; if( CRhinoFileUtilities::DirExists(pszFolder) ) { CRhinoDirectoryManager& dm = RhinoApp().RhinoDirectoryManager(); const int path_count = dm.SearchPathCount(); for( int i = 0; i < path_count; i++ ) { if( 0 == on_wcsicmp(pszFolder, dm.SearchPath(i)) ) { rc = i; break; } } if( rc == -1 ) { index = RHINO_CLAMP( index, -1, path_count ); if( index == -1 ) rc = dm.AppendSearchPath( pszFolder ); else if( dm.InsertSearchPath(index, pszFolder) ) rc = index; } } return rc; } ``` -------------------------------------------------------------------------------- # Adding User Strings to Objects Source: https://developer.rhino3d.com/en/guides/cpp/adding-user-strings-to-objects/ This guide discusses attaching custom user data to any object using the Rhino C/C++ SDK. ## Overview User Data is a powerful set of APIs that allow plugin developers to attach custom data of any kind to any object derived from `ON_Object`. In order to take advantage of User Data, you are required to implement your own user data object by deriving a class from `ON_UserData` and overriding the required virtual functions. In Rhino, the SDK adds a new standardized approach for adding User Data to objects called User Strings. The Rhino C/C++ SDK allows you to quickly attaches User Data in the form of a key-value string pair to any object derived from `ON_Object`. This feature is exposed to the C/C++ SDK as member functions on `ON_Object`: - `ON_Object::SetUserString`: attaches a user string to an object. This information will persist through copy construction, operator=, and file IO. - `ON_Object::GetUserString`: gets a user string from an object. - `ON_Object::GetUserStringKeys`: retrieves a list of all user string keys on an object. - `ON_Object::GetUserStrings`: retrieves a list of all user strings on an object. There are a number of advantages to User Strings: - The mechanism is very simple - you do not have to derive any new classes. - Rhino is responsible for all of the file IO. - User Strings can hold text of any length and format, including XML. - Since the mechanism is standard, user strings can shared between Rhino and other plugins. For example, you can use Rhino's *GetUserText* and *SetUserText* commands to get and set user strings. ## Example The following example demonstrates how to add User Strings to a selected object... ```cpp CRhinoCommand::result CCommandTest::RunCommand( const CRhinoCommandContext& context ) { bool bAttribute = true; CRhinoGetObject go; go.SetCommandPrompt( L"Select object to attach user text" ); go.AddCommandOptionToggle( RHCMDOPTNAME(L"Location"), RHCMDOPTVALUE(L"Object"), RHCMDOPTVALUE(L"Attribute"), bAttribute, &bAttribute ); for(;;) { CRhinoGet::result res = go.GetObjects( 1, 1 ); if( res == CRhinoGet::option ) continue; if( res != CRhinoGet::object ) return cancel; break; } const CRhinoObjRef& ref = go.Object(0); const CRhinoObject* obj = ref.Object(); if( !obj ) return failure; ON_wString key = L"test"; ON_wString text = L"sample text"; if( bAttribute ) { // Attach user string to object's attributes CRhinoObjectAttributes attribs = obj->Attributes(); attribs.SetUserString( key, text ); context.m_doc.ModifyObjectAttributes( ref, attribs ); } else { // Attach user string to object's geometry CRhinoObject* dupe = obj->DuplicateRhinoObject(); if( dupe ) { ON_Geometry* geom = const_cast( dupe->Geometry() ); if( geom ) { geom->SetUserString( key, text ); context.m_doc.ReplaceObject( ref, dupe ); } } } return success; } ``` -------------------------------------------------------------------------------- # Adjusting Clipping Planes from Conduits Source: https://developer.rhino3d.com/en/guides/cpp/adjusting-clipping-planes-from-conduits/ This brief guide discusses adjusting clipping planes from display conduits using C/C++. ## Overview You may reason that it is necessary to change Rhino's Z-Buffer range because when you draw using the `CRhinoDisplayConduit` class, as objects that are far from Rhino's objects are being clipped. However, the Z-Buffer range has nothing to do with whether or not objects get culled, or clipped, from the viewing frustum. This behavior has to do with Rhino's near and far clipping planes. Rhino's clipping planes are adjusted dynamically in order to maximize the precision within the Z-Buffer. Additionally, Rhino does this by placing the near plane as close to the closest visible object in the frustum, and the far plane as far as the furthest visible object in the frustum. If you are adding or drawing objects that Rhino does not know about - that are not in the drawing database - then they will not be included in those calculations. Thus, you must also implement the `SC_CALCBOUNDINGBOX` channel inside your conduit and add to the overall scene bounding box. This will make Rhino adjust its clipping planes to include your objects. For example... ## Example ```cpp class CTestDisplayConduit : public CRhinoDisplayConduit { public: CTestDisplayConduit(); bool ExecConduit( CRhinoDisplayPipeline& dp, UINT nChannel, bool& bTerminate ); // TODO: add other methods and members here }; CTestDisplayConduit::CTestDisplayConduit() : CRhinoDisplayConduit(CSupportChannels::SC_PREDRAWOBJECTS|CSupportChannels::SC_CALCBOUNDINGBOX) { // TODO: initialize members here } bool CTestDisplayConduit::ExecConduit( CRhinoDisplayPipeline& dp, UINT nChannel, bool& bTerminate ) { switch( nChannel ) { case CSupportChannels::SC_CALCBOUNDINGBOX: { m_pChannelAttrs->m_BoundingBox.Union( /*YOUR_OBJECTS_BOUNDING_BOXES*/ ); break; } case CSupportChannels::SC_PREDRAWOBJECTS: { // TODO: add drawing code here break; } } return true; } ``` -------------------------------------------------------------------------------- # Adjusting Isocurve Density Source: https://developer.rhino3d.com/en/guides/cpp/adjusting-isocurve-density/ This brief guide demonstrates how to modify the isocurve density of a surface. ## Problem When creating a new surface from a selected curve, it is always a single isocurve crossing the surface. One has to adjust isocurve density after the fact from Rhino's Properties window. It is possible to do this automatically from a plugin? ## Solution The isocurve density for surface object is stored on the object's attributes. For C/C++ plugins, this would be the `CRhinoObjectAttributes` and `ON_3dmObjectAttributes` classes. Just set the `m_wire_density` property. Note: setting this property to zero (0) will disable the display of isocurves for that object. ## Example ```cpp CRhinoCommand::result CCommandFooBar::RunCommand( const CRhinoCommandContext& context ) { CRhinoGetObject go; go.SetGeometryFilter( CRhinoGetObject::surface_object ); go.GetObjects( 1, 1 ); if( go.CommandResult() == CRhinoCommand::success ) { const CRhinoObjRef& objref = go.Object(0); const CRhinoBrepObject* brep_obj = CRhinoBrepObject::Cast( objref.Object() ); if( brep_obj ) { CRhinoObjectAttributes atts = brep_obj->Attributes(); atts.m_wire_density = 3; // for example context.m_doc.ModifyObjectAttributes( objref, atts ); context.m_doc.Redraw(); } } return go.CommandResult(); } ``` -------------------------------------------------------------------------------- # Advanced Display Settings Source: https://developer.rhino3d.com/en/samples/rhinocommon/advanced-display-settings/ Demonstrates how to change advanced display settings in Rhino where the mesh wireframe thickness (in pixels) is modified. ```cs partial class Examples { // The following example code demonstrates how to modify advanced display settings using // the Rhino SDK. In this example, a display mode's mesh wireframe thickness (in pixels) // will be modified. public static Rhino.Commands.Result AdvancedDisplay(Rhino.RhinoDoc doc) { // Use the display attributes manager to build a list of display modes. // Note, these are copies of the originals... DisplayModeDescription[] display_modes = DisplayModeDescription.GetDisplayModes(); if( display_modes==null || display_modes.Length<1 ) return Rhino.Commands.Result.Failure; // Construct an options picker so the user can pick which // display mode they want modified Rhino.Input.Custom.GetOption go = new Rhino.Input.Custom.GetOption(); go.SetCommandPrompt("Display mode to modify mesh thickness"); List opt_list = new List(); for( int i=0; i True strLine = objFile.ReadLine ' Convert the string to a point arrPt = PointFromString(strLine) If IsArray(arrPt) Then ReDim Preserve arrPoints(nCount) arrPoints(nCount) = arrPt nCount = nCount + 1 End If Loop ' Close the curve ReDim Preserve arrPoints(nCount) arrPoints(nCount) = arrPoints(0) ' Add the named interpolated curve If IsArray(arrPoints) Then strCurve = Rhino.AddInterpCurveEx(arrPoints) Rhino.ObjectName strCurve, strAirfoil End If ' Close the file and release objects objFile.Close Set objFile = Nothing Set objFSO = Nothing End Sub ' Function to generate a point from a string Function PointFromString( strLine ) Dim arrTokens, arrPoint, x, y PointFromString = Null If VarType(strLine) = vbString Then strLine = Trim(strLine) arrTokens = Rhino.StrTok(strLine, " ") If IsArray(arrTokens) And UBound(arrTokens) = 1 Then x = CDbl(arrTokens(0)) y = CDbl(arrTokens(1)) arrPoint = Array(x, y, 0.0) PointFromString = arrPoint End If End If End Function ``` -------------------------------------------------------------------------------- # Annotation Objects Source: https://developer.rhino3d.com/en/guides/cpp/annotation-objects/ This guide discusses the annotation objects provided by the Rhino SDK. ## Overview There are two virtual and seven concrete annotation objects. The seven concrete classes are: - `Text`: Created by the Rhino Text command. - `Leader`: Created by the Rhino Leader command. - `Linear dimension`: Created by the Rhino Dim, DimAligned, and DimRotated commands. - `Angular dimension`: Created by the Rhino DimAngle command. - `Radial dimension`: Created by the Rhino DimRadius and DimDiameter commands. - `Ordinate dimension`: Created by the Rhino DimOrdinate command. - `Centermark`: Created by the Rhino Centermark command. The two virtual classes are: - `Annotation `: Contains text content, plane, and dimension style information. This is the base class for all annotation objects. - `Dimension`: Contains information common to the linear, angular, radial, ordinate, and centermark dimensions, primarily associated with the measured value (distance, angle). The annotation geometry class hierarchy in openNURBS is: - `ON_Annotation`: Pure virtual class derived from `ON_Geometry`. - `ON_Text`: Text geometry. - `ON_Leader`: Leader geometry. - `ON_Dimension`: Pure virtual class derived from `ON_Annotation`. - `ON_DimLinear`: Linear dimension geometry. - `ON_DimAngular`: Angular dimension geometry. - `ON_DimRadial`: Radial dimension geometry. - `ON_DimOrdinate`: Ordinate dimension geometry. - `ON_Centermark`: Centermark geometry. Other openNURBS classes and useful member functions: - `ON_TextContent`: Class that manages the RTF text and related text information and is a member of `ON_Annotation`. - `ON_Annotation.Text()`: Rreturns a pointer to the object's text content. - `ON_Annotation.RichText()`: Returns the "raw" rich text string. - `ON_Annotation.PlainText()`: Returns the "string" part formatting instructions are removed. The Rhino annotation object class hierarchy, including their geometry class member, in the Rhino SDK is: - `CRhinoAnnotation, ON_Annotation`: Pure virtual class derived from `CRhinoObject`. - `CRhinoText, ON_Text`: Text object. - `CRhinoLeader, ON_Leader`: Leader object. - `CRhinoDimension, ON_Dimension`: Pure virtual class derived from `CRhinoAnnotation`. - `CRhinoDimLinear, ON_DimLinear`: Linear dimension object. - `CRhinoDimAngular, ON_DimAngular`: Angular dimension object. - `CRhinoDimRadial, ON_DimRadial`: Radial dimension object. - `CRhinoDimOrdinate, ON_DimOrdinate`: Ordinate dimension object. - `CRhinoCentermark, ON_Centermark`: Centermark object. ## Creating Annotation Objects Every annotation object needs a plane, dimension style, location, and text content. In addition, each specific type of annotation needs its specific information. Here are simple examples showing how to create annotation objects. Below, doc is a `CRhinoDoc` reference. ### Text Creates a text object that says “Hello world” with the upper left corner at (1,2,3). The font, text height, and so on all use the current dimension style - `CRhinoDimStyleTable::CurrentDimStyle()`. ```cpp doc.AddTextObject( L"Hello world", ON_Plane::World_xy, ON_3dPoint(1, 2, 3) ); ``` ### Leader Create leader object with the arrow tip at (0,0,11) and text that says “Hello world” to the right of (8,-5,11). ```cpp ON_3dPoint leader_polyline[] = { ON_3dPoint(0,0,11), ON_3dPoint(5,-5,11), ON_3dPoint(8,-5,11) }; doc.AddLeaderObject( L"Hello world", ON_Plane::World_xy, 3, Leader_polyline ); ``` ### Angular Dimension Create from an arc and an offset. ```cpp CArgsRhinoGetArc args; ON_Arc arc; cmdrc = RhinoGetArc(args, arc, nullptr); if (CRhinoCommand::success != cmdrc) return cmdrc; double offset = arc.Radius() / 10.0; doc.AddDimAngularObject(arc, offset); ``` Create from two lines and a point on the angular dimension. ```cpp ON_Line line1(ON_3dPoint::Origin, ON_3dPoint(20, 0, 0)); ON_Line line2(ON_3dPoint::Origin, ON_3dPoint(20, 20, 0)); // Acute angle has arc point "between" the line segments. ON_3dPoint acute_angle_point(10, 5, 0); doc.AddDimAngularObject(line1, ON_3dPoint::UnsetPoint, line2, ON_3dPoint::UnsetPoint, acute_angle_point, false); // Obtuse angle has arc point "outside" the line segments. ON_3dPoint obtuse_angle_point(-10, -5, 0); doc.AddDimAngularObject(line1, ON_3dPoint::UnsetPoint, line2, ON_3dPoint::UnsetPoint, obtuse_angle_point, false); ``` ### Aligned Linear Dimensions Create an aligned linear dimension from the extension points and a point on the dimension line. ```cpp const CRhinoDimLinear* dim = doc.AddDimLinearObject( ON_3dPoint(0,0,0), ON_3dPoint(50,20,0), ON_3dPoint(25,25,0), ON_3dVector::ZAxis ); ``` ### Rotated Linear Dimensions Create a rotated linear dimensions from the extension points and a dimension lines. ```cpp ON_3dPoint ext_points[5] = { ON_3dPoint(0,-20,0), ON_3dPoint(12,-8,0), ON_3dPoint(25,4,0), ON_3dPoint(38,9,0), ON_3dPoint(50,-3,0) }; const ON_Line dim_line(ON_3dPoint(0, 0, 0), ON_3dPoint(1, 0, 0)); for (int i = 0; i < 4; i++) { doc.AddDimLinearObject( ext_points[i], ext_points[i+1], dim_line, ON_3dVector::ZAxis ); } ``` ## Using Styles When you need to customize annotation appearance, use override styles. Create a text object that says “Hello world” in bold comic sans 7 units high. ```cpp const ON_Font* comic_sans_bold = ON_Font::GetManagedFont(L"Comic Sans MS",true, false); ON_DimStyle style = doc.m_dimstyle_table.CurrentDimStyle().CreateOverrideCandidate(); style.SetFont(*comic_sans_bold); style.SetTextHeight(7); doc.AddTextObject( L"Hello world", ON_Plane::World_xy, ON_3dPoint::Origin, &style ); ``` ## Customization If you need to make something the `CRhinoDoc::AddObject()` functions cannot produce, then you make your own annotation geometry. Here is an example that makes a blue leader with a curved line. ```cpp const ON_DimStyle parent_style = doc.m_dimstyle_table.CurrentDimStyle(); ON_3dPoint leader_control_points[]= { ON_3dPoint(0,0,0), ON_3dPoint(10,0,0), ON_3dPoint(5,5,0), ON_3dPoint(15,5,0) }; ON_Leader leader; leader.Create( L"Wiggle", &parent_style, 4, leader_control_points, ON_Plane::World_xy, false, 0.0); leader.SetLeaderCurveType(&parent_style, ON_DimStyle::leader_curve_type::Spline); ON_3dmObjectAttributes attributes; doc.GetDefaultObjectAttributes(attributes); attributes.SetColorSource(ON::object_color_source::color_from_object); attributes.m_color = ON_Color::SaturatedBlue; CRhinoLeader* rhino_leader = doc.CreateLeaderObject( Leader, &attributes ); doc.AddObject(rhino_leader); ``` ## Annotation Details ### Plane Every annotation object is planar and the plane specifies where that plane is located in model space. ```cpp ON_Plane plane = CRhinoAnnotation.Plane(); ``` ### Dimension Style The dimension style specifies all appearance properties like the text font, size, and alignment, arrow head shape, and so on. There are about 100 appearance properties in a dimension style. The model contains several basic dimension styles and every annotation object references one of the basic model dimension styles. If an annotation object requires some customized dimension style, those are set using a property setting function on `ON_Annotation`. -------------------------------------------------------------------------------- # Applying Non-Uniform Transformations to Objects Source: https://developer.rhino3d.com/en/guides/cpp/applying-non-uniform-transforms-to-objects/ This brief guide discusses similarity transformations and how to use them on objects. ## Discussion Imagine you are trying to apply a non-uniform scale to a cylinder. If the same scale operation is applied to another object type, it works as expected; but not a cylinder. Why? Not all `ON_Geometry` derived classes can be can be accurately modified with "squishy" transformations like projections, shears, an non-uniform scaling. For example, if you were to apply a non-uniform scale to a circle, which is represented by an `ON_ArcCurve` curve, the resulting geometry is no longer a circle. Before transforming your object, you will want to first test to see if the the transformation is a similarity transformation. This can be done by using `ON_XForm::IsSimilarity()`. See *opennurbs_xform.h* for more information. If the transformation is not a similarity, then you should convert the geometry into a form that can be accurately modified. This can be done by using the `ON_Geometry::MakeDeformable()` override on the geometric object. -------------------------------------------------------------------------------- # Arc Point Distribution Source: https://developer.rhino3d.com/en/samples/rhinoscript/arc-point-distribution/ Demonstrates arc point distribution. ```vbnet Option Explicit Sub ArcPointDistribution ' Local variables Dim arc, cnt, rads Dim n_t(), n, i, a0, a1 Dim line, dom, t ' Select arc curve arc = Rhino.GetObject("Select arc", 4) If IsNull(arc) Then Exit Sub If Not Rhino.IsArc(arc) Then Exit Sub ' Get number of points to calculate cnt = Rhino.GetInteger("Number of points", 2) If IsNull(cnt) Then Exit Sub rads = Rhino.ToRadians(Rhino.ArcAngle(arc)) n = cnt - 1 ReDim n_t(n) ' Calculate normalized parameters For i = 0 To n a0 = Sin(rads/2) a1 = Sin(i*rads/n - rads/2) n_t(i) = (a0+a1)/(2*a0) Next Rhino.EnableRedraw False line = Rhino.AddLine(Rhino.CurveStartPoint(arc), Rhino.CurveEndPoint(arc)) dom = Rhino.CurveDomain(line) For i = 0 To n ' Convert normalized parameter to domain value t = (1.0 - n_t(i)) * dom(0) + n_t(i) * dom(1) Rhino.AddPoint Rhino.EvaluateCurve(line, t) Next Rhino.EnableRedraw True End Sub ``` -------------------------------------------------------------------------------- # Archimedean Spirals Source: https://developer.rhino3d.com/en/guides/rhinoscript/archimedian-spirals/ This guide demonstrates how to create Archimedean Spirals using RhinoScript. ## Overview It is possible to define an Archimedean Spiral with polar coordinates. In polar coordinates $$(r, θ)$$, an Archimedean Spiral can be described by the following equation: $$r = a+bθ$$ with real numbers $$a$$ and $$b$$. Changing the parameter a will turn the spiral, while $$b$$ controls the distance between successive turnings... ![Archimedean Spiral](https://developer.rhino3d.com/images/archimedean-spirals-01.png) ## Sample Once the polar coordinates have been calculated, we can use RhinoScript's `Polar` method to convert them to Cartesian coordinates, which will allow us to plot the curve using RhinoScript's `AddInterpCurve` method. The following sample script code demonstrates how to create an interpolated curve through the points that were calculated using the above equation... ```vbnet ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' ArchimedeanSpiral.rvb -- June 2008 ' If this code works, it was written by Dale Fugier. ' If not, I don't know who wrote it. ' Works with Rhino 4.0. Option Explicit Sub ArchimedeanSpiral() Dim a_const, b_const, step_angle, num_points Dim curr_angle, base_point, radius, points(), i Rhino.Print "Archimedean Spiral (r = a + bθ)" a_const = Rhino.GetReal("Value of 'A' constant", 1.0, 0.01) If IsNull(a_const) Then Exit Sub b_const = Rhino.GetReal("Value of 'B' constant", 1.0, 0.01) If IsNull(a_const) Then Exit Sub num_points = Rhino.GetInteger("Number of points to calculate", 10, 2) If IsNull(num_points) Then Exit Sub step_angle = Rhino.GetReal("Angle between points", 30.0, 1.0, 45.0) If IsNull(step_angle) Then Exit Sub curr_angle = 0.0 base_point = Array(0.0, 0.0, 0.0) ReDim points(num_points - 1) For i = 0 To UBound(points) radius = a_const + (b_const * curr_angle) points(i) = Rhino.Polar(base_point, radius, curr_angle) curr_angle = curr_angle + step_angle Next Rhino.AddInterpCurve points 'Rhino.AddPoints points End Sub ``` -------------------------------------------------------------------------------- # Archiving Curves to a File Source: https://developer.rhino3d.com/en/guides/cpp/archiving-curves-to-a-file/ This guide demonstrates how to write and read curves to a file using C/C++. ## Problem Writing curves to a file has some special considerations. Curves come in many variations: the curve could be a circle, simple curve, or a polycurve. You cannot know in advance what the user will be selecting. As such, the class containing my data declares it as an `ON_Curve`. Writing the data is not an issue. You simply call `ON_Curve::Write`. However, things don't go so well when you try to read the data. You need a way to simply read curve data without going to a non-abstract class. What is needed is a way to read/write curve data that represents any kind of curve possible. ## Solution It is probably best not to call `ON_Curve::Write` for this very reason. When serializing anything derived from `ON_Object`, just use `ON_BinaryArchive::WriteObject` and `ON_BinaryArchive::ReadObject`... ### Write ```cpp static bool WriteCurveFile(FILE* fp, const ON_Curve* curve) { if (nullptr == fp || nullptr == curve) return false; ON_BinaryFile archive(ON::archive_mode::write3dm, fp); int major_version = 1; int minor_version = 0; bool rc = archive.BeginWrite3dmChunk(TCODE_ANONYMOUS_CHUNK, major_version, minor_version); if (!rc) return false; for (;;) { // version 1.0 fields rc = (archive.WriteObject(curve) ? true : false); if (!rc) break; // todo... break; } if (!archive.EndWrite3dmChunk()) rc = false; return rc; } ``` ### Read ```cpp static bool ReadCurveFile(FILE* fp, ON_Curve*& curve) { if (nullptr == fp) return false; ON_BinaryFile archive(ON::archive_mode::read3dm, fp); int major_version = 0; int minor_version = 0; bool rc = archive.BeginRead3dmChunk(TCODE_ANONYMOUS_CHUNK, &major_version, &minor_version); if (!rc) return false; for (;;) { rc = (1 == major_version); if (!rc) break; // version 1.0 fields ON_Object* object = 0; rc = (archive.ReadObject(&object) ? true : false); if (!rc) break; curve = ON_Curve::Cast(object); // todo... break; } if (!archive.EndRead3dmChunk()) rc = false; return (rc && curve); } ``` ## Sample To use the above functions, you could do the following: ```cpp bool rc = false; FILE* fp = ON::OpenFile(filename, L"wb"); if (fp) { rc = WriteCurveFile(fp, curve); ON::CloseFile(fp); } if (rc) RhinoApp().Print(L"Successfully wrote %s.\n", filename); else RhinoApp().Print(L"Errors while writing %s.\n", filename); ``` and ```cpp bool rc = false; ON_Curve* curve = nullptr; FILE* fp = ON::OpenFile(filename, L"rb"); if (fp) { rc = ReadCurveFile(fp, curve); ON::CloseFile(fp); } if (rc) { CRhinoCurveObject* curve_object = new CRhinoCurveObject(); curve_object->SetCurve(curve); context.m_doc.AddObject(curve_object); context.m_doc.Redraw(); } else { RhinoApp().Print(L"Errors while reading %s.\n", filename); } ``` -------------------------------------------------------------------------------- # Array By Distance Source: https://developer.rhino3d.com/en/samples/rhinocommon/array-by-distance/ Demonstrates how to array a user-selected object by specifying a start point and a contraint line. ```cs partial class Examples { static double m_distance = 1; static Rhino.Geometry.Point3d m_point_start; public static Rhino.Commands.Result ArrayByDistance(Rhino.RhinoDoc doc) { Rhino.DocObjects.ObjRef objref; var rc = Rhino.Input.RhinoGet.GetOneObject("Select object", true, Rhino.DocObjects.ObjectType.AnyObject, out objref); if (rc != Rhino.Commands.Result.Success) return rc; rc = Rhino.Input.RhinoGet.GetPoint("Start point", false, out m_point_start); if (rc != Rhino.Commands.Result.Success) return rc; var obj = objref.Object(); if (obj == null) return Rhino.Commands.Result.Failure; // create an instance of a GetPoint class and add a delegate // for the DynamicDraw event var gp = new Rhino.Input.Custom.GetPoint(); gp.DrawLineFromPoint(m_point_start, true); var optdouble = new Rhino.Input.Custom.OptionDouble(m_distance); bool constrain = false; var optconstrain = new Rhino.Input.Custom.OptionToggle(constrain, "Off", "On"); gp.AddOptionDouble("Distance", ref optdouble); gp.AddOptionToggle("Constrain", ref optconstrain); gp.DynamicDraw += ArrayByDistanceDraw; gp.Tag = obj; while (gp.Get() == Rhino.Input.GetResult.Option) { m_distance = optdouble.CurrentValue; if (constrain != optconstrain.CurrentValue) { constrain = optconstrain.CurrentValue; if (constrain) { var gp2 = new Rhino.Input.Custom.GetPoint(); gp2.DrawLineFromPoint(m_point_start, true); gp2.SetCommandPrompt("Second point on constraint line"); if (gp2.Get() == Rhino.Input.GetResult.Point) gp.Constrain(m_point_start, gp2.Point()); else { gp.ClearCommandOptions(); optconstrain.CurrentValue = false; constrain = false; gp.AddOptionDouble("Distance", ref optdouble); gp.AddOptionToggle("Constrain", ref optconstrain); } } else { gp.ClearConstraints(); } } } if (gp.CommandResult() == Rhino.Commands.Result.Success) { m_distance = optdouble.CurrentValue; var pt = gp.Point(); var vec = pt - m_point_start; double length = vec.Length; vec.Unitize(); int count = (int)(length / m_distance); for (int i = 1; i < count; i++) { var translate = vec * (i * m_distance); var xf = Rhino.Geometry.Transform.Translation(translate); doc.Objects.Transform(obj, xf, false); } doc.Views.Redraw(); } return gp.CommandResult(); } // this function is called whenever the GetPoint's DynamicDraw // event occurs static void ArrayByDistanceDraw(object sender, Rhino.Input.Custom.GetPointDrawEventArgs e) { Rhino.DocObjects.RhinoObject rhobj = e.Source.Tag as Rhino.DocObjects.RhinoObject; if (rhobj == null) return; var vec = e.CurrentPoint - m_point_start; double length = vec.Length; vec.Unitize(); int count = (int)(length / m_distance); for (int i = 1; i < count; i++) { var translate = vec * (i * m_distance); var xf = Rhino.Geometry.Transform.Translation(translate); e.Display.DrawObject(rhobj, xf); } } } ``` ```python import Rhino import scriptcontext def dynamic_array(): rc, objref = Rhino.Input.RhinoGet.GetOneObject("Select object", True, Rhino.DocObjects.ObjectType.AnyObject) if rc!=Rhino.Commands.Result.Success: return rc, pt_start = Rhino.Input.RhinoGet.GetPoint("Start point", False) if rc!=Rhino.Commands.Result.Success: return obj = objref.Object() if not obj: return dist = 1 if "dynamic_array_distance" in scriptcontext.sticky: dist = scriptcontext.sticky["dynamic_array_distance"] # This is a function that is called whenever the GetPoint's # DynamicDraw event occurs def ArrayByDistanceDraw( sender, args ): rhobj = args.Source.Tag if not rhobj: return vec = args.CurrentPoint - pt_start length = vec.Length vec.Unitize() count = int(length / dist) for i in range(1,count): translate = vec * (i*dist) xf = Rhino.Geometry.Transform.Translation(translate) args.Display.DrawObject(rhobj, xf) # Create an instance of a GetPoint class and add a delegate # for the DynamicDraw event gp = Rhino.Input.Custom.GetPoint() gp.DrawLineFromPoint(pt_start, True) optdouble = Rhino.Input.Custom.OptionDouble(dist) constrain = False optconstrain = Rhino.Input.Custom.OptionToggle(constrain,"Off", "On") gp.AddOptionDouble("Distance", optdouble) gp.AddOptionToggle("Constrain", optconstrain) gp.DynamicDraw += ArrayByDistanceDraw gp.Tag = obj while gp.Get()==Rhino.Input.GetResult.Option: dist = optdouble.CurrentValue if constrain!=optconstrain.CurrentValue: constrain = optconstrain.CurrentValue if constrain: gp2 = Rhino.Input.Custom.GetPoint() gp2.DrawLineFromPoint(pt_start, True) gp2.SetCommandPrompt("Second point on constraint line") if gp2.Get()==Rhino.Input.GetResult.Point: gp.Constrain(pt_start, gp2.Point()) else: gp.ClearCommandOptions() optconstrain.CurrentValue = False constrain = False gp.AddOptionDouble("Distance", optdouble) gp.AddOptionToggle("Constrain", optconstrain) else: gp.ClearConstraints() continue if gp.CommandResult()==Rhino.Commands.Result.Success: scriptcontext.sticky["dynamic_array_distance"] = dist pt = gp.Point() vec = pt - pt_start length = vec.Length vec.Unitize() count = int(length / dist) for i in range(1, count): translate = vec * (i*dist) xf = Rhino.Geometry.Transform.Translation(translate) scriptcontext.doc.Objects.Transform(obj,xf,False) scriptcontext.doc.Views.Redraw() if( __name__ == "__main__" ): dynamic_array() ``` -------------------------------------------------------------------------------- # Array Dimensions & Upper Bounds Source: https://developer.rhino3d.com/en/guides/rhinoscript/array-bounds/ This guide discusses how to determine the dimension and the upper bounds of arrays in RhinoScript. ## Overview In a two-dimensional array, how do you determine the upper bounds of the array for each dimension? Also, how do you handle jagged arrays? That is, an array whose elements themselves are arrays? How do I find the upper bounds of two, three, or n-dimensional array when the array is rectangular or jagged? ## UBound To determine the upper bounds of an array, use VBScript's `UBound` method. The `UBound` method returns the largest available subscript for the indicated dimension of an array. The syntax for `UBound` is: ```vbnet UBound(arrayname [,dimension]) ``` where... - `arrayname` *(required)* is the name of the array variable. - `dimension` *(optional)* is whole number indicating which dimension's upper bound is returned. Use 1 for the first dimension, 2 for the second, and so on. If dimension is omitted, 1 is assumed. For example: ```vbnet Dim A(100,3,4) UBound(A) ' 100 UBound(A, 1) ' 100 UBound(A, 2) ' 3 UBound(A, 3) ' 4 ``` ## Jagged Arrays What if you do not know an array's dimensions, which might be the case with a jagged array? How do you know what dimension values are valid to pass to `UBound`? VBScript does not have a function that returns the number of dimensions of an array. But, by using the `UBound` method and some simple error checking, we can write our own function that determines the number of dimension of an array. Consider the following function: ```vbnet 'Description ' Returns the dimension of an array. 'Parameters ' arr - Name of the array variable. 'Returns ' The dimension of the array if successful. ' Null on error. ' Function GetArrayDim(ByVal arr) GetArrayDim = Null Dim i If IsArray(arr) Then For i = 1 To 60 On Error Resume Next UBound arr, i If Err.Number <> 0 Then GetArrayDim = i-1 Exit Function End If Next GetArrayDim = i End If End Function ``` `GetArrayDim` simply calls `UBound` with a different dimension parameter until an error is thrown. Note, since VBScript allows arrays of up to 60 dimensions, we must check up to this value. Now that we have a function that will return the dimensions of an array, we can write a subroutine that dumps out the dimensions and upper bounds of either a rectangular or jagged array. For example: ```vbnet 'Description ' Prints an array's dimensions and upper bounds to the command line. 'Parameters ' arr - Name of the array variable. ' Sub DumpArrayInfo(arr) Dim i, j, d, b If IsArray(arr) Then For i = 0 To UBound(arr) If IsArray(arr(i)) Then d = GetArrayDim(arr(i)) If IsNull(d) Then Rhino.Print "Element(" & CStr(i) & ") is not dimensioned" Else Rhino.Print "Element(" & CStr(i) & ") dimension = " & CStr(d) For j = 1 To d b = GetArrayUBound(arr(i), j) If IsNull(b) Then Rhino.Print " Dimension(" & CStr(j) & ") has no bounds" Else Rhino.Print " Dimension(" & CStr(j) & ") bounds = " & CStr(b) End If Next End If Else Rhino.Print "Element(" & CStr(i) & ") is not an array" End If Next End If End Sub ``` For every element in an array, `DumpArrayInfo` calls `GetArrayDim` to determine the dimension of the array element. If `GetArrayDim` returns a valid result, then the subroutine determines the upper bounds in each dimension of the array element and prints the results to the Rhino command line. **NOTE**: `DumpArrayInfo` uses a `UBound` helper function named `GetArrayUBound` that is a little safer than just using `UBound`... ```vbnet 'Description ' Safely returns the largest available subscript for ' the indicated dimension of an array. 'Parameters ' arr - Name of the array variable. ' i - Number indicating which dimension's upper bound to return. 'Returns ' The upper bounds for the indicated dimension if successful. ' Null on error. ' Function GetArrayUBound(ByVal arr, ByVal i) GetArrayUBound = Null If IsArray(arr) Then On Error Resume Next b = UBound(arr, i) If Err.Number = 0 Then GetArrayUBound = b End If End Function ``` We can test our array dumping subroutine as follows: ```vbnet Sub TestDumpArrayInfo ' Declare arrays of various dimensions Dim arr0(6) Dim arr1(5,4) Dim arr2(3,2,1) Dim arr3() Dim arr4 ' Create a jagged array Dim arr(4) arr(0) = arr0 arr(1) = arr1 arr(2) = arr2 arr(3) = arr3 arr(4) = arr4 DumpArrayInfo arr End Sub ``` -------------------------------------------------------------------------------- # Array Points on Surface Source: https://developer.rhino3d.com/en/samples/rhinoscript/array-points-on-surface/ Demonstrates how to array points on a surface with a RhinoScript. ```vbnet Option Explicit Sub ArrayPointsOnSurface() ' Get the surface object Dim srf : srf = Rhino.GetObject("Select surface", 8, vbTrue) If IsNull(srf) Then Exit Sub ' Get the number of rows Dim rows : rows = Rhino.GetInteger("Number of rows", 2, 2) If IsNull(rows) Then Exit Sub rows = rows - 1 ' Get the number of columns Dim cols : cols = Rhino.GetInteger("Number of columns", 2, 2) If IsNull(cols) Then Exit Sub cols = cols - 1 ' Get the domain of the surface Dim U : U = Rhino.SurfaceDomain(srf, 0) Dim V : V = Rhino.SurfaceDomain(srf, 1) If Not IsArray(U) Or Not IsArray(V) Then Exit Sub ' Turn off redrawing (faster) Rhino.EnableRedraw vbFalse ' Add the points Dim i, j, t(1), pt, obj For i = 0 To rows t(0) = U(0) + (((U(1) - U(0)) / rows) * i) For j = 0 To cols t(1) = V(0) + (((V(1) - V(0)) / cols) * j) pt = Rhino.EvaluateSurface(srf, t) If IsArray(pt) Then obj = Rhino.AddPoint(pt) ' add the point Rhino.SelectObject obj ' select the point End If Next Next ' Turn on redrawing Rhino.EnableRedraw vbTrue End Sub ``` -------------------------------------------------------------------------------- # Array Utilities Source: https://developer.rhino3d.com/en/guides/rhinoscript/array-utilities/ This guide presents an array of VBScript Array utilities. ## Overview Arrays are a very useful and easy way of storing variables - and they're especially easy to use in VBScript. This is due to several factors: - VBScript is particularly liberal with any variable definition - that means that there is no strict defining of variables to a particular data type. - The data type is assigned automatically when the variable is loaded with a value. - It is even possible to mix data types within the same array. - It is also possible to define the arrays in different ways: - Create the array element by element. - Use the VBScript `Array` method. - Use the VBScript `Split` method. - It is even possible to create multi-dimensional arrays and to make them dynamic rather than static. But, VBScript falls short, compared to other programming languages, when it comes to providing tools for manipulating arrays. Some common array operations that VBScript does not provide methods for are: - Adding a new elements to an array. - Appending one array to the end of another array. - Inserting new elements at given positions in an array. - Removing elements from an array. - Removing duplicate items from an array. - Sorting the elements in an array. - Reverse the order of the elements in an array. For some of these tasks, RhinoScript does provide useful methods: - `CullDuplicateNumbers` - Remove duplicates from an array of numbers. - `CullDuplicatePoints` - Remove duplicates from an array of 3-D points. - `CullDuplicateStrings` - Remove duplicates from an array of strings. - `SortNumbers` - Sorts an array of numbers. - `SortPoints` - Sorts an array of 3-D points. - `SortStrings` - Sorts an array of strings. For the other tasks, you will need to write your own procedures. Fortunately, VBScript provides us enough tools to write our own procedures to do most of what we would ever want to do with an array. Note, ## Utilities The following examples are general purpose utilities. If you need something more specific, these examples are a good starting point. ### Add Elements At End Add a new element to the end of an array... ```vbnet Sub ArrayAdd(ByRef arr, ByVal val) Dim ub If IsArray(arr) Then On Error Resume Next ub = UBound(arr) If Err.Number <> 0 Then ub = -1 ReDim Preserve arr(ub + 1) arr(UBound(arr)) = val End If End Sub ``` ### Append Array Append another array to the end of an array... ```vbnet Sub ArrayAppend(ByRef arr, ByVal arr0) Dim i, ub If IsArray(arr) And IsArray(arr0) Then On Error Resume Next ub = UBound(arr) If Err.Number <> 0 Then ub = -1 ReDim Preserve arr(ub + UBound(arr0) + 1) For i = 0 To UBound(arr0) arr(ub + 1 + i) = arr0(i) Next End If End Sub ``` This example uses the .NET `ArrayList` object: ```vbnet Sub ArrayAppend(ByRef arr, ByVal arr0) Dim i, list If IsArray(arr) Then Set list = CreateObject("System.Collections.ArrayList") For i = 0 To UBound(arr) Call list.Add(arr(i)) Next For i = 0 To UBound(arr0) Call list.Add(arr0(i)) Next arr = list.ToArray() End If End Sub ``` ### Insert Elements Insert a new element at a given position in an array... ```vbnet Sub ArrayInsert(ByRef arr, ByVal pos, ByVal val) Dim i If IsArray(arr) Then If pos > UBound(arr) Then Call ArrayAdd(arr, val) ElseIf pos >= 0 Then ReDim Preserve arr(UBound(arr) + 1) For i = UBound(arr) To pos + 1 Step -1 arr(i) = arr(i - 1) Next arr(pos) = val End If End If End Sub ``` This example uses the .NET `ArrayList` object: ```vbnet Sub ArrayInsert(ByRef arr, ByVal pos, ByVal val) Dim i, list If IsArray(arr) Then Set list = CreateObject("System.Collections.ArrayList") For i = 0 To UBound(arr) Call list.Add(arr(i)) Next Call list.Insert(pos, val) arr = list.ToArray() End If End Sub ``` ### Remove Elements Remove an element from the end of an array... ```vbnet Sub ArrayRemove(ByRef arr) If IsArray(arr) Then If UBound(arr) > -1 Then ReDim Preserve arr(UBound(arr) - 1) End If End If End Sub ``` Remove an element at a given position from an array... ```vbnet Sub ArrayRemoveAt(ByRef arr, ByVal pos) Dim i If IsArray(arr) Then If pos >= 0 And pos <= UBound(arr) Then For i = pos To UBound(arr) - 1 arr(i) = arr(i + 1) Next ReDim Preserve arr(UBound(arr) - 1) End If End If End Sub ``` This example uses the .NET `ArrayList` object to remove an element from a given position in an array... ```vbnet Sub ArrayRemoveAt(ByRef arr, ByVal pos) Dim i, list If IsArray(arr) Then Set list = CreateObject("System.Collections.ArrayList") For i = 0 To UBound(arr) Call list.Add(arr(i)) Next Call list.RemoveAt(pos) arr = list.ToArray() End If End Sub ``` Remove all instances of a value from an array... ```vbnet Sub ArrayRemoveVal(ByRef arr, ByVal val) Dim i, j If IsArray(arr) Then i = 0 : j = -1 For i = 0 To UBound(arr) If arr(i) <> val Then j = j + 1 arr(j) = arr(i) End If Next ReDim Preserve arr(j) End If End Sub ``` Remove duplicate items from an array using VBScript's `Dictionary` object: ```vbnet Sub ArrayCull(ByRef arr) Dim i, dict If IsArray(arr) Then Set dict = CreateObject("Scripting.Dictionary") For i = 0 To UBound(arr) If Not dict.Exists(arr(i)) Then Call dict.Add(arr(i), arr(i)) End If Next arr = dict.Items End If End Sub ``` Remove duplicate items from an array using the .NET `ArrayList` object: ```vbnet Sub ArrayCull(ByRef arr) Dim i, list, tmp If IsArray(arr) Then Set list = CreateObject("System.Collections.ArrayList") For i = 0 To UBound(arr) If Not list.Contains(arr(i)) Then Call list.Add(arr(i)) End If Next arr = list.ToArray() End If End Sub ``` ### Find Elements Find the first occurance of a value in an array... ```vbnet Function ArraySearch(ByRef arr, ByVal val) Dim i ArraySearch = -1 If IsArray(arr) Then For i = 0 To UBound(arr) If arr(i) = val Then ArraySearch = i Exit Function End If Next End If End Function ``` ### Sort Elements This example uses a simple Bubble Sort algorithm... ```vbnet Sub ArraySort(ByRef arr) Dim i, j, tmp If IsArray(arr) Then For i = UBound(arrShort) - 1 To 0 Step -1 For j = 0 To i If arr(j) > arr(j + 1) Then tmp = arr(j + 1) arr(j + 1) = arr(j) arr(j) = tmp End If Next Next End If End Sub ``` This example uses the .NET `ArrayList` object... ```vbnet Sub ArraySort(ByRef arr) Dim i, list If IsArray(arr) Then Set list = CreateObject("System.Collections.ArrayList") For i = 0 To UBound(arr) Call list.Add(arr(i)) Next Call list.Sort() arr = list.ToArray() End If End Sub ``` Reverse the order of the elements in an array... ```vbnet Sub ArrayReverse(ByRef arr) Dim i, ub, ub2, tmp ub = UBound(arr) ub2 = Int(ub / 2) For i = 0 To ub2 tmp = arr(i) arr(i) = arr(ub - i) arr(ub - i) = tmp Next End Sub ``` This example uses the .NET `ArrayList` object to reverse the order of elements... ```vbnet Sub ArrayReverse(ByRef arr) Dim i, list If IsArray(arr) Then Set list = CreateObject("System.Collections.ArrayList") For i = 0 To UBound(arr) Call list.Add(arr(i)) Next Call list.Reverse() arr = list.ToArray() End If End Sub ``` ### Verify Dimmed Verifies that an array has been "dimmed"... ```vbnet Function IsArrayDim(ByVal arr) IsArrayDim = False If IsArray(arr) Then On Error Resume Next Call UBound(arr) If Err.Number = 0 Then IsArrayDim = True End If End Function ``` ### Count Dimensions Returns the number of dimensions to an array... ```vbnet Public Function GetArrayDim(ByVal arr) Dim i If IsArray(arr) Then For i = 1 To 60 On Error Resume Next Call UBound(arr, i) If Err.Number <> 0 Then GetArrayDim = i - 1 Exit Function End If Next GetArrayDim = i Else GetArrayDim = Null End If End Function ``` ### Print Contents Print the contents of an array... ```vbnet Sub ArrayPrint(ByVal arr) Dim i If IsArray(arr) Then For i = 0 To UBound(arr) Call Rhino.Print("Item(" & CStr(i) & ") = " & CStr(arr(i))) Next End If End Sub ``` -------------------------------------------------------------------------------- # Assign Flamingo Material Source: https://developer.rhino3d.com/en/samples/rhinoscript/assign-flamingo-material/ Demonstrates how to assign a Flamingo nXt material to an object using RhinoScript. ```vbnet Option Explicit Call Main() Sub Main() Dim objPlugIn, strObject, strMaterial On Error Resume Next Set objPlugIn = Rhino.GetPluginObject("8008880f-8d13-4b2d-92b0-727e12878a4c") If Err Then MsgBox Err.Description Exit Sub End If strMaterial = objPlugIn.ModalMaterialBrowser() If Not IsNull(strMaterial) And Not strMaterial = "" Then strObject = Rhino.GetObject("Select object") If Not IsNull(strObject) And Not strObject = "" Then If objPlugIn.SetMaterialId(strObject, strMaterial) Then Rhino.Print("Object assigned to material " + objPlugIn.GetMaterialName(strMaterial)) Else Rhino.Print("Error assigning material to object") End If End If End If Set objPlugIn = Nothing End Sub ``` -------------------------------------------------------------------------------- # Attaching User Data to Brep Components Source: https://developer.rhino3d.com/en/samples/cpp/attaching-user-data-to-brep-components/ Demonstrates how to attach object user data to components of a Brep. ```cpp CRhinoCommand::result CCommandTest::RunCommand( const CRhinoCommandContext& context) { // Allow for picking of either a surface or a brep face. CRhinoGetObject go; go.SetCommandPrompt( L"Select surface to attach data" ); go.SetGeometryFilter( CRhinoGetObject::surface_object ); go.EnableSubObjectSelect( TRUE ); go.GetObjects( 1, 1 ); if( go.CommandResult() != CRhinoCommand::success ) return go.CommandResult(); // Get first object reference. Note, this reference // reprsents the picked surface or face, not the // top level brep. const CRhinoObjRef& objref = go.Object(0); // Get face on brep that was picked const ON_BrepFace* face = objref.Face(); if( !face ) return failure; // Get the faces underlying surface const ON_Surface* srf = face->SurfaceOf(); if( !srf ) return failure; // Query surface for user data CTestUserData* ud = CTestUserData::Cast( srf->GetUserData(ud->m_uuid) ); if( ud ) { RhinoApp().Print( L"Data already attached.\n" ); return nothing; } // Get the top level object const CRhinoBrepObject* obj = CRhinoBrepObject::Cast( objref.Object() ); if( !obj ) return failure; // Duplicate the top level object. CRhinoBrepObject* dupe_obj = CRhinoBrepObject::Cast( obj->DuplicateObject() ); if( !dupe_obj ) return failure; // Get the brep ON_Brep* dupe_brep = dupe_obj->Brep(); if( !dupe_brep ) return failure; // Get the surface ON_Surface* dupe_srf = dupe_brep->m_S[face->SurfaceIndexOf()]; if( !dupe_srf ) return failure; // New up the user data ud = new CTestUserData(); if( !ud ) { delete dupe_obj; return failure; } // TODO: Initialize user data object here // Attach user data to surface, not face. Note, ud // is now owned by dupe_srf, not by us. So, we are // not responsible for deleting it; if( !dupe_srf->AttachUserData(ud) ) { delete ud; delete dupe_obj; return failure; } // Replace object. Note, we cannot reuse objref for it references // the picked face, not the top level brep. // Note, dupe_obj is now owned by Rhino, so we are not // responsible for deleting it. if( !context.m_doc.ReplaceObject(CRhinoObjRef(obj), dupe_obj) ) { delete dupe_obj; return failure; } // Done deal RhinoApp().Print( L"Data attached successfully.\n" ); context.m_doc.Redraw(); return CRhinoCommand::success; } ``` -------------------------------------------------------------------------------- # Auto Label Objects Source: https://developer.rhino3d.com/en/samples/rhinoscript/auto-label-objects/ Demonstrates how to automatically label objects using RhinoScript. ```vbnet Option Explicit ' ' AutoLabel Subroutine ' Sub AutoLabel ' Declare variables Dim arrDirPoint1, arrDirPoint2, arrSortDir, arrObjects, arrPoint, arrItem, arrArea, arrPrompts, arrCollect() Dim strObject, strName, strDot Dim intCount, intSuffix, intXDir, intYDir, intZDir Dim i, j, temp ' Select objects arrObjects = Rhino.GetObjects("Select objects to name") If Not IsArray(arrObjects) Then Exit Sub ' Prompt for a prefix to add to the labels strName = Rhino.GetString("Prefix for labels, press Enter for none") If Not IsString(strName) Then Exit Sub ' Prompt for a suffix starting number intSuffix = Rhino.GetInteger("Starting base number to increment",0) If IsNull(intSuffix) Then Exit Sub ' Prompt for direction arrDirPoint1 = Rhino.GetPoint("Base point for sort direction") If IsNull (arrDirPoint1) Then Exit Sub arrDirPoint2 = Rhino.GetPoint("Pick point for sort direction", arrDirPoint1) If IsNull (arrDirPoint2) Then Exit Sub ' Determine Direction of sort for each axis, 0 is Negative Direction, 1 is positive If arrDirPoint1(0) > arrDirPoint2(0) Then intXDir = 0 Else intXDir = 1 End If If arrDirPoint1(1) > arrDirPoint2(1) Then intYDir = 0 Else intYDir = 1 End If If arrDirPoint1(2) > arrDirPoint2(2) Then intZDir = 0 Else intZDir = 1 End If arrSortDir = Array(intXDir, intYDir, intZDir) ' Initialize collection counter intCount = 0 ' Process each seleted object For Each strObject In arrObjects ' Process curves If Rhino.IsCurve(strObject) Then ' Get the curve starting point arrPoint = Rhino.CurveStartPoint(strObject) ' Append the object name to the point array ReDim Preserve arrPoint(3) arrPoint(3) = strObject ' Append the modified point array to the collection ReDim Preserve arrCollect(intCount) arrCollect(intCount) = arrPoint ' Increment collection counter intCount = intCount + 1 End If ' Process surfaces If Rhino.IsSurface(strObject) Then ' Get the Surface center point arrArea = Rhino.SurfaceAreaCentroid (strObject) arrPoint = arrArea(0) ' Append the object name to the point array ReDim Preserve arrPoint(3) arrPoint(3) = strObject ' Append the modified point array to the collection ReDim Preserve arrCollect(intCount) arrCollect(intCount) = arrPoint ' Increment collection counter intCount = intCount + 1 End If ' Process points If Rhino.IsPoint(strObject) Then ' Get the Point Corrdinates point arrPoint = Rhino.PointCoordinates (strObject) ' Append the object name to the point array ReDim Preserve arrPoint(3) arrPoint(3) = strObject ' Append the modified point array to the collection ReDim Preserve arrCollect(intCount) arrCollect(intCount) = arrPoint ' Increment collection counter intCount = intCount + 1 End If ' ' TODO: add support for additional object types here ' Next ' Validate the collection If Not IsUpperBound(arrCollect) Then Exit Sub ' Bubble sort the collection For i = UBound(arrCollect) - 1 To 0 Step -1 For j = 0 To i If CompareItems(arrCollect(j), arrCollect(j+1), arrSortDir) = True Then temp = arrCollect(j+1) arrCollect(j+1) = arrCollect(j) arrCollect(j) = temp End If Next Next ' Process each item in the collection For i = 0 To UBound(arrCollect) ' Get an item from the collection arrItem = arrCollect(i) ' Rebuild the point array arrPoint = Array(arrItem(0), arrItem(1), arrItem(2)) 'Curves need to have the textdot at thier Midpoint If Rhino.IsCurve(arrItem(3)) Then arrPoint = Rhino.CurveMidPoint(arrItem(3)) End If ' Add a text dot at the point location strDot = Rhino.AddTextDot(strName & CStr(intSuffix + i), arrPoint) ' Set the dot name to the originating object Rhino.ObjectName strDot, arrItem(3) Rhino.ObjectName arrItem(3), strName & CStr(intSuffix +i) Rhino.SetObjectData arrItem(3), "AutoCount", "DotUiid", strDot Next End Sub ' ' Compare function for bubble sort ' Function CompareItems(x, y, dir) If x(0) > y(0) Then If dir(0) = 1 Then CompareItems = True Else CompareItems = False End If ElseIf x(0) = y(0) Then If x(1) > y(1) Then If dir(1) = 1 Then CompareItems = True Else CompareItems = False End If ElseIf x(1) = y(1) And x(2) >= y(2) Then If dir(2) = 1 Then CompareItems = True Else CompareItems = False End If Else If dir(1) = 1 Then CompareItems = False Else CompareItems = True End If End If Else If dir(0) = 1 Then CompareItems = False Else CompareItems = True End If End If End Function ' ' Returns a Boolean value indicating whether an ' expression can be evaluated as a string ' Function IsString(ByVal str) IsString = False If VarType(str) = vbString Then IsString = True End Function ' ' Returns a Boolean value indicating whether an ' array has been dimensioned. ' Function IsUpperBound(ByVal arr) IsUpperBound = False If IsArray(arr) Then On Error Resume Next UBound arr If Err.Number = 0 Then IsUpperBound = True End If End Function ``` -------------------------------------------------------------------------------- # Avoiding Buffer Overruns in String Functions Source: https://developer.rhino3d.com/en/guides/cpp/avoiding-buffer-overruns-in-string-functions/ This guide discusses how to write safe string function using C/C++. ## Overview Buffer overruns can be caused by passing buffers to functions without also passing the buffer's size. Consider the following function: ```cpp int GetName( wchar_t* pInput ) { wchar_t* pBuffer = (wchar_t*)malloc(100); wcscpy( pBuffer, pInput ); // might overrun buffer! wcscat( pBuffer, L".txt"); // also might overrun buffer! <...> } ``` ## A Safer Method Use the following techniques to write safer functions... Add a `size_t` argument for buffer size... ```cpp // Pass pointer to buffer and buffer size int GetName( wchar_t* buffer, size_t buffer_size ); // Ex: wchar_t buffer[100]; int rc = GetName( buffer, _countof(buffer) ); // Ex: const size_t kBufLen = 100; wchar_t* pBuffer = new wchar_t[kBufLen]; GetName( pBuffer, kBufLen ); <...> delete pBuffer; // Ex: const size_t kBufLen = 100; ON_wString strBuffer; strBuffer.ReserveArray( kBufLen ); GetName( strBuffer.Array(), kBufLen ); // Ex: const size_t kBufLen = 100; CString strBuffer; GetName( strBuffer.GetBuffer(kBufLen), kBufLen ); strBuffer.ReleaseBuffer(); ``` Change buffer argument to use a string object reference... ```cpp // Pass a reference to a ON_wString object int GetName( ON_wString& str ); // Pass a reference to a CString object int GetName( CString& str ); // Ex: ON_wString str; int rc = GetName( str ); // Ex: CString str; int rc = GetName( str ); ``` ...change buffer argument to a fixed size array reference... ```cpp // Pass a reference to a fixed size array int GetName( wchar_t(&buffer)[100] ); // Ex: wchar_t buffer[100]; int rc = GetName( buffer ); ``` Change buffer point argument to reference to a pointer... ```cpp // Pass a reference to a pointer // API allocates buffer, caller required to free it int GetName( wchar_t*& pBuffer ); // Ex: wchar_t* pBuffer = 0; int rc = GetName( pBuffer ); <...> delete pBuffer; ``` -------------------------------------------------------------------------------- # Basepoint of Block Instance Source: https://developer.rhino3d.com/en/samples/cpp/basepoint-of-block-instance/ Demonstrates how to find the basepoint coordinates of a block instance. ```cpp ON_3dPoint BlockInstanceInsertionPoint(const CRhinoInstanceObject* instance_obj) { ON_3dPoint pt = ON_UNSET_POINT; if (instance_obj != 0) { pt = ON_origin; pt.Transform(instance_obj->InstanceXform()); } return pt; } ``` -------------------------------------------------------------------------------- # Batch Convert AutoCAD Files Source: https://developer.rhino3d.com/en/samples/rhinoscript/batch-convert-autocad-files/ Demonstrates how to convert a folder of AutoCAD files to Rhino 3dm files using RhinoScript. ```vbnet ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' BatchConvertAutoCAD script for Rhinoceros ' Robert McNeel & Associates ' www.rhino3d.com ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Option Explicit ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' BatchConvertAutoCAD ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Sub BatchConvertAutoCAD() ' Make sure RhinoScript does not reinitialize when opening models, ' otherwise this script will only process one file. Rhino.Command "_-Options _RhinoScript _Reinitialize=_No _Enter _Enter", 0 ' Allow the user to interactively pick a folder Dim sFolder sFolder = Rhino.BrowseForFolder(, "Select folder to process", "Batch Convert AutoCAD") If VarType(sFolder) <> vbString Then Exit Sub ' Create a file system object Dim oFSO Set oFSO = CreateObject("Scripting.FileSystemObject") ' Get a folder object based on the selected folder Dim oFolder Set oFolder = oFSO.GetFolder(sFolder) ' Process the folder ProcessFolder oFSO, oFolder ' Release the objects Set oFolder = Nothing Set oFSO = Nothing ' Close the last file processed Rhino.DocumentModified False Rhino.Command "_-New _None", 0 End Sub ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' ProcessFolder ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Sub ProcessFolder(oFSO, oFolder) ' Process all .dwg files in the selected folder Dim oFile, strOpen, strSave For Each oFile In oFolder.Files If LCase(oFSO.GetExtensionName(oFile.Path)) = "dwg" Then strOpen = LCase(oFile.Path) strSave = LCase(Replace(strOpen, ".dwg", ".3dm", 1, -1, 1)) ProcessFile strOpen, strSave End If Next ' Comment out the following lines if you do not ' want to recursively process the selected folder. Dim oSubFolder For Each oSubFolder In oFolder.SubFolders ProcessFolder oFSO, oSubFolder Next End Sub ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' ProcessFile ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Sub ProcessFile(strOpen, strSave) Rhino.DocumentModified False Rhino.Command "_-Open " & Chr(34) & strOpen & Chr(34), 0 Rhino.Command "_-Zoom _All _Extents", 0 Rhino.Command "_-SetActiveView _Top", 0 Rhino.Command "_-Save " & Chr(34) & strSave & Chr(34), 0 End Sub ``` -------------------------------------------------------------------------------- # Batch Render Source: https://developer.rhino3d.com/en/samples/rhinoscript/batch-render/ Demonstrates how to recurse through a folder and render every Rhino file using RhinoScript. ```vbnet Option Explicit ' Run the subroutine Call BatchRender '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' BatchRender '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Sub BatchRender() ' Allow the user to interactively pick a folder Dim sFolder sFolder = Rhino.BrowseForFolder(, "Select folder to process", "Batch Render" ) If VarType( sFolder ) <> vbString Then Exit Sub ' Create a file system object Dim oFSO Set oFSO = CreateObject( "Scripting.FileSystemObject" ) ' Get a folder object based on the selected folder Dim oFolder Set oFolder = oFSO.GetFolder( sFolder ) ' Process the folder RecurseFolder oFolder ' Open an empty model Rhino.Command "_-New _None", 0 ' Release the objects Set oFolder = Nothing Set oFSO = Nothing End Sub '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' RecurseFolder '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Sub RecurseFolder( oFolder ) ' Process each file in the folder Dim oFile For Each oFile In oFolder.Files ProcessFile oFile.Path Next ' Remark out the following lines if you do not want ' to recursively process the folder ' Process each subfolder in this folder Dim oSubFolder For Each oSubFolder In oFolder.SubFolders RecurseFolder( oSubFolder ) Next End Sub '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' ProcessFile '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Sub ProcessFile( sFile ) ' Once we have gotten here, we have a valid file name. ' In this case, we are interested in just 3DM files. Dim sBitmap If (InStr(LCase(sFile), ".3dm") > 0) Then sBitmap = Replace(sFile, ".3dm", ".jpg", 1, -1, 1) Rhino.DocumentModified False Rhino.Command "_-Open " & Chr(34) & sFile & Chr(34), 0 Rhino.Command "_-Render", 0 Rhino.Command "_-SaveRenderWindowAs " & Chr(34) & sBitmap & Chr(34), 0 Rhino.Command "_-CloseRenderWindow", 0 Rhino.DocumentModified False End If End Sub ``` -------------------------------------------------------------------------------- # Batch Save Small Source: https://developer.rhino3d.com/en/samples/rhinoscript/batch-save-small/ Demonstrates how to recurse through a folder and save small every Rhino file using RhinoScript. ```vbnet ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' BatchSaveSmall.rvb -- October 2008 ' If this code works, it was written by Dale Fugier. ' If not, I don't know who wrote it. ' Works with Rhino 4.0. ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Option Explicit ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' Main subroutine ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Sub BatchSaveSmall() Dim sFolder, oFSO, oFolder sFolder = Rhino.BrowseForFolder(, "Select folder to recurse", "Batch SaveSmall") If IsNull(sFolder) Then Exit Sub Set oFSO = CreateObject("Scripting.FileSystemObject") Set oFolder = oFSO.GetFolder(sFolder) Call RecurseSaveSmall(oFolder) Call Rhino.DocumentModified(False) Call Rhino.Command("_-New _None", 0) Call Rhino.Print("Done!") End Sub ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' RecurseSaveSmall ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Sub RecurseSaveSmall(oFolder) Dim oFile, oSubFolder, dModified For Each oFile In oFolder.Files ' Save file modified date dModified = oFile.DateLastModified ' Do the "save small" Call DoSaveSmall(oFile.Path) ' Reset file modified date Call TouchDateModified(oFile.Path, dModified) Next ' If you want to recurse folders, ' just un-comment the following lines. 'For Each oSubFolder In oFolder.SubFolders ' Call RecurseSaveSmall(oSubFolder) 'Next End Sub ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' DoSaveSmall ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Sub DoSaveSmall(sPath) Dim aViews, sView If InStr(LCase(sPath), ".3dm") > 0 Then ' Open the file Call Rhino.DocumentModified(False) Call Rhino.Command("_-Open " & Chr(34) & sPath & Chr(34), 0) ' Set all of the viewports to wireframe before saving aViews = Rhino.ViewNames For Each sView In aViews Call Rhino.ViewDisplayMode(sView, 0) Next ' Save it Call Rhino.Command("_-SaveSmall _Enter", 0) End If End Sub ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' TouchDateModified ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Sub TouchDateModified(sPath, dModified) Dim oShell, oFolder, oFile Dim sDir, sFile, nPos nPos = InStrRev(sPath, "\") sDir = Left(sPath, nPos) sFile = Mid(sPath, nPos + 1) Set oShell = CreateObject("Shell.Application") Set oFolder = oShell.NameSpace(sDir) Set oFile = oFolder.ParseName(sFile) oFile.ModifyDate = dModified End Sub ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Rhino.AddStartupScript Rhino.LastLoadedScriptFile Rhino.AddAlias "BatchSaveSmall", "_NoEcho _-RunScript (BatchSaveSmall)" ``` -------------------------------------------------------------------------------- # Block Definition Geometry Source: https://developer.rhino3d.com/en/samples/cpp/block-definition-geometry/ Demonstrates how to obtain a block instance's definition geometry. ```cpp CRhinoCommand::result CCommandTest::RunCommand( const CRhinoCommandContext& context ) { CRhinoGetObject go; go.SetCommandPrompt(L"Select instance"); go.SetGeometryFilter(CRhinoGetObject::instance_reference); go.GetObjects(1, 1); if (go.CommandResult() != CRhinoCommand::success) return go.CommandResult(); const CRhinoInstanceObject* iref = CRhinoInstanceObject::Cast(go.Object(0).Object()); if (iref == 0) return CRhinoCommand::failure; const CRhinoInstanceDefinition* idef = iref->InstanceDefinition(); if (idef == 0) return CRhinoCommand::failure; ON_SimpleArray objects; int count = idef->GetObjects(objects); for (int i = 0; i < count; i++ ) { const CRhinoObject* obj = objects[i]; if (obj != 0) { ON_wString str; ON_UuidToString( obj->Attributes().m_uuid, str ); RhinoApp().Print(L"Object %d = %s\n", i, str); } } return CRhinoCommand::success; } ``` -------------------------------------------------------------------------------- # Block Insertion Point Source: https://developer.rhino3d.com/en/samples/rhinocommon/block-insertion-point/ Demonstrates how to set (or reset) the block insertion point of a block instance. ```cs partial class Examples { public static Rhino.Commands.Result BlockInsertionPoint(Rhino.RhinoDoc doc) { Rhino.DocObjects.ObjRef objref; Result rc = Rhino.Input.RhinoGet.GetOneObject("Select instance", true, Rhino.DocObjects.ObjectType.InstanceReference, out objref); if (rc != Rhino.Commands.Result.Success) return rc; Rhino.DocObjects.InstanceObject instance = objref.Object() as Rhino.DocObjects.InstanceObject; if (instance != null) { Rhino.Geometry.Point3d pt = instance.InsertionPoint; doc.Objects.AddPoint(pt); doc.Views.Redraw(); } return Rhino.Commands.Result.Success; } } ``` ```python import Rhino import scriptcontext def BlockInsertionPoint(): rc, objref = Rhino.Input.RhinoGet.GetOneObject("Select instance", True, Rhino.DocObjects.ObjectType.InstanceReference) if rc!=Rhino.Commands.Result.Success: return rc; instance = objref.Object() if instance: pt = instance.InsertionPoint scriptcontext.doc.Objects.AddPoint(pt) scriptcontext.doc.Views.Redraw() return Rhino.Commands.Result.Success return Rhino.Commands.Result.Failure if __name__=="__main__": BlockInsertionPoint() ``` -------------------------------------------------------------------------------- # Block Utilities Source: https://developer.rhino3d.com/en/samples/rhinoscript/block-utilities/ A couple of useful block utilities written in RhinoScript. ```vbnet ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' BlockUtilities.rvb -- November 2009 ' If this code works, it was written by Dale Fugier. ' If not, I don't know who wrote it. ' Works with Rhino 4.0. ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Option Explicit ' Script to update all linked blocks Sub UpdateAllLinkedBlocks() Call Rhino.Command("_-BlockManager _Update _All _Enter _Enter _Enter", 0) End Sub ' Script to insert multiple files as linked blocks Sub InsertMultipleFilesAsBlocks() Dim arrFiles, strFiles arrFiles = Rhino.OpenFileNames("Insert", "Rhino 3D Models (*.3dm)|*.3dm|All Files (*.*)|*.*|") If IsArray(arrFiles) Then For Each strFile In arrFiles Call Rhino.Command("_-Insert _File=_Yes " & strFile & " _Block _Pause _Enter _Enter", 0) Next End If End Sub ' Script to export select and then insert as linked block Sub Externalize() Dim arrObjects, arrPoint, strFile Dim arrCopy, strPoint arrObjects = Rhino.GetObjects("Select objects to define block") If IsNull(arrObjects) Then Exit Sub arrPoint = Rhino.GetPoint("Block base point") If IsNull(arrPoint) Then Exit Sub strFile = Rhino.SaveFileName("Save", "Rhino 3D Model (*.3dm)|*.3dm|") If IsNull(strFile) Then Exit Sub Call Rhino.EnableRedraw(False) arrCopy = Rhino.CopyObjects(arrObjects, arrPoint, Array(0,0,0)) Call Rhino.SelectObjects(arrCopy) Call Rhino.Command("_-Export " & Chr(34) & strFile & Chr(34), 0) strPoint = Rhino.Pt2Str(arrPoint) Call Rhino.Command("_-Insert _File=_Yes " & strFile & " _Block " & strPoint & " _Enter _Enter", 0) Call Rhino.DeleteObjects(arrCopy) Call Rhino.DeleteObjects(arrObjects) Call Rhino.EnableRedraw(True) End Sub ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Rhino.AddStartupScript Rhino.LastLoadedScriptFile Rhino.AddAlias "UpdateAllLinkedBlocks", "_NoEcho _-RunScript (UpdateAllLinkedBlocks)" Rhino.AddAlias "InsertMultipleFilesAsBlocks", "_NoEcho _-RunScript (InsertMultipleFilesAsBlocks)" Rhino.AddAlias "Externalize", "_NoEcho _-RunScript (Externalize)" ``` -------------------------------------------------------------------------------- # Boolean Difference Source: https://developer.rhino3d.com/en/samples/cpp/boolean-difference/ Demonstrates how to perform a Boolean Difference operation on two sets of polysurfaces. ```cpp CRhinoCommand::result CCommandTest::RunCommand( const CRhinoCommandContext& context ) { CRhinoGetObject go; go.SetCommandPrompt( L"Select first set of polysurfaces" ); go.SetGeometryFilter( CRhinoGetObject::polysrf_object ); go.EnablePreSelect( TRUE ); go.GetObjects( 1, 0 ); if( success != go.CommandResult() ) return go.CommandResult(); ON_SimpleArray InBreps0( go.ObjectCount() ); int i; for( i = 0; i < go.ObjectCount(); i++ ) { const ON_Brep* brep = go.Object(i).Brep(); if( brep ) InBreps0.Append( brep ); } go.SetCommandPrompt( L"Select second set of polysurfaces" ); go.EnablePreSelect( FALSE ); go.EnableDeselectAllBeforePostSelect( false ); go.GetObjects( 1, 0 ); if( success != go.CommandResult() ) return go.CommandResult(); ON_SimpleArray InBreps1( go.ObjectCount() ); for( i = 0; i < go.ObjectCount(); i++ ) { const ON_Brep* brep = go.Object(i).Brep(); if( brep ) InBreps1.Append( brep ); } ON_SimpleArray OutBreps; ON_SimpleArray InputIndexForOutput; bool something_happened = false; double tolerance = context.m_doc.AbsoluteTolerance(); bool rc = RhinoBooleanDifference( InBreps0, InBreps1, tolerance, &something_happened, OutBreps, InputIndexForOutput ); if( !rc | !something_happened ) { for( i = 0; i < OutBreps.Count(); i++ ) { delete OutBreps[i]; OutBreps[i] = 0; } return nothing; } for( i = 0; i < OutBreps.Count(); i++ ) { ON_Brep* brep = OutBreps[i]; if( brep ) { context.m_doc.AddBrepObject( *brep ); brep = 0; delete OutBreps[i]; OutBreps[i] = 0; } } context.m_doc.Redraw(); return success; } ``` -------------------------------------------------------------------------------- # Boolean Difference Source: https://developer.rhino3d.com/en/samples/rhinocommon/boolean-difference/ Demonstrates how to perform a boolean difference operation on two selected polysurfaces. ```cs partial class Examples { public static Rhino.Commands.Result BooleanDifference(Rhino.RhinoDoc doc) { Rhino.DocObjects.ObjRef[] objrefs; Result rc = Rhino.Input.RhinoGet.GetMultipleObjects("Select first set of polysurfaces", false, Rhino.DocObjects.ObjectType.PolysrfFilter, out objrefs); if (rc != Rhino.Commands.Result.Success) return rc; if (objrefs == null || objrefs.Length < 1) return Rhino.Commands.Result.Failure; List in_breps0 = new List(); for (int i = 0; i < objrefs.Length; i++) { Rhino.Geometry.Brep brep = objrefs[i].Brep(); if (brep != null) in_breps0.Add(brep); } doc.Objects.UnselectAll(); rc = Rhino.Input.RhinoGet.GetMultipleObjects("Select second set of polysurfaces", false, Rhino.DocObjects.ObjectType.PolysrfFilter, out objrefs); if (rc != Rhino.Commands.Result.Success) return rc; if (objrefs == null || objrefs.Length < 1) return Rhino.Commands.Result.Failure; List in_breps1 = new List(); for (int i = 0; i < objrefs.Length; i++) { Rhino.Geometry.Brep brep = objrefs[i].Brep(); if (brep != null) in_breps1.Add(brep); } double tolerance = doc.ModelAbsoluteTolerance; Rhino.Geometry.Brep[] breps = Rhino.Geometry.Brep.CreateBooleanDifference(in_breps0, in_breps1, tolerance); if (breps.Length < 1) return Rhino.Commands.Result.Nothing; for (int i = 0; i < breps.Length; i++) doc.Objects.AddBrep(breps[i]); doc.Views.Redraw(); return Rhino.Commands.Result.Success; } } ``` ```python import Rhino import scriptcontext def BooleanDifference(): filter = Rhino.DocObjects.ObjectType.PolysrfFilter rc, objrefs = Rhino.Input.RhinoGet.GetMultipleObjects("Select first set of polysurfaces", False, filter) if rc != Rhino.Commands.Result.Success: return rc if not objrefs: return Rhino.Commands.Result.Failure in_breps0 = [] for objref in objrefs: brep = objref.Brep() if brep: in_breps0.append(brep) scriptcontext.doc.Objects.UnselectAll() rc, objrefs = Rhino.Input.RhinoGet.GetMultipleObjects("Select second set of polysurfaces", False, filter) if rc != Rhino.Commands.Result.Success: return rc if not objrefs: return Rhino.Commands.Result.Failure in_breps1 = [] for objref in objrefs: brep = objref.Brep() if brep: in_breps1.append(brep) tolerance = scriptcontext.doc.ModelAbsoluteTolerance breps = Rhino.Geometry.Brep.CreateBooleanDifference(in_breps0, in_breps1, tolerance) if not breps: return Rhino.Commands.Result.Nothing for brep in breps: scriptcontext.doc.Objects.AddBrep(brep) scriptcontext.doc.Views.Redraw() return Rhino.Commands.Result.Success if __name__=="__main__": BooleanDifference() ``` -------------------------------------------------------------------------------- # Box Shell Source: https://developer.rhino3d.com/en/samples/rhinocommon/box-shell/ Demonstrates how to give thickness to (or shell) a Brep box. ```cs partial class Examples { public static Rhino.Commands.Result BoxShell(Rhino.RhinoDoc doc) { Rhino.Geometry.Box box; Rhino.Commands.Result rc = Rhino.Input.RhinoGet.GetBox(out box); if (rc == Rhino.Commands.Result.Success) { Rhino.Geometry.Brep brep = Rhino.Geometry.Brep.CreateFromBox(box); if (null != brep) { System.Collections.Generic.List facesToRemove = new System.Collections.Generic.List(1); facesToRemove.Add(0); Rhino.Geometry.Brep[] shells = Rhino.Geometry.Brep.CreateShell(brep, facesToRemove, 1.0, doc.ModelAbsoluteTolerance); if (null != shells) { for (int i = 0; i < shells.Length; i++) doc.Objects.AddBrep(shells[i]); doc.Views.Redraw(); } } } return rc; } } ``` ```python # No Python sample available ``` -------------------------------------------------------------------------------- # Brep from Curve Bounding Box Source: https://developer.rhino3d.com/en/samples/rhinocommon/brep-from-curve-bounding-box/ Demonstrates how to create a valid Brep from a curve's bounding box. ```cs partial class Examples { public static Result BrepFromCurveBBox(RhinoDoc doc) { Rhino.DocObjects.ObjRef objref; var rc = RhinoGet.GetOneObject("Select Curve", false, ObjectType.Curve, out objref); if( rc != Result.Success ) return rc; var curve = objref.Curve(); var view = doc.Views.ActiveView; var plane = view.ActiveViewport.ConstructionPlane(); // Create a construction plane aligned bounding box var bbox = curve.GetBoundingBox(plane); if (bbox.IsDegenerate(doc.ModelAbsoluteTolerance) > 0) { RhinoApp.WriteLine("the curve's bounding box is degenerate (flat) in at least one direction so a box cannot be created."); return Result.Failure; } var brep = Brep.CreateFromBox(bbox); doc.Objects.AddBrep(brep); doc.Views.Redraw(); return Result.Success; } } ``` ```python import Rhino from Rhino.Geometry import Brep from Rhino.Commands import Result from Rhino.Input import RhinoGet from Rhino.DocObjects import ObjectType import rhinoscriptsyntax as rs from scriptcontext import doc def RunCommand(): rc, objRef = RhinoGet.GetOneObject("Select curve", False, ObjectType.Curve) if rc != Result.Success: return rc curve = objRef.Curve() if None == curve: return Result.Failure view = doc.Views.ActiveView plane = view.ActiveViewport.ConstructionPlane() # Create a construction plane aligned bounding box bbox = curve.GetBoundingBox(plane) if bbox.IsDegenerate(doc.ModelAbsoluteTolerance) > 0: print("the curve's bounding box is degenerate (flat) in at least one direction so a box cannot be created.") return Result.Failure brep = Brep.CreateFromBox(bbox) doc.Objects.AddBrep(brep) doc.Views.Redraw() return Result.Success if __name__ == "__main__": print(RunCommand()) ``` -------------------------------------------------------------------------------- # Brep Loop & Edge Directions Source: https://developer.rhino3d.com/en/guides/opennurbs/brep-loop-edge-directions/ This guide discusses Brep loop end edge directions in the openNURBS toolkit. ## Question Is there a function to query if a loop `ON_BrepLoop` is reversed on the face `ON_BrepFace`? In other words, whether the boundary of the face agrees with or opposes that of the corresponding loop? Also, is there a way to query if the edge `ON_BrepEdge` direction is reversed? Or, whether an edge curve agrees with the start and end vertices? ## Answer Loops are always oriented so that the active region of the face is to the left of the 2D curve. Thus, outer loops are oriented counter-clockwise and inner loops are oriented clockwise. Also, to determine whether or not an edge is reversed, use `ON_BrepEdge::ProxyCurveIsReversed()`. See *opennurbs_curveproxy.h* for details. -------------------------------------------------------------------------------- # ByRef vs ByVal Source: https://developer.rhino3d.com/en/guides/rhinoscript/byref-vs-byval/ This guide discusses VBScript argument passing. ## Overview There has always been confusion about what exactly `ByRef` and `ByVal` mean in VBScript. The confusion arises because VBScript uses “by reference” to mean two similar, but different things. VBScript supports: 1. Reference types 1. Variable references The best way to illustrate the difference is with an example. Consider this class: ```vbnet Class Foo Public Bar End Class ``` Now we can create an instance of this class: ```vbnet Dim Blah, Baz Set Blah = New Foo Set Baz = Blah Blah.Bar = 123 ``` Both `Blah` and `Baz` are references to the same object. The fourth line changes both `Blah.Bar` and `Baz.Bar` because these are different names for the same thing. That's the “reference type” feature. We say that VBScript treats objects as reference types. Now consider this little program: ```vbnet Sub Change(ByRef XYZ) XYZ = 5 End Sub Dim ABC ABC = 123 Change ABC ``` This passes a reference to variable `ABC`. The local variable `XYZ` becomes an alias for `ABC`, so the assignment `XYZ = 5` changes `ABC` as well. ## Related Topics - [Parentheses](/guides/rhinoscript/parentheses) -------------------------------------------------------------------------------- # Calculate Curve Intersections Source: https://developer.rhino3d.com/en/samples/cpp/calculate-curve-intersections/ Demonstrates how to calculate the intersection of two curves and obtain their intersection points. ```cpp CRhinoCommand::result CCommandTest::RunCommand( const CRhinoCommandContext& context ) { // Select two curves to intersect CRhinoGetObject go; go.SetCommandPrompt( L"Select two curves" ); go.SetGeometryFilter( ON::curve_object ); go.GetObjects( 2, 2 ); if( go.CommandResult() != CRhinoCommand::success ) return go.CommandResult(); // Validate input const ON_Curve* curveA = go.Object(0).Curve(); const ON_Curve* curveB = go.Object(1).Curve(); if( 0 == curveA | 0 == curveB ) return CRhinoCommand::failure; // Calculate the intersection double intersection_tolerance = 0.001; double overlap_tolerance = 0.0; ON_SimpleArray events; int count = curveA->IntersectCurve( curveB, events, intersection_tolerance, overlap_tolerance ); // Process the results if( count > 0 ) { int i; for( i = 0; i < events.Count(); i++ ) { const ON_X_EVENT& e = events[i]; context.m_doc.AddPointObject( e.m_A[0] ); if( e.m_A[0].DistanceTo(e.m_B[0]) > ON_EPSILON ) { context.m_doc.AddPointObject( e.m_B[0] ); context.m_doc.AddCurveObject( ON_Line(e.m_A[0], e.m_B[0]) ); } } context.m_doc.Redraw(); } return CRhinoCommand::success; } ``` -------------------------------------------------------------------------------- # Calculate Curve Intersections Source: https://developer.rhino3d.com/en/samples/rhinocommon/calculate-curve-intersections/ Demonstrates how to calculate the intersection points between two user-specified curves. ```cs partial class Examples { public static Rhino.Commands.Result IntersectCurves(Rhino.RhinoDoc doc) { // Select two curves to intersect var go = new Rhino.Input.Custom.GetObject(); go.SetCommandPrompt("Select two curves"); go.GeometryFilter = Rhino.DocObjects.ObjectType.Curve; go.GetMultiple(2, 2); if (go.CommandResult() != Rhino.Commands.Result.Success) return go.CommandResult(); // Validate input var curveA = go.Object(0).Curve(); var curveB = go.Object(1).Curve(); if (curveA == null || curveB == null) return Rhino.Commands.Result.Failure; // Calculate the intersection const double intersection_tolerance = 0.001; const double overlap_tolerance = 0.0; var events = Rhino.Geometry.Intersect.Intersection.CurveCurve(curveA, curveB, intersection_tolerance, overlap_tolerance); // Process the results if (events != null) { for (int i = 0; i < events.Count; i++) { var ccx_event = events[i]; doc.Objects.AddPoint(ccx_event.PointA); if (ccx_event.PointA.DistanceTo(ccx_event.PointB) > double.Epsilon) { doc.Objects.AddPoint(ccx_event.PointB); doc.Objects.AddLine(ccx_event.PointA, ccx_event.PointB); } } doc.Views.Redraw(); } return Rhino.Commands.Result.Success; } } ``` ```python import Rhino import scriptcontext def IntersectCurves(): # Select two curves to intersect go = Rhino.Input.Custom.GetObject() go.SetCommandPrompt("Select two curves") go.GeometryFilter = Rhino.DocObjects.ObjectType.Curve go.GetMultiple(2, 2) if go.CommandResult()!=Rhino.Commands.Result.Success: return # Validate input curveA = go.Object(0).Curve() curveB = go.Object(1).Curve() if not curveA or not curveB: return # Calculate the intersection intersection_tolerance = 0.001 overlap_tolerance = 0.0 events = Rhino.Geometry.Intersect.Intersection.CurveCurve(curveA, curveB, intersection_tolerance, overlap_tolerance) # Process the results if not events: return for ccx_event in events: scriptcontext.doc.Objects.AddPoint(ccx_event.PointA) if ccx_event.PointA.DistanceTo(ccx_event.PointB) > float.Epsilon: scriptcontext.doc.Objects.AddPoint(ccx_event.PointB) scriptcontext.doc.Objects.AddLine(ccx_event.PointA, ccx_event.PointB) scriptcontext.doc.Views.Redraw() if __name__=="__main__": IntersectCurves() ``` -------------------------------------------------------------------------------- # Calculate Mesh Volume Source: https://developer.rhino3d.com/en/samples/cpp/calculate-mesh-volume/ Demonstrates how to calculate the volumes of mesh objects. ```cpp CRhinoCommand::result CCommandTest::RunCommand( const CRhinoCommandContext& context ) { CRhinoGetObject go; go.SetCommandPrompt( L"Select solid meshes for volume calculation" ); go.SetGeometryFilter( CRhinoGetObject::mesh_object ); go.SetGeometryAttributeFilter( CRhinoGetObject::closed_mesh ); go.EnableSubObjectSelect( FALSE ); go.EnableGroupSelect(); go.GetObjects( 1, 0 ); if( go.CommandResult() != CRhinoCommand::success ) return go.CommandResult(); int i; ON_SimpleArray meshes; for( i = 0; i < go.ObjectCount(); i++ ) { const ON_Mesh* mesh = go.Object(i).Mesh(); if( mesh ) meshes.Append( mesh ); } const int mesh_count = meshes.Count(); if( 0 == mesh_count ) return nothing; ON_BoundingBox bbox; for( i = 0; i < mesh_count; i++ ) meshes[i]->GetBoundingBox( bbox, TRUE ); ON_3dPoint base_point = bbox.Center(); double total_volume = 0.0; double total_error_estimate = 0.0; for( i = 0; i < mesh_count; i++ ) { double error_estimate = 0.0; double volume = meshes[i]->Volume( base_point, &error_estimate ); RhinoApp().Print( L"Mesh%d = %f (+/- %f)\n", i, volume, error_estimate ); total_volume += volume; total_error_estimate += error_estimate; } RhinoApp().Print( L"Total volume = %f (+/- %f)\n", total_volume, total_error_estimate ); return CRhinoCommand::success; } ``` -------------------------------------------------------------------------------- # Calculate Radius of Curvature Source: https://developer.rhino3d.com/en/samples/cpp/calculate-radius-of-curvature/ Demonstrates how to compute the radius of curvature of a curve object at a selected location. ```cpp CRhinoCommand::result CCommandTest::RunCommand( const CRhinoCommandContext& context ) { CRhinoGetObject go; go.SetCommandPrompt( L"Select curve for curvature measurement" ); go.SetGeometryFilter( CRhinoGetObject::curve_object ); go.GetObjects( 1, 1 ); if( go.CommandResult() != success ) return go.CommandResult(); const ON_Curve* crv = go.Object(0).Curve(); if( 0 == crv ) return failure; CRhinoGetPoint gp; gp.SetCommandPrompt( L"Select point on curve for curvature measurement" ); gp.Constrain( *crv ); gp.GetPoint(); if( gp.CommandResult() != success ) return gp.CommandResult(); ON_3dPoint pt = gp.Point(); double t = 0.0; if( !crv->GetClosestPoint(pt, &t) ) { RhinoApp().Print( L"Failed to compute radius of curvature.\n" ); return failure; } ON_3dVector tangent = crv->TangentAt( t ); if( tangent.IsTiny() ) { RhinoApp().Print( L"Failed to compute radius of curvature. Curve may have stacked control points.\n" ); return failure; } ON_3dVector curvature = crv->CurvatureAt( t ); const double k = curvature.Length(); if( k < ON_SQRT_EPSILON ) { RhinoApp().Print( L"Radius of curvature: infinite.\n" ); return failure; } ON_3dVector radius_vector = curvature / (k * k); ON_Circle circle; if ( !circle.Create(pt, tangent, pt + 2.0 * radius_vector) ) { RhinoApp().Print( L"Failed to compute radius of curvature.\n" ); return failure; } context.m_doc.AddCurveObject( circle ); context.m_doc.AddPointObject( pt ); context.m_doc.Redraw(); ON_wString wRadius; RhinoFormatNumber( circle.Radius(), wRadius ); RhinoApp().Print( L"Radius of curvature: %s.\n", wRadius ); return success; } ``` -------------------------------------------------------------------------------- # Calculate Solid Volumes Source: https://developer.rhino3d.com/en/samples/cpp/calculate-solid-volumes/ Demonstrates how to calculating the volumes of closed surface, polysurface, and mesh objects. ```cpp CRhinoCommand::result CCommandTest::RunCommand( const CRhinoCommandContext& context ) { CRhinoGetObject go; go.SetCommandPrompt( L"Select solids for volume calculation" ); go.SetGeometryFilter( CRhinoGetObject::surface_object | CRhinoGetObject::polysrf_object | CRhinoGetObject::mesh_object ); go.SetGeometryAttributeFilter( CRhinoGetObject::closed_surface | CRhinoGetObject::closed_polysrf | CRhinoGetObject::closed_mesh ); go.EnableSubObjectSelect( false ); go.EnableGroupSelect( true ); go.GetObjects( 1, 0 ); if( go.CommandResult() != success ) return go.CommandResult(); ON_SimpleArray geom( go.ObjectCount() ); int i; for( i = 0; i < go.ObjectCount(); i++ ) { const ON_Geometry* geo = go.Object(i).Geometry(); if( 0 == geo ) return failure; geom.Append( geo ); } // Get bounding box of all objects ON_BoundingBox bbox; for( i = 0; i < geom.Count(); i++ ) geom[i]->GetBoundingBox( bbox, bbox.IsValid() ); ON_3dPoint base_point = bbox.Center(); ON_SimpleArray MassProp; MassProp.Reserve( geom.Count() ); for( i = 0; i < geom.Count(); i++ ) { ON_MassProperties* mp = &MassProp.AppendNew(); if( const ON_Surface* srf = ON_Surface::Cast(geom[i]) ) srf->VolumeMassProperties( *mp, true, false, false, false, base_point ); else if( const ON_Brep* brep = ON_Brep::Cast(geom[i]) ) brep->VolumeMassProperties( *mp, true, false, false, false, base_point ); else if( const ON_Mesh* mesh = ON_Mesh::Cast(geom[i]) ) mesh->VolumeMassProperties( *mp, true, false, false, false, base_point ); } ON_MassProperties results; results.Sum( MassProp.Count(), MassProp.Array() ); RhinoApp().Print( L"Volume = %g\n", results.Volume() ); return success; } ``` -------------------------------------------------------------------------------- # Calculate Surface Curvature Source: https://developer.rhino3d.com/en/samples/rhinocommon/calculate-surface-curvature/ Demonstrates how to calculate the principle curvature at a user-specified point on a surface. ```cs partial class Examples { public static Result PrincipalCurvature(RhinoDoc doc) { ObjRef obj_ref; var rc = RhinoGet.GetOneObject("Select surface for curvature measurement", true, ObjectType.Surface, out obj_ref); if (rc != Result.Success) return rc; var surface = obj_ref.Surface(); var gp = new Rhino.Input.Custom.GetPoint(); gp.SetCommandPrompt("Select point on surface for curvature measurement"); gp.Constrain(surface, false); gp.Get(); if (gp.CommandResult() != Result.Success) return gp.CommandResult(); var point_on_surface = gp.Point(); double u, v; if (!surface.ClosestPoint(point_on_surface, out u, out v)) return Result.Failure; var surface_curvature = surface.CurvatureAt(u, v); if (surface_curvature == null) return Result.Failure; RhinoApp.WriteLine("Surface curvature evaluation at parameter: ({0}, {1})", u, v); RhinoApp.WriteLine(" 3-D Point: ({0}, {1}, {2})", surface_curvature.Point.X, surface_curvature.Point.Y, surface_curvature.Point.Z); RhinoApp.WriteLine(" 3-D Normal: ({0}, {1}, {2})", surface_curvature.Normal.X, surface_curvature.Normal.Y, surface_curvature.Normal.Z); RhinoApp.WriteLine(string.Format(" Maximum principal curvature: {0} ({1}, {2}, {3})", surface_curvature.Kappa(0), surface_curvature.Direction(0).X, surface_curvature.Direction(0).Y, surface_curvature.Direction(0).Z)); RhinoApp.WriteLine(string.Format(" Minimum principal curvature: {0} ({1}, {2}, {3})", surface_curvature.Kappa(1), surface_curvature.Direction(1).X, surface_curvature.Direction(1).Y, surface_curvature.Direction(1).Z)); RhinoApp.WriteLine(" Gaussian curvature: {0}", surface_curvature.Gaussian); RhinoApp.WriteLine(" Mean curvature: {0}", surface_curvature.Mean); return Result.Success; } } ``` ```python import rhinoscriptsyntax as rs surface_id,_,_,_,_,_ = rs.GetSurfaceObject("Select surface for curvature measurement") point = rs.GetPointOnSurface(surface_id, "Select point on surface for curvature measurement") u,v = rs.SurfaceClosestPoint(surface_id, point) #point, normal, kappa_u, direction_u, kappa_v, direction_v, gaussian, mean = surface_curvature = rs.SurfaceCurvature(surface_id, (u,v)) point, normal, kappa_u, direction_u, kappa_v, direction_v, gaussian, mean = surface_curvature print("Surface curvature evaluation at parameter: ({0}, {1})".format(u,v)) print(" 3-D Point: ({0}, {1}, {2})".format(point.X, point.Y, point.Z)) print(" 3-D Normal: ({0}, {1}, {2})".format(normal.X, normal.Y, normal.Z)) print(" Maximum principal curvature: {0} ({1}, {2}, {3})".format(kappa_u, direction_u.X, direction_u.Y, direction_u.Z)) print(" Minimum principal curvature: {0} ({1}, {2}, {3})".format(kappa_v, direction_v.X, direction_v.Y, direction_v.Z)) print(" Gaussian curvature: {0}".format(gaussian)) print(" Mean curvature: {0}".format(mean)) ``` -------------------------------------------------------------------------------- # Calculate the Angle Between Two Vectors Source: https://developer.rhino3d.com/en/samples/cpp/calculate-the-angle-between-two-vectors/ Demonstrates how to calculate the angle between two vectors. ```cpp // Description: Calculates the angle between two 3-D vectors. // Parameters: // v0 - [in] The first angle. // v1 - [in] The second angle. // reflex_angle - [out] The reflex angle. // Returns: The angle in radians. double ON_3dVectorAngle( ON_3dVector v0, ON_3dVector v1, double* reflex_angle = 0 ) { // Unitize the input vectors v0.Unitize(); v1.Unitize(); double dot = ON_DotProduct( v0, v1 ); // Force the dot product of the two input vectors to // fall within the domain for inverse cosine, which // is -1 <= x <= 1. This will prevent runtime // "domain error" math exceptions. dot = ( dot < -1.0 ? -1.0 : ( dot > 1.0 ? 1.0 : dot ) ); double angle = acos( dot ); if( reflex_angle ) *reflex_angle = (ON_PI * 2) - angle; return angle; } ``` -------------------------------------------------------------------------------- # Calculate the Angle Between Two Vectors Source: https://developer.rhino3d.com/en/samples/rhinoscript/calculate-the-angle-between-two-vectors/ Demonstrates how to calculate the angle between two vectors using RhinoScript. ```vbnet ' Description: ' Calculates the angle between two 3-D vectors. ' Parameters: ' v0 - [in] - the first vector. ' v1 - [in] - the second vector. ' Returns: ' the angle in degrees. Function VectorAngle(v0, v1) Dim u0 : u0 = Rhino.VectorUnitize(v0) Dim u1 : u1 = Rhino.VectorUnitize(v1) Dim dot : dot = Rhino.VectorDotProduct(u0, u1) ' Force the dot product of the two input vectors to ' fall within the domain for inverse cosine, which ' is -1 <= x <= 1. This will prevent runtime ' "domain error" math exceptions. If (dot < -1.0) Then dot = -1.0 ElseIf (dot > 1.0) Then dot = 1.0 End If VectorAngle = Rhino.ToDegrees(Rhino.ACos(dot)) End Function ``` -------------------------------------------------------------------------------- # Calculate Volume Centroid of Solids Source: https://developer.rhino3d.com/en/samples/cpp/calculate-volume-centroid-of-solids/ http://wiki.mcneel.com/developer/sdksamples/volumecentroid ```cpp CRhinoCommand::result CCommandTest::RunCommand( const CRhinoCommandContext& context ) { CRhinoGetObject go; go.SetCommandPrompt( L"Select solids for volume centroid calculation" ); go.SetGeometryFilter( CRhinoGetObject::surface_object | CRhinoGetObject::polysrf_object ); go.SetGeometryAttributeFilter( CRhinoGetObject::closed_surface | CRhinoGetObject::closed_polysrf ); go.EnableSubObjectSelect( false ); go.EnableGroupSelect( true ); go.GetObjects( 1, 0 ); if( go.CommandResult() != success ) return go.CommandResult(); ON_SimpleArray geom( go.ObjectCount() ); int i; for( i = 0; i < go.ObjectCount(); i++ ) { const ON_Geometry* geo = go.Object(i).Geometry(); if( 0 == geo ) return failure; geom.Append( geo ); } // Get bounding box of all objects ON_BoundingBox bbox; for( i = 0; i < geom.Count(); i++ ) geom[i]->GetBoundingBox( bbox, bbox.IsValid() ); ON_3dPoint base_point = bbox.Center(); ON_SimpleArray MassProp; MassProp.Reserve( geom.Count() ); for( i = 0; i < geom.Count(); i++ ) { ON_MassProperties* mp = &MassProp.AppendNew(); if( const ON_Surface* srf = ON_Surface::Cast(geom[i]) ) srf->VolumeMassProperties( *mp, true, true, false, false, base_point ); else if( const ON_Brep* brep = ON_Brep::Cast(geom[i]) ) brep->VolumeMassProperties( *mp, true, true, false, false, base_point ); } ON_MassProperties results; results.Sum( MassProp.Count(), MassProp.Array() ); ON_3dPoint pt( results.m_x0, results.m_y0, results.m_z0 ); context.m_doc.AddPointObject( pt ); context.m_doc.Redraw(); RhinoApp().Print( L"Volume centroid = %g,%g,%g\n", pt.x, pt.y, pt.z ); return success; } ``` -------------------------------------------------------------------------------- # Calculating Partial Lengths of Curves Source: https://developer.rhino3d.com/en/samples/cpp/calculate-partial-lengths-of-curves/ Demonstrates how to calculate the length of a curve from the start point to some point on the curve. ```cpp CRhinoCommand::result CCommandTest::RunCommand( const CRhinoCommandContext& context ) { CRhinoGetObject go; go.SetCommandPrompt( L"Select curve" ); go.SetGeometryFilter( CRhinoGetObject::curve_object ); go.GetObjects( 1, 1 ); if( go.CommandResult() != CRhinoCommand::success ) return go.CommandResult(); const ON_Curve* crv = go.Object(0).Curve(); if( 0 == crv ) return CRhinoCommand::failure; CRhinoGetPoint gp; gp.SetCommandPrompt( L"Point on surface" ); gp.Constrain( *crv ); gp.GetPoint(); if( gp.CommandResult() != CRhinoCommand::success ) return gp.CommandResult(); ON_3dPoint pt = gp.Point(); double t = 0.0; if( crv->GetClosestPoint(pt, &t) ) { ON_Interval domain = crv->Domain(); ON_Interval sub_domain( domain.Min(), t ); double length = 0.0; if( crv->GetLength(&length, 0.0, &sub_domain) ) RhinoApp().Print( L"Distance from start of curve = %f.\n", length ); } return CRhinoCommand::success; } ``` -------------------------------------------------------------------------------- # Calculating Permutations Source: https://developer.rhino3d.com/en/guides/rhinoscript/calculating-permutations/ This guide discusses how to calculate permutations using RhinoScript. ## Overview The permutation of a set is the number of ways that the items in the set can be uniquely ordered. For example, the permutations of the set $$\{1, 2, 3\}$$ are: $$\{1, 2, 3\}$$, $$\{1, 3, 2\}$$, $$\{2, 1, 3\}$$, $$\{2, 3, 1\}$$, $$\{3, 1, 2\}$$, and $$\{3, 2, 1\}$$. For $$N$$ objects, the number of permutations is $$N$$ (N factorial, or $$1 * 2 * 3 * N$$). ## Example There are a number of methods for calculating permutation sets. The implementation below uses an ordered, or lexicographic, permutation algorithm. This algorithm is based on a a permutation algorithm from the book *Practical Algorithms in C++* by Bryan Flamig. ```vbnet Option Explicit ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' TestPermute - the Main subroutine ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Sub TestPermute Dim arr, n 'arr = Array("One", "Two", "Three", "Four") 'arr = Array(1, "Two", 3, "Four") arr = Array(1, 2, 3, 4) n = UBound(arr) + 1 Rhino.ClearCommandHistory Call Rhino.Print(PermuteCount(n)) Call Permute(arr, 0, n) End Sub ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' Permute ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Sub Permute(ByRef arr, ByVal start, ByVal n) Dim i, j Call PermutePrint(arr) If (start < n) Then For i = n-2 To start Step -1 For j = i+1 To n-1 Call PermuteSwap(arr, i, j) Call Permute(arr, i+1, n) Next Call PermuteRotate(arr, i, n) Next End If End Sub ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' PermutePrint ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Sub PermutePrint(ByRef arr) Dim s, v s = "" For Each v In arr s = s & CStr(v) & vbTab Next Rhino.Print s End Sub ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' PermuteSwap ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Sub PermuteSwap(ByRef arr, ByVal i, ByVal j) Dim tmp tmp = arr(i) arr(i) = arr(j) arr(j) = tmp End Sub ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' PermuteRotate ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Sub PermuteRotate(ByRef arr, ByVal start, ByVal n) Dim tmp, i tmp = arr(start) For i = start To n-2 arr(i) = arr(i+1) Next arr(n-1) = tmp End Sub ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' PermuteCount (e.g. Factorial) ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Function PermuteCount(ByVal n) If n <= 1 Then PermuteCount = 1 Else PermuteCount = n * PermuteCount(n-1) End If End Function ``` -------------------------------------------------------------------------------- # Calculating the Angle Between Two Points Source: https://developer.rhino3d.com/en/guides/cpp/calculating-angle-between-two-points/ This guide demonstrates how to calculate the angle between two points using C/C++. ## Problem For two sets of 3D vectors, you can use the method demonstrated in [Calculate the Angle Between Two Vectors ](/samples/cpp/calculate-the-angle-between-two-vectors/) to calculate the angle between them. The results for both are always the same - 45 degree in this case. For example: ![Angle between vectors](https://developer.rhino3d.com/images/calculating-angle-between-two-points-01.png) But what if you need a result in which one is 45 degrees the other is 315 degrees? ## Solution The following example code demonstrates how to calculate the angle between two 3D points, given a common base point. ```cpp /* Description: Calculates the angle between two points that lie on a given plane. Parameters: plane - [in] The plane on which the points lie basept - [in] The base point refpt1 - [in] The first reference point refpt2 - [in] The second reference point radians - [out] The angle in radians Returns: TRUE if successful, FALSE otherwise. */ static bool CalculatePlaneAngle( const ON_Plane& plane, const ON_3dPoint& basept, const ON_3dPoint& refpt1, const ON_3dPoint& refpt2, double& radians ) { // Make sure the points are on the plane double tolerance = 0.000001; double dist = 0.0; dist = plane.plane_equation.ValueAt( basept ); if( fabs(dist) > tolerance ) return false; dist = plane.plane_equation.ValueAt( refpt1 ); if( fabs(dist) > tolerance ) return false; dist = plane.plane_equation.ValueAt( refpt2 ); if( fabs(dist) > tolerance ) return false; // Make sure base and reference points are not equal if( basept == refpt1 | basept == refpt2 ) return false; // Calculate angle between vectors ON_3dVector v = refpt2 - basept; v.Unitize(); ON_3dVector zerov = refpt1 - basept; zerov.Unitize(); double dot = ON_DotProduct( zerov, v ); dot = RHINO_CLAMP( dot, -1.0, 1.0 ); double angle = acos( dot ); // Calculate a new y-axis based on the plane's // zaxis and our zero vector v = ON_CrossProduct( plane.zaxis, zerov ); v.Unitize(); // Create a plane using our y-axis a the normal ON_Plane yplane; yplane.CreateFromNormal( basept, v ); // Figure out which side of this plane that refpt2 is on dist = yplane.plane_equation.ValueAt( refpt2 ); if( dist < 0.0 ) angle = (ON_PI * 2.0) - angle; radians = angle; return true; } ``` You can use the above function as follows... ```cpp CRhinoCommand::result CCommandTest::RunCommand( const CRhinoCommandContext& context ) { CRhinoView* view = RhinoApp().ActiveView(); if( 0 == view ) return failure; ON_Plane plane = view->ActiveViewport().ConstructionPlane().m_plane; ON_3dPoint basept, refpt1, refpt2; CRhinoGetPoint gp; gp.SetCommandPrompt( L"Base point" ); gp.Constrain( plane ); gp.GetPoint(); if( gp.CommandResult() != success ) return gp.CommandResult(); basept = gp.Point(); gp.SetCommandPrompt( L"First angle point" ); gp.SetBasePoint( basept ); gp.DrawLineFromPoint( basept, TRUE ); gp.GetPoint(); if( gp.CommandResult() != success ) return gp.CommandResult(); refpt1 = gp.Point(); gp.SetCommandPrompt( L"Second angle point" ); gp.SetBasePoint( basept ); gp.DrawLineFromPoint( basept, TRUE ); gp.GetPoint(); if( gp.CommandResult() != success ) return gp.CommandResult(); refpt2 = gp.Point(); double angle = 0.0; if( CalculatePlaneAngle(plane, basept, refpt1, refpt2, angle) ) RhinoApp().Print( L"Angle = %.3f degrees.\n", angle * (180.0 / ON_PI) ); return success; } ``` ## Related Topics - [Calculate the Angle Between Two Vectors (Sample)](/samples/cpp/calculate-the-angle-between-two-vectors/) -------------------------------------------------------------------------------- # Calculating the Lengths of NURBS Curves Source: https://developer.rhino3d.com/en/guides/cpp/calculating-lengths-of-nurbs-curves/ This guide discusses a problem when trying to calculate the length of a NURBS curve using C/C++. ## Problem You may run into problems determining the length of an `ON_NurbsCurve` created by calling `ON_Curve::GetNurbForm`. The following block of code only gets the length of the `ON_Curve` object, not the ON_NurbsCurve object. ```cpp CRhinoCommand::result CCommandFooBar::RunCommand( const CRhinoCommandContext& context ) { CRhinoGetObject go; go.SetCommandPrompt( L"Select curve" ); go.SetGeometryFilter( CRhinoGetObject::curve_object ); go.GetObjects( 1, 1 ); if( go.CommandResult() != success ) return go.CommandResult(); const ON_Curve* curve = go.Object(0).Curve(); if( 0 == curve ) return failure; double curve_length = 0.0; if( curve->GetLength(&curve_length) ) RhinoApp().Print( L"ON_Curve::GetLength() = %g\n", curve_length ); ON_NurbsCurve nurbs_curve; if( curve->GetNurbForm(nurbs_curve) ) { double nurbs_curve_length = 0.0; if( nurbs_curve.GetLength(&nurbs_curve_length) ) RhinoApp().Print( L"ON_NurbsCurve::GetLength() = %g\n", nurbs_curve_length ); } return success; } ``` What is going wrong? ## Solution There is nothing wrong with the code above. Rather, this exposes a flaw in Microsoft Visual C++ and the way it deals with stack variables with modified *vtables*. The problem might sound complicated, but the solution is rather easy. Just make a simple function that you can pass your `ON_NurbsCurve` object to that will calculate the curve length for you. For example, the function below should work for most developers: ```cpp BOOL ON_NurbsCurve_GetLength( const ON_NurbsCurve& curve, double* length, double fractional_tolerance = 1.0e-8, const ON_Interval* sub_domain = NULL ) { return curve.GetLength( length, fractional_tolerance, sub_domain ); } ``` Then, your code would look like this: ```cpp CRhinoCommand::result CCommandFooBar::RunCommand( const CRhinoCommandContext& context ) { CRhinoGetObject go; go.SetCommandPrompt( L"Select curve" ); go.SetGeometryFilter( CRhinoGetObject::curve_object ); go.GetObjects( 1, 1 ); if( go.CommandResult() != success ) return go.CommandResult(); const ON_Curve* curve = go.Object(0).Curve(); if( 0 == curve ) return failure; double curve_length = 0.0; if( curve->GetLength(&curve_length) ) RhinoApp().Print( L"ON_Curve::GetLength() = %g\n", curve_length ); ON_NurbsCurve nurbs_curve; if( curve->GetNurbForm(nurbs_curve) ) { double nurbs_curve_length = 0.0; if( ON_NurbsCurve_GetLength(nurbs_curve, &nurbs_curve_length) ) RhinoApp().Print( L"ON_NurbsCurve::GetLength() = %g\n", nurbs_curve_length ); } return success; } ``` -------------------------------------------------------------------------------- # Canceling Long Processes with ESC Source: https://developer.rhino3d.com/en/guides/cpp/canceling-long-processes-with-esc/ This guide demonstrates two methods for checking the Escape key. ## Overview When writing commands that contain long, time-consuming tasks, you might want to allow the user to cancel the process or command. Here are a couple examples of ways this can be achieved... ## Example 1 The following example demonstrates how to periodically test to see if the user pressed the ESC key by "peeking" in Rhino's message queue... ```cpp // -1 = Quit Rhino // 1 = Escape key pressed // 0 = Okay to proceed int EscapeKeyPressed() { AFX_MANAGE_STATE( RhinoApp().RhinoModuleState() ); MSG msg; memset( &msg, 0, sizeof(MSG) ); while( ::PeekMessage(&msg, 0, 0, 0, PM_NOREMOVE) ) { if( msg.message == WM_KEYDOWN && msg.wParam == VK_ESCAPE ) return 1; if( !RhinoApp().PumpMessage() ) return -1; } return 0; } ``` Using this function is quite simple: in the middle of your loop, call the above function. If the function returns something other than 0, break out of your loop. ## Example 2 The following sample class demonstrates how to hook the Windows keyboard from a Rhino plugin and check for the ESC key. ```cpp // // rhinoEscapeKey.h // class CRhinoEscapeKey { public: CRhinoEscapeKey( bool bHookNow = false ); ~CRhinoEscapeKey(); bool Start(); void Stop(); bool EscapeKeyPressed() const; void ClearEscapeKeyPressedFlag(); protected: static LRESULT CALLBACK HookProc( int code, WPARAM wParam, LPARAM lParam ); static HHOOK m_KeyboardHookProc; static bool m_escape_pressed; }; // // rhinoEscapeKey.cpp // bool CRhinoEscapeKey::m_escape_pressed = false; HHOOK CRhinoEscapeKey::m_KeyboardHookProc = NULL; CRhinoEscapeKey::CRhinoEscapeKey( bool bStartNow ) { if( bStartNow ) Start(); } CRhinoEscapeKey::~CRhinoEscapeKey() { Stop(); } bool CRhinoEscapeKey::Start() { if( NULL == m_KeyboardHookProc ) m_KeyboardHookProc = ::SetWindowsHookEx( WH_KEYBOARD, CRhinoEscapeKey::HookProc, RhinoApp().RhinoInstanceHandle(), ::AfxGetThread()->m_nThreadID ); ClearEscapeKeyPressedFlag(); return( NULL != m_KeyboardHookProc ); } void CRhinoEscapeKey::Stop() { if( m_KeyboardHookProc ) UnhookWindowsHookEx( m_KeyboardHookProc ); m_KeyboardHookProc = NULL; } bool CRhinoEscapeKey::EscapeKeyPressed() const { RhinoApp().Wait(0); return m_escape_pressed; } void CRhinoEscapeKey::ClearEscapeKeyPressedFlag() { m_escape_pressed = false; } LRESULT CALLBACK CRhinoEscapeKey::HookProc( int code, WPARAM wParam, LPARAM lParam ) { // On escape key down.... if( code == HC_ACTION && wParam == VK_ESCAPE && !(lParam & 0x80000000) ) { m_escape_pressed = true; UnhookWindowsHookEx( m_KeyboardHookProc ); m_KeyboardHookProc = NULL; return 0; // Eat the escape key } // call next hook proc including standard windows proc. return CallNextHookEx( m_KeyboardHookProc, code, wParam, lParam ); } ``` ## Usage The following sample code demonstrates using the `CRhinoEscapeKey` class within a Rhino command... ```cpp CRhinoCommand::result CCommandTest::RunCommand( const CRhinoCommandContext& context ) { CRhinoEscapeKey escape; escape.Start(); int i = 0; while( true ) { if( escape.EscapeKeyPressed() ) { escape.Stop(); RhinoApp().Print( L"Command canceled.\n" ); break; } RhinoApp().Print( L"Count = %d.\n", ++i ); } return success; } ``` -------------------------------------------------------------------------------- # Cancelling Scripts Source: https://developer.rhino3d.com/en/guides/rhinoscript/cancelling-scripts/ This guide demonstrates how to allow scripts to be cancelled by the user. ## Overview When running a RhinoScript, it often occurs that a user wants to interrupt a running RhinoScript by pressing ESC. This most frequently happens when running a script with a tight loop or a recursive function. In order to cancel the script, we want to allow the user to press ESC, rather than having to stop Rhino using Task Manager and restart everything. There are two topics to cover: Sleep and OnCancelScript. ## Sleep If you have a tight loop that does not call back into Rhino, then it is possible for ESC key processing to be either very slow or not happen at all. This is because the tight loop does not allow Rhino to process the messages, such as keystrokes, sent to it by Windows. To work around this situation, you will want to call back into Rhino inside of your tight loop. Using RhinoScript's **Sleep** function is a good way to do this without slowing down your code... ```vbnet Sub TightLoopEscapeTest For i = 0 To 100000 ' ' Do tight loop processing here... ' ' Allow Rhino to "pump" it's message queue Call Rhino.Sleep(0) Next End Sub ``` If your loop is relatively fast, you may want to postpone the Sleep call or else it will slow down your script significantly... ```vbnet Sub TightLoopEscapeTest For i = 0 To 100000 ' ' Do tight loop processing here... ' ' Allow Rhino to "pump" it's message queue If ((i Mod 25) = 0) Then Call Rhino.Sleep(0) Next End Sub ``` ...which will call the Sleep method only once every 25 iterations. ## OnCancelScript The default behavior when cancelling a script is to halt the script's execution and print a “Script cancelled” message to Rhino's command line. There are often times when you might want to know when your script is cancelled. For example, lets say you have a script that does the following steps in this order: 1. Modifies some Rhino parameters... 1. Performs an operation and... 1. Resets the modified parameters. If your script is cancelled in operation (2), then Rhino can be left in a state unfamiliar to the user. It is possible for your script to be notified when it has been cancelled. This is done through a special, user-defined subroutine named `OnCancelScript`. When a script is running and the ESC key is pressed, RhinoScript searches for loaded, user-defined subroutine named `OnCancelScript`. If this subroutine is detected, RhinoScript will automatically run this procedure instead of just printing the “Script cancelled” message. In the above script scenario, the user-defined `OnCancelScript` subroutine could reset the parameters (3) that were modified when the script started (1). The following is a simple example that demonstrates the `OnCancelScript` procedure... ```vbnet Sub TightLoopEscapeTest For i = 0 to 100000 Call Rhino.Print(i) Call Rhino.Sleep(0) Next End Sub ' User-defined cancel script handler Sub OnCancelScript ' Do script cancelling operations here... Call MsgBox("Script Cancelled!", vbOkOnly + vbExclamation, "RhinoScript") End Sub ``` **NOTE**: your user-defined `OnCancelScript` subroutine must not have any arguments. If you define your `OnCancelScript` subroutine as one that requires one or more arguments, then RhinoScript will not execute it when ESC is pressed. -------------------------------------------------------------------------------- # Change Construction Plane Modes Source: https://developer.rhino3d.com/en/samples/cpp/change-construction-plane-modes/ Demonstrates how to switch between standard and universal construction plane modes. ```cpp CRhinoCommand::result CCommandCPlaneMode::RunCommand( const CRhinoCommandContext& context ) { BOOL bUPlane = RhinoApp().AppSettings().ModelAidSettings().m_uplane_mode; ON_wString str; if( bUPlane ) str = L"Universal construction planes enabled. New value"; else str = L"Standard construction planes enabled. New value"; CRhinoGetOption go; go.SetCommandPrompt( str ); int c_option = go.AddCommandOption( RHCMDOPTNAME(L"CPlane") ); int u_option = go.AddCommandOption( RHCMDOPTNAME(L"UPlane") ); int t_option = go.AddCommandOption( RHCMDOPTNAME(L"Toggle") ); go.GetOption(); if( go.CommandResult() != CRhinoCommand::success ) return go.CommandResult(); const CRhinoCommandOption* option = go.Option(); if( 0 == option ) return CRhinoCommand::failure; CRhinoAppModelAidSettings settings = RhinoApp().AppSettings().ModelAidSettings(); int option_index = option->m_option_index; if( c_option == option_index ) { if( bUPlane ) { settings.m_uplane_mode = FALSE; RhinoApp().AppSettings().SetModelAidSettings( settings ); } } else if( u_option == option_index ) { if( !bUPlane ) { settings.m_uplane_mode = TRUE; RhinoApp().AppSettings().SetModelAidSettings( settings ); } } else if( t_option == option_index ) { settings.m_uplane_mode = !bUPlane; RhinoApp().AppSettings().SetModelAidSettings( settings ); } return CRhinoCommand::success; } ``` -------------------------------------------------------------------------------- # Change Dimension Style Source: https://developer.rhino3d.com/en/samples/rhinocommon/change-dimension-style/ Demonstrates how to change the dimension style on all objects in a Rhino document. ```cs partial class Examples { public static Result ChangeDimensionStyle(RhinoDoc doc) { foreach (var rhino_object in doc.Objects.GetObjectList(ObjectType.Annotation)) { var annotation_object = rhino_object as AnnotationObjectBase; if (annotation_object == null) continue; var annotation = annotation_object.Geometry as AnnotationBase; if (annotation == null) continue; if (annotation.Index == doc.DimStyles.CurrentDimensionStyleIndex) continue; annotation.Index = doc.DimStyles.CurrentDimensionStyleIndex; annotation_object.CommitChanges(); } doc.Views.Redraw(); return Result.Success; } } ``` ```python from Rhino import * from Rhino.DocObjects import * from Rhino.Commands import * from Rhino.Geometry import * from scriptcontext import doc def RunCommand(): for annotation_object in doc.Objects.GetObjectList(ObjectType.Annotation): if not isinstance (annotation_object, AnnotationObjectBase): continue annotation = annotation_object.Geometry if annotation.Index == doc.DimStyles.CurrentDimensionStyleIndex: continue annotation.Index = doc.DimStyles.CurrentDimensionStyleIndex annotation_object.CommitChanges() doc.Views.Redraw() return Result.Success if __name__ == "__main__": RunCommand() ``` -------------------------------------------------------------------------------- # Changing Display Precision Source: https://developer.rhino3d.com/en/guides/cpp/changing-display-precision/ This brief guide demonstrates how to change the unit's display precision of the current document using C/C++. ## Problem Rhino has a document display precision - the "display precision" option found in the *Units* page in the *Options* dialog. Imagine you want to modify this using C/C++ from your plugin. ## Solution A document's display precision, the number of decimal places used for the distance display, is maintained on an `ON_3dmUnitsAndTolerances` object, which in turn is stored on a `CRhinoDocProperties` object which is a member of the current `CRhinoDoc` object. To modify this variable, you will need to: 1. Make a copy of the document's `ON_3dmUnitsAndTolerances` object. 1. Modify the object's `m_distance_display_precision` member variable. 1. Replace the current `ON_3dmUnitsAndTolerances` with the newly modified one. ## Sample The following sample code demonstrates how to change the unit's display precision of the current document using the Rhino C/C++ SDK... ```cpp CRhinoCommand::result CCommandTest::RunCommand( const CRhinoCommandContext& context ) { // Make a copy of the current model units and tolerances ON_3dmUnitsAndTolerances units = context.m_doc.Properties().ModelUnitsAndTolerances(); // Prompt the user to enter a new display precision value CRhinoGetInteger gi; gi.SetCommandPrompt( L"New display precision" ); gi.SetDefaultInteger( units.m_distance_display_precision ); gi.SetLowerLimit( 0 ); gi.SetUpperLimit( 6 ); gi.GetInteger(); if( gi.CommandResult() == CRhinoCommand::success ) { // The the user's input int distance_display_precision = gi.Number(); if( distance_display_precision != units.m_distance_display_precision ) { units.m_distance_display_precision = distance_display_precision; // Replace the current setting with our updated value context.m_doc.Properties().SetModelUnitsAndTolerances( units, false ); } } return CRhinoCommand::success; } ``` -------------------------------------------------------------------------------- # Circle Center Source: https://developer.rhino3d.com/en/samples/rhinocommon/circle-center/ Demonstrates how to find the center of a circle. ```cs partial class Examples { public static Rhino.Commands.Result CircleCenter(Rhino.RhinoDoc doc) { Rhino.Input.Custom.GetObject go = new Rhino.Input.Custom.GetObject(); go.SetCommandPrompt("Select objects"); go.GeometryFilter = Rhino.DocObjects.ObjectType.Curve; go.GeometryAttributeFilter = Rhino.Input.Custom.GeometryAttributeFilter.ClosedCurve; go.GetMultiple(1, 0); if( go.CommandResult() != Rhino.Commands.Result.Success ) return go.CommandResult(); Rhino.DocObjects.ObjRef[] objrefs = go.Objects(); if( objrefs==null ) return Rhino.Commands.Result.Nothing; double tolerance = doc.ModelAbsoluteTolerance; for( int i=0; i 1 Then marker = 1 Else dblRotA = Atn(-dblCosA / Sqr((-dblCosA * dblCosA) + 1)) + (2 * Atn(1)) dblRotA = Rhino.ToDegrees(dblRotA) 'Create, rotate and scale Vector arrPoint(k) = Rhino.VectorCreate(arrPoint(k-intHole),arrPoint(intCurrentCentre)) arrPoint(k) = Rhino.VectorRotate(arrPoint(k),dblRotA,Array(0,0,1)) arrPoint(k) = Rhino.VectorScale(arrPoint(k),(arrSide(1)/arrSide(0))) arrPoint(k) = Rhino.VectorAdd(arrPoint(k),arrPoint(intCurrentCentre)) 'Check if Circle will Intersect with Existing Circles For checkloop = (k-1) To 0 Step -1 checkdistanceA = Rhino.distance(arrPoint(k), arrPoint(checkloop)) + 0.001 checkdistanceB = (arrRadius(k) + arrRadius(checkloop)) If checkdistanceA < checkdistanceB Then marker = 1 Exit For End If Next If marker = 0 Then 'rhino.AddLine arrPoint(k-intHole), arrPoint(k) strCurrentCircleID = Rhino.AddCircle(Array(arrPoint(k),Array(1,0,0),Array(0,1,0),Array(0,0,1)),arrRadius(k)) End If End If 'Exit the Do Loop if the Circle is Good If marker = 0 Then intHole = 1 Exit Do End If intCurrentCentre = intCurrentCentre + 1 If intCurrentCentre = k-intHole Then intHole = intHole + 1 intCurrentCentre = 0 'If intHole > 2 Then ' rhino.addpoint arrPoint(k-intHole) 'End If 'rhino.messagebox intHole End If Loop Next Rhino.EnableRedraw vbTrue End Sub ``` -------------------------------------------------------------------------------- # Clear Undo and Redo Lists Source: https://developer.rhino3d.com/en/guides/cpp/clear-undo-redo-lists/ This brief guide demonstrates how to clear Rhino's Undo and Redo lists using C/C++. ## How To Rhino allows users to undo the most recent or several create, edit, or transform commands. If you are performing editing operations on large memory consuming objects, Rhino's undo list can quickly grow very large. When this happens, most Rhino users run the ClearUndo command to clear the undo list. It is also possible to clear the undo list from within a plugin. The following sample code demonstrates how to clear Rhino's undo and redo lists... ```cpp CRhinoCommand::result CCommandTest::RunCommand( const CRhinoCommandContext& context ) { RhinoApp().Print( L"Clearing undo and redo lists.\n" ); context.m_doc.ClearUndoRecords( true ); return CRhinoCommand::success; } ``` -------------------------------------------------------------------------------- # Closest Axis Point Source: https://developer.rhino3d.com/en/guides/rhinoscript/closest-axis-point/ This guide demonstrates how to find the closest point on a planar curve to an axis. ## Problem You have a bunch of 2-D curves that are planar to the world x-y plane, and you need to find a point in each curve that is closest to the world y-axis. For example: ![Closest Axis Point](https://developer.rhino3d.com/images/closest-axis-point-01.png) What can you do to calculate this point? ## Solution After selecting the curves and verifying that they are both planar and line in the world x-y plane, calculate the world axis-aligned bounding box for each curve. Using the results of the bounding box calculation, create a line, using the first two points from the results, that is parallel to the world y-axis. Intersect this line with the curve. The results of the intersection will be at the point that is closest to the world y-axis. The following example demonstrates the above algorithm... ```vbnet Option Explicit Sub ClosestAxisPoint Dim arrCurves arrCurves = Rhino.GetObjects("Select planar curves", 4, True, True) If Not IsArray(arrCurves) Then Exit Sub Dim strCurve, arrPlane(3), arrBox, strLine, arrCCX arrPlane(0) = Array(0,0,0) arrPlane(1) = Array(1,0,0) arrPlane(2) = Array(0,1,0) arrPlane(3) = Array(0,0,1) Rhino.EnableRedraw False For Each strCurve In arrCurves If Rhino.IsCurvePlanar(strCurve) Then If Rhino.IsCurveInPlane(strCurve, arrPlane) Then arrBox = Rhino.BoundingBox(strCurve) strLine = Rhino.AddLine(arrBox(0), arrBox(1)) arrCCX = Rhino.CurveCurveIntersection(strCurve, strLine) If IsArray(arrCCX) Then Rhino.AddPoint arrCCX(0,1) End If Rhino.DeleteObject strLine End If End If Next Rhino.EnableRedraw True End Sub ``` -------------------------------------------------------------------------------- # Closest Point Calculation with RTree Source: https://developer.rhino3d.com/en/samples/rhinocommon/closest-point-calculation-with-rtree/ Demonstrates how to perform a closest point calculation using an RTree data structure. ```cs partial class Examples { static void SearchCallback(object sender, RTreeEventArgs e) { SearchData data = e.Tag as SearchData; if (data == null) return; data.HitCount = data.HitCount + 1; Point3f vertex = data.Mesh.Vertices[e.Id]; double distance = data.Point.DistanceTo(vertex); if (data.Index == -1 || data.Distance > distance) { // shrink the sphere to help improve the test e.SearchSphere = new Sphere(data.Point, distance); data.Index = e.Id; data.Distance = distance; } } class SearchData { public SearchData(Mesh mesh, Point3d point) { Point = point; Mesh = mesh; HitCount = 0; Index = -1; Distance = 0; } public int HitCount { get; set; } public Point3d Point { get; private set; } public Mesh Mesh { get; private set; } public int Index { get; set; } public double Distance { get; set; } } public static Rhino.Commands.Result RTreeClosestPoint(RhinoDoc doc) { Rhino.DocObjects.ObjRef objref; var rc = Rhino.Input.RhinoGet.GetOneObject("select mesh", false, Rhino.DocObjects.ObjectType.Mesh, out objref); if (rc != Rhino.Commands.Result.Success) return rc; Mesh mesh = objref.Mesh(); objref.Object().Select(false); doc.Views.Redraw(); using (RTree tree = new RTree()) { for (int i = 0; i < mesh.Vertices.Count; i++) { // we can make a C++ function that just builds an rtree from the // vertices in one quick shot, but for now... tree.Insert(mesh.Vertices[i], i); } while (true) { Point3d point; rc = Rhino.Input.RhinoGet.GetPoint("test point", false, out point); if (rc != Rhino.Commands.Result.Success) break; SearchData data = new SearchData(mesh, point); // Use the first vertex in the mesh to define a start sphere double distance = point.DistanceTo(mesh.Vertices[0]); Sphere sphere = new Sphere(point, distance * 1.1); if (tree.Search(sphere, SearchCallback, data)) { doc.Objects.AddPoint(mesh.Vertices[data.Index]); doc.Views.Redraw(); RhinoApp.WriteLine("Found point in {0} tests", data.HitCount); } } } return Rhino.Commands.Result.Success; } } ``` ```python import Rhino import rhinoscriptsyntax as rs # data passed to the RTree's SearchCallback function that # we can use for recording what is going on class SearchData: def __init__(self, mesh, point): self.HitCount = 0 self.Mesh = mesh self.Point = point self.Index = -1 self.Distance = 0 def SearchCallback(sender, e): data = e.Tag data.HitCount += 1 vertex = data.Mesh.Vertices[e.Id] distance = data.Point.DistanceTo(vertex) if data.Index == -1 or data.Distance > distance: # shrink the sphere to help improve the test e.SearchSphere = Rhino.Geometry.Sphere(data.Point, distance) data.Index = e.Id data.Distance = distance def RunSearch(): id = rs.GetObject("select mesh", rs.filter.mesh) mesh = rs.coercemesh(id) if mesh: rs.UnselectObject(id) tree = Rhino.Geometry.RTree() # I can add a RhinoCommon function that just builds an rtree from the # vertices in one quick shot, but for now... for i,vertex in enumerate(mesh.Vertices): tree.Insert(vertex, i) while(True): point = rs.GetPoint("test point") if not point: break data = SearchData(mesh, point) # Use the first vertex in the mesh to define a start sphere distance = point.DistanceTo(mesh.Vertices[0]) sphere = Rhino.Geometry.Sphere(point, distance * 1.1) if tree.Search(sphere, SearchCallback, data): rs.AddPoint(mesh.Vertices[data.Index]) print("Found point in {0} tests".format(data.HitCount)) if __name__=="__main__": RunSearch() ``` -------------------------------------------------------------------------------- # Cloud Zoo License Cluster Object Source: https://developer.rhino3d.com/en/guides/rhinocommon/cloudzoo/cloudzoo-licensecluster/ A License Cluster object is a JSON object that represents a list of License objects for a software product issued by a registered issuer in Cloud Zoo. ## Structure { "licenses": [ONE OR MORE LICENSE OBJECTS] } ## Description - `licenses` - An array of one or more [License objects](/guides/rhinocommon/cloudzoo/cloudzoo-license). ## When should I cluster licenses? Under normal circumstances, a License Cluster contains a single [License object](/guides/rhinocommon/cloudzoo/cloudzoo-license) in its `licenses` array. However, particularly when dealing with upgrades, clustering multiple related licenses may be useful. For example, consider a hypothetical Plug-In called *3D Donuts*. There are licenses for *3D Donuts* version 1, and licenses for *3D Donuts* version 2. The *Donuts* software license agreement can describe one of the following three scenarios: 1. *After upgrading to version 2, users can run version 1 and version 2, but not concurrently.* In this scenario, it makes sense to cluster the version 1 license and the version 2 license. To do this, simply return a license cluster with both License objects. Cloud Zoo will automatically replace the existing version 1 license cluster with the new one passed. Users now have a single seat (Unless `numberOfSeats` is greater than 1) to share between version 1 and version 2. If the licenses in the cluster have an expiration date, the `exp` field of the first license in the array will be assumed to be the expiration date for all licenses. Similarly, the `numberOfSeats` field of the first license will apply to all licenses in the cluster, regardless of the value of their `numberOfSeats` field. 2. After upgrading to version 2, users can run version 1 and version 2 concurrently. In this scenario, it is advisable not to cluster licenses, but to treat them independent of each other--even if the version 1 license is required initially to add the version 2 license. Users will be able to run version 1 and version 2 concurrently. 3. After upgrading to version 2, users can no longer run version 1. This scenario is similar to scenario #2, but after adding a license, the issuer must manually call the [DELETE endpoint](/guides/rhinocommon/cloudzoo/cloudzoo-optional-endpoints#delete-license) to remove version 1 from an entity. -------------------------------------------------------------------------------- # Cloud Zoo License Object Source: https://developer.rhino3d.com/en/guides/rhinocommon/cloudzoo/cloudzoo-license/ A License is a JSON object that represents a license for a software product issued by a registered issuer in Cloud Zoo. ## Structure ```json { "id": "6-32403404-3434340-343432", "key": "RH60-ABCD-EFGZ-HIJK-LMNO", "aud": "4ed9ebbe-c43e-4d4a-9642-e555d727df9f", "iss": "mcneel", "exp": null, "numberOfSeats": 1, "editions": { "en": "Commercial", "es": "Comercial" }, "metadata": OPTIONAL JSON OBJECT SMALLER THAN 10K } ``` ## Description - `id` - A string that uniquely describes each license. This string must be unique for all licenses of the same product type. It is also known as the license's serial number. - `key` - A string that uniquely describes each license. This string must be unique for all licenses of the same product type. Normally, this string is used to add a license to Cloud Zoo by users. - `aud` - A GUID denoting the product id of the plugin. This is how Cloud Zoo knows that this license can be used for your product. - ` iss` - The id of the issuer as registered with Cloud Zoo. - `exp` - The expiration date of the license, expressed in a JSON numeric value representing the number of seconds from 1970-01-01T00:00:00Z UTC until the specified UTC date/time, ignoring leap seconds. For example, for midnight on March 31, 2019, the value would be `1553990400`. Cloud Zoo will not honor the license as valid for issuing leases after the expiration date has passed. If null, the license is considered perpetual (i.e. never expires). - `numberOfSeats` - Denotes how many seats can be concurrently issued, barring any advanced heuristics, for the license. This number must be at least 1. For example, a lab license with a value of `30` would allow 30 team members to use your software concurrently. - `editions` - A dictionary of localized product editions. Each key represents an ISO 639-1 language code. You may specify a two letter country code after the language with a dash or an underscore (i.e. such as `zh-tw`, case insensitive). If that exact language id is not available for a particular task in the system, the system will attempt to use a more generic language id (i.e. for example, if `es-CO` is not available, then the system will try to use `es`). If the region agnostic language id is also not available, en (English) will be used. At least one key-value pair must be present, preferably in English. - `metadata` - Any arbitrary data that is to be stored with the license. The issuer will receive this data back on other callbacks, so the issuer can use this field to save state or other arbitrary information. The raw json data must be less than 10KB. This field is optional. -------------------------------------------------------------------------------- # Cloud Zoo Product Object Source: https://developer.rhino3d.com/en/guides/rhinocommon/cloudzoo/cloudzoo-product/ A Product is a JSON object that represents a a product for a specific issuer in Cloud Zoo. A product's id should be correlated with your Plug-In's id. All licenses in the system are related to a specific product. ## Structure { "id": "06eb1079-5a56-47a1-aw6d-0b4518fd894b", "creationDate": 1559928168, "iss": "mcneel", "format": { "length": { "min": 24, "max": 24 }, "prefix": "RMA7-", "example": "RMA7-XXXX-XXXX-XXXX-XXXX-XXXX", "regexFilter": "[A-Za-z0-9]" }, "version": "9", "platforms": [ "Windows" ], "picture": "https://elisapi.mcneel.com/media/2", "downloadUrl": "https://www.rhino3d.com/download/rhino-for-mac/6/wip", "titles": { "en": "Rhino Unicorn" }, "relatedProducts": [ {"id":"59ff75c9-9c71-4ef8-a290-6b590f3fc63a"}, {"id":"55500d41-3a41-4474-99b3-684032a4f4df"} ] } ## Description - `id` (_readonly_) - A **lowercase** GUID that uniquely describes each product. This GUID must be unique in the entire system. - `creationDate` (_readonly_) - A unix timestamp in seconds representing the date the product was added to Cloud Zoo. - ` iss` (_readonly_) - The id of the issuer as registered with Cloud Zoo. - `format` - A [License Format object](/guides/rhinocommon/cloudzoo/cloudzoo-licenseformat). Cloud Zoo will send all requests to add a license to the system to the issuer of the product whose license format matches the given license key. - `version` - The version of the product that this license represents. This string is user facing and will be used in Rhino as well as in the Licenses Portal. - `platforms` - An array of supported platforms for this license. Currently, only `Windows` and `Mac` are supported. - `picture` - A url where an icon for this product may be found. The icon must not be larger than 1MP. - `downloadUrl` - A url where the actual software the product represents may be downloaded. This link will be publicly available to users. - `titles` - A dictionary of localized product names. Each key represents an ISO 639-1 language code. You may specify a two letter country code after the language with a dash or an underscore (i.e. such as `zh-tw`, case insensitive). If that exact language id is not available for a particular task in the system, the system will attempt to use a more generic language id (i.e. for example, if `es-CO` is not available, then the system will try to use `es`). If the region agnostic language id is also not available, en (English) will be used. At least one key-value pair must be present, preferably in English. - `relatedProducts` - An array of related products. Related products allow Cloud Zoo to issue a lease for any related product listed in this array using a seat for the given product. For example, if the current product is Rhino 9 and it includes Rhino 8 and Rhino 7 in its related products array, a client who has a Rhino 9 seat available can request a lease for Rhino 8 or Rhino 7. In this manner, related products allow users to run previous and future versions of your product without having to have additional licenses in their respective entities. -------------------------------------------------------------------------------- # Comparing Arrays Source: https://developer.rhino3d.com/en/guides/rhinoscript/comparing-arrays/ This guide discusses efficient VBScript array comparison. ## Slow Comparison Imagine you have two collections of items and you want to determine how many of those items have the same name. In short, you want to compare the contents of two arrays. Consider this straightforward method of comparison: ```vbnet intSame = 0 For Each strFirst In arrFirst For Each strSecond In arrSecond If StrComp(strFirst, strSecond, vbTextCompare) = 0 Then intSame = intSame + 1 Exit For End If Next Next ``` This method, although simple, is extremely slow. Let's say there are 5000 items in `arrFirst`, 3000 items in `arrSecond`, and 1500 of them have the same value. Every one of the 3500 unsuccessful searches checks all 3000 `arrSecond` items, and the 1500 successful searches on average check 1500 `arrSecond` items each. Each time through, the inner loop does one loop iteration and one string comparison. Add all those up and you get millions of function calls to determine this count. Now, each individual function call is only taking a few microseconds, but all of these calls add up! There is another way... ## Fast Comparison Try building a faster lookup table rather than doing a full search through the collection every time. ```vbnet Set objLookup = CreateObject("Scripting.Dictionary") For Each strFirst In arrFirst Call objLookup.Add(strFirst, 0) ' 0 = some useless value Next For Each strSecond In arrSecond If objLookup.Exists(strSecond) Then intSame = intSame + 1 Next ``` This is only a couple of thousand function calls. So we believe that this will be much, much faster. ## Related Topics - [Array Bounds](/guides/rhinoscript/array-bounds) - [Array Utilities](/guides/rhinoscript/array-utilities) - [VBScript Dictionaries](/guides/rhinoscript/vbscript-dictionaries) - [VBScript Looping](/guides/rhinoscript/vbscript-looping) -------------------------------------------------------------------------------- # Compute: Features Source: https://developer.rhino3d.com/en/guides/compute/features/ Rhino SDK functions via REST API ## Compute Can * Run on Windows and Linux Servers (Ubuntu 24.04 and AmazonLinux 2023). * Calculate Grasshopper definitions online in a serial or parallel solutions. * Manipulate Rhino (3DM) and other file types anywhere on the net. * Call to over 2400+ geometric operations on custom objects from within existing processes online. Including points, curves, surfaces, meshes and solids. ## Compute Features * Do geometry calculations through a cloud based stateless REST API. * Access [2400+ RhinoCommon API calls](https://compute.rhino3d.com/sdk) from outside Rhino. * Access to additional RhinoCommon functions not available in [openNURBS](https://www.rhino3d.com/opennurbs), including: * Closest point calculations * Intersection calculations * Surface tessellation (meshing) * Interpolation * Booleans * Area and mass property calculations * Other miscellaneous geometry calculations * Extendable REST method calls through a common declaration. * Access existing Rhino/Grasshopper plugins through the online interface. * Serialize operations in one request through grasshopper or python scripts. * Client side libraries available for use with standalone C#(.NET), Python and JavaScript. ## Open Source Based on [Rhino Inside™ technology](https://www.rhino3d.com/inside), compute is an [open source project](https://github.com/mcneel/compute.rhino3d). -------------------------------------------------------------------------------- # Computer Physical Address Source: https://developer.rhino3d.com/en/samples/rhinoscript/computer-physical-address/ Illustrates RhinoScript code that determines the physical, or MAC, address of a computer. ```vbnet Sub PrintMacAddress Dim strComputer strComputer = "." Dim objWMIService Set objWMIService = GetObject("winmgmts:" _ & "{impersonationLevel=impersonate}!\\" & strComputer & "\root\cimv2") Dim colAdaptors Set colAdapters = objWMIService.ExecQuery _ ("SELECT * FROM Win32_NetworkAdapterConfiguration WHERE IPEnabled = True") Dim n n = 1 For Each objAdapter In colAdapters Rhino.Print Rhino.Print "Network Adapter " & n Rhino.Print " Description: " & objAdapter.Description Rhino.Print " Physical (MAC) address: " & objAdapter.MACAddress n = n + 1 Next End Sub ``` -------------------------------------------------------------------------------- # Conduit Draw Shaded Mesh Source: https://developer.rhino3d.com/en/samples/rhinocommon/conduit-draw-shaded-mesh/ Demonstrates how to draw a shaded mesh using a display conduit. ```cs public class DrawShadedMeshConduit : Rhino.Display.DisplayConduit { public Rhino.Geometry.Mesh Mesh { get; set; } protected override void CalculateBoundingBox(Rhino.Display.CalculateBoundingBoxEventArgs e) { if (null != Mesh) { Rhino.Geometry.BoundingBox bbox = Mesh.GetBoundingBox(false); // Unites a bounding box with the current display bounding box in // order to ensure dynamic objects in "box" are drawn. e.IncludeBoundingBox(bbox); } } protected override void PostDrawObjects(Rhino.Display.DrawEventArgs e) { if (null != Mesh) { Rhino.Display.DisplayMaterial material = new Rhino.Display.DisplayMaterial(); material.Diffuse = System.Drawing.Color.Blue; e.Display.DrawMeshShaded(Mesh, material); } } } partial class Examples { public static Rhino.Commands.Result ConduitDrawShadedMesh(Rhino.RhinoDoc doc) { Rhino.Geometry.Mesh mesh = MeshBox(100, 500, 10); if (null == mesh) return Rhino.Commands.Result.Failure; DrawShadedMeshConduit conduit = new DrawShadedMeshConduit(); conduit.Mesh = mesh; conduit.Enabled = true; doc.Views.Redraw(); string outputString = string.Empty; Rhino.Input.RhinoGet.GetString("Press to continue", true, ref outputString); conduit.Enabled = false; doc.Views.Redraw(); return Rhino.Commands.Result.Success; } public static Rhino.Geometry.Mesh MeshBox(double x, double y, double z) { Rhino.Geometry.Mesh mesh = new Rhino.Geometry.Mesh(); mesh.Vertices.Add(0, 0, 0); mesh.Vertices.Add(x, 0, 0); mesh.Vertices.Add(x, y, 0); mesh.Vertices.Add(0, y, 0); mesh.Vertices.Add(0, 0, z); mesh.Vertices.Add(x, 0, z); mesh.Vertices.Add(x, y, z); mesh.Vertices.Add(0, y, z); mesh.Faces.AddFace(3, 2, 1, 0); mesh.Faces.AddFace(4, 5, 6, 7); mesh.Faces.AddFace(0, 1, 5, 4); mesh.Faces.AddFace(1, 2, 6, 5); mesh.Faces.AddFace(2, 3, 7, 6); mesh.Faces.AddFace(3, 0, 4, 7); mesh.Normals.ComputeNormals(); mesh.Compact(); if (mesh.IsValid) return mesh; return null; } } ``` ```python # No Python sample available ``` -------------------------------------------------------------------------------- # Constrained Copy Source: https://developer.rhino3d.com/en/samples/rhinocommon/constrained-copy/ Demonstrates how to use a constrained GetPoint to copy a curve. ```cs partial class Examples { public static Rhino.Commands.Result ConstrainedCopy(Rhino.RhinoDoc doc) { // Get a single planar closed curve var go = new Rhino.Input.Custom.GetObject(); go.SetCommandPrompt("Select curve"); go.GeometryFilter = Rhino.DocObjects.ObjectType.Curve; go.GeometryAttributeFilter = Rhino.Input.Custom.GeometryAttributeFilter.ClosedCurve; go.Get(); if( go.CommandResult() != Rhino.Commands.Result.Success ) return go.CommandResult(); var objref = go.Object(0); var base_curve = objref.Curve(); var first_point = objref.SelectionPoint(); if( base_curve==null || !first_point.IsValid ) return Rhino.Commands.Result.Cancel; Rhino.Geometry.Plane plane; if( !base_curve.TryGetPlane(out plane) ) return Rhino.Commands.Result.Cancel; // Get a point constrained to a line passing through the initial selection // point and parallel to the plane's normal var gp = new Rhino.Input.Custom.GetPoint(); gp.SetCommandPrompt("Offset point"); gp.DrawLineFromPoint(first_point, true); var line = new Rhino.Geometry.Line(first_point, first_point+plane.Normal); gp.Constrain(line); gp.Get(); if( gp.CommandResult() != Rhino.Commands.Result.Success ) return gp.CommandResult(); var second_point = gp.Point(); Rhino.Geometry.Vector3d vec = second_point - first_point; if( vec.Length > 0.001 ) { var xf = Rhino.Geometry.Transform.Translation(vec); Guid id = doc.Objects.Transform(objref, xf, false); if( id!=Guid.Empty ) { doc.Views.Redraw(); return Rhino.Commands.Result.Success; } } return Rhino.Commands.Result.Cancel; } } ``` ```python import Rhino import scriptcontext def constrainedcopy(): #get a single closed curve go = Rhino.Input.Custom.GetObject() go.SetCommandPrompt("Select curve") go.GeometryFilter = Rhino.DocObjects.ObjectType.Curve go.GeometryAttributeFilter = Rhino.Input.Custom.GeometryAttributeFilter.ClosedCurve go.Get() if go.CommandResult() != Rhino.Commands.Result.Success: return objref = go.Object(0) base_curve = objref.Curve() first_point = objref.SelectionPoint() if not base_curve or not first_point.IsValid: return isplanar, plane = base_curve.TryGetPlane() if not isplanar: return gp = Rhino.Input.Custom.GetPoint() gp.SetCommandPrompt("Offset point") gp.DrawLineFromPoint(first_point, True) line = Rhino.Geometry.Line(first_point, first_point + plane.Normal) gp.Constrain(line) gp.Get() if gp.CommandResult() != Rhino.Commands.Result.Success: return second_point = gp.Point() vec = second_point - first_point if vec.Length > 0.001: xf = Rhino.Geometry.Transform.Translation(vec) id = scriptcontext.doc.Objects.Transform(objref, xf, False) scriptcontext.doc.Views.Redraw() return id if __name__=="__main__": constrainedcopy() ``` -------------------------------------------------------------------------------- # Control Point Curve Through Polyline Source: https://developer.rhino3d.com/en/samples/cpp/control-point-curve-through-polyline/ Demonstrates how to create a control points curve through a polyline. ```cpp ON_NurbsCurve* RhControlPointsCurveThroughPolyline( const ON_Polyline& polyline, int degree ) { const int count = polyline.Count(); if( count < 2 ) return 0; degree = ( count <= degree) ? count - 1 : degree; ON_NurbsCurve* curve = ON_NurbsCurve::New(); if( curve ) { bool rc = false; if( polyline.IsClosed() ) rc = curve->CreatePeriodicUniformNurbs( 3, degree + 1, count - 1, polyline ); else rc = curve->CreateClampedUniformNurbs( 3, degree + 1, count, polyline ); if( !rc ) { delete curve; curve = 0; } } return curve; } ``` -------------------------------------------------------------------------------- # Convert an Arc to a NURBS Curve Source: https://developer.rhino3d.com/en/samples/cpp/convert-arc-to-nurbs-curve/ Demonstrates how to convert an ON_ArcCurve object to an ON_NurbsCurve object. ```cpp CRhinoCommand::result CCommandTest::RunCommand( const CRhinoCommandContext& context ) { CRhinoGetObject go; go.SetCommandPrompt( L"Select arc to convert" ); go.SetGeometryFilter( CRhinoGetObject::curve_object ); go.SetGeometryAttributeFilter( CRhinoGetObject::open_curve ); go.GetObjects( 1, 1 ); if( go.CommandResult() != success ) return go.CommandResult(); const CRhinoObjRef& obj_ref = go.Object(0); const CRhinoObject* obj = obj_ref.Object(); if( !obj ) return failure; const ON_ArcCurve* arc_crv = ON_ArcCurve::Cast( obj_ref.Geometry() ); if( !arc_crv ) { RhinoApp().Print( L"Curve is not an arc.\n" ); return nothing; } ON_NurbsCurve nurbs_crv; if( arc_crv->GetNurbForm(nurbs_crv) && nurbs_crv.IsValid() ) { ON_3dmObjectAttributes attribs = obj->Attributes(); context.m_doc.AddCurveObject( nurbs_crv, &attribs ); context.m_doc.DeleteObject( obj_ref ); context.m_doc.Redraw(); return success; } return failure; } ``` -------------------------------------------------------------------------------- # Convert Block to Group Source: https://developer.rhino3d.com/en/samples/rhinoscript/convert-block-to-group/ Demonstrates how to explode a block and group its components using RhinoScript. ```vbnet ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' ConvertBlockToGroup.rvb -- March 2010 ' If this code works, it was written by Dale Fugier. ' If not, I don't know who wrote it. ' Works with Rhino 4.0. ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Option Explicit ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' Main procedure for script ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Sub ConvertBlockToGroup() ' Local variables Dim arrBlocks, strBlock, arrObjects(), nBound ' Get blocks to explode arrBlocks = Rhino.GetObjects("Select blocks to convert", 4096, True, True) If IsNull(arrBlocks) Then Exit Sub ' Explode the blocks For Each strBlock In arrBlocks ' Reset our array of objects ReDim arrObjects(-1) ' Explode the block Call BlockExplode(strBlock, arrObjects) ' See if any objects were added to our array On Error Resume Next nBound = UBound(arrObjects) If (Err.Number = 0) Then ' Group the objects Call Rhino.AddObjectsToGroup(arrObjects, Rhino.AddGroup()) End If Next End Sub ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' Explodes a block and all of its nested blocks ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Sub BlockExplode(ByVal strBlock, ByRef arrObjects) ' Local variables Dim arrExplodes, strExplode ' Explode the block If Rhino.IsBlockInstance(strBlock) Then arrExplodes = Rhino.ExplodeBlockInstance(strBlock) If IsArray(arrExplodes) Then For Each strExplode In arrExplodes ' Recusive call... Call BlockExplode(strExplode, arrObjects) Next End If Else ' Add the object to our array Call ArrayAdd(arrObjects, strBlock) End If End Sub ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' Adds a new element to the end of an array ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Sub ArrayAdd(ByRef arr, ByVal val) Dim ub If IsArray(arr) Then On Error Resume Next ub = UBound(arr) If Err.Number <> 0 Then ub = -1 ReDim Preserve arr(ub + 1) arr(UBound(arr)) = val End If End Sub ``` -------------------------------------------------------------------------------- # Convert Dots to Text Source: https://developer.rhino3d.com/en/samples/rhinoscript/convert-dots-to-text/ Illustrates RhinoScript code that converts annotation dots to text object. ```vbnet Sub ConvertDotsToText Dim arrDots, strDot arrDots = Rhino.GetObjects("Select dots", 0, True, True ) If Not IsArray(arrDots) Then Exit Sub Dim arrPt, strText For Each strDot In arrDots If Rhino.IsTextDot(strDot) Then strText = Rhino.TextDotText(strDot) arrPt = Rhino.TextDotPoint(strDot) Rhino.AddText strText, arrPt Rhino.DeleteObject strDot End If Next End Sub ``` -------------------------------------------------------------------------------- # Converting GUIDs to Strings Source: https://developer.rhino3d.com/en/guides/rhinoscript/converting-guids-to-strings/ This guide demonstrates how convert an array of bytes containing a GUID to a string. ## Problem If you have written a RhinoScript function which calls a method on a COM object that return an array of bytes with a GUID, you will likely want to convert this GUID into a string. Converting GUIDs to strings is possible, but it takes a little work. ## Solution The logical format of a GUID in memory is not in the same order as the bytes are in the string. A GUID stored in binary format in memory is a sixteen byte structure in the following format: `DWORD-WORD-WORD-BYTE BYTE-BYTE BYTE BYTE BYTE BYTE BYTE` Where a WORD consists of two bytes and a DWORD consists of four bytes. They are stored in memory in order from the least to the most significant, or “little endian”, on Intel-based systems. So, you need to make sure you decode the array in the correct order The following example RhinoScript code demonstrates how to convert an array of bytes containing a GUID to a string. ```vbnet ' Returns single digit bytes, like 0, as "00", not "0" Function HexByte(b) HexByte = Right("0" & Hex(b), 2) End Function ' Converts a GUID to a string Function GuidToString(ByteArray) Dim Binary, S Binary = CStr(ByteArray) ' Uncomment if you want opening paren ' S = "{" S = S & HexByte(AscB(MidB(Binary, 4, 1))) S = S & HexByte(AscB(MidB(Binary, 3, 1))) S = S & HexByte(AscB(MidB(Binary, 2, 1))) S = S & HexByte(AscB(MidB(Binary, 1, 1))) S = S & "-" S = S & HexByte(AscB(MidB(Binary, 6, 1))) S = S & HexByte(AscB(MidB(Binary, 5, 1))) S = S & "-" S = S & HexByte(AscB(MidB(Binary, 8, 1))) S = S & HexByte(AscB(MidB(Binary, 7, 1))) S = S & "-" S = S & HexByte(AscB(MidB(Binary, 9, 1))) S = S & HexByte(AscB(MidB(Binary, 10, 1))) S = S & "-" S = S & HexByte(AscB(MidB(Binary, 11, 1))) S = S & HexByte(AscB(MidB(Binary, 12, 1))) S = S & HexByte(AscB(MidB(Binary, 13, 1))) S = S & HexByte(AscB(MidB(Binary, 14, 1))) S = S & HexByte(AscB(MidB(Binary, 15, 1))) S = S & HexByte(AscB(MidB(Binary, 16, 1))) ' Uncomment if you want closing paren ' S = S & "}" GuidToString = S End Function ``` ## Related Topics - [Creating GUIDs](/guides/rhinoscript/creating-guids) -------------------------------------------------------------------------------- # Converting Text to Geometry Source: https://developer.rhino3d.com/en/guides/rhinoscript/converting-text-to-geometry/ This guide demonstrates how to convert text to curves using RhinoScript. ## Problem You have many text elements that you would like to convert to text objects (geometry) for engraving. You can explode a text element and get curves that outline the text. The problem is, when you change a text element to a single stroke font, it automatically closes each letter/number and is unreadable. The only way you have been able to make a single stroke font work is by creating geometry using Rhino's TextObject command. However, because you have so many text elements it would take forever to remake geometry for each of them. It is possible to write a script to automate this. ## Solution The following script demonstrates how to convert text elements to text objects (geometry). In this sample, text objects (geometry) are created with the identical properties, such as font, height, bold, and italics, as the text element. ```vbnet ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' ConvertTextToGeometry.rvb -- September 2008 ' If this code works, it was written by Dale Fugier. ' If not, I don't know who wrote it. ' Works with Rhino 4.0. Option Explicit Sub ConvertTextToGeometry ' Declare local variables Dim obj_list, obj, saved_plane, cmd Dim font, height, plane, style, text, bold, italic ' Select annotation objects obj_list = Rhino.GetObjects("Select text to convert to geometry", 512, True, True) If Not IsArray(obj_list) Then Exit Sub ' For speed, turn of screen redrawing Call Rhino.EnableRedraw(False) ' Save the current construction plane saved_plane = Rhino.ViewCPlane() ' Process each selected object For Each obj In obj_list ' Weed out just the text objects If Rhino.IsText(obj) Then ' Acquire the text parameters font = Rhino.TextObjectFont(obj) height = Rhino.TextObjectHeight(obj) plane = Rhino.TextObjectPlane(obj) style = Rhino.TextObjectStyle(obj) text = Rhino.TextObjectText(obj) If (style And 1) Then bold = "_Yes" Else bold = "_No" End If If (style And 2) Then italic = "_Yes" Else italic = "_No" End If ' Set the current construction plane Call Rhino.ViewCPlane(, plane) ' Add a new text object (geometry) cmd = "_-TextObject " cmd = cmd & "_GroupOutput=_Yes " cmd = cmd & "_FontName=" & font & " " cmd = cmd & "_Italic=" & italic & " " cmd = cmd & "_Bold=" & bold & " " cmd = cmd & "_Height=" & CStr(height) & " " cmd = cmd & "_Output=_Curves " cmd = cmd & "_AllowOpenCurves=_Yes " cmd = cmd & Chr(34) & text & Chr(34) & " " cmd = cmd & "0" Call Rhino.Command(cmd, 0) ' Delete the original object Call Rhino.DeleteObject(obj) End If Next ' Restore the saved construction plane Call Rhino.ViewCPlane(, saved_plane) ' Enable screen redrawing Call Rhino.EnableRedraw(True) End Sub ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Rhino.AddStartupScript Rhino.LastLoadedScriptFile Rhino.AddAlias "ConvertTextToGeometry", "_NoEcho _-RunScript (ConvertTextToGeometry)" ``` If you want to override the font to use a single stroke font, simply modify this line: ```vbnet font = Rhino.TextObjectFont(obj) ``` and replace it with something more like this: ```vbnet font = "" ``` -------------------------------------------------------------------------------- # Converting to Grayscale Source: https://developer.rhino3d.com/en/guides/rhinoscript/converting-to-grayscale/ This guide demonstrates how to convert an RGB color value to grayscale. ## Overview To convert any color to a grayscale representation of its luminance, first one must obtain the values of its red, green, and blue (RGB) primaries in linear intensity encoding, by gamma expansion. Then, add together 30% of the red value, 59% of the green value, and 11% of the blue value. The resultant number is the desired linear luminance value; it typically needs to be gamma compressed to get back to a conventional grayscale representation. ## Example The following example demonstrates the above algorithm... ```vbnet Option Explicit Sub ConvertLayersToGrayscale() ' Declare local variables Dim arrLayers, strLayer, lngColor Dim nGray, nRed, nGreen, nBlue ' Turn off screen redrawing (for performance) Call Rhino.EnableRedraw(False) ' Get all of the layers in the document arrLayers = Rhino.LayerNames ' Process each layer one-by-one For Each strLayer In arrLayers ' Get the layer's color lngColor = Rhino.LayerColor(strLayer) ' Get the color's red-green-blue components nRed = Rhino.ColorRedValue(lngColor) nGreen = Rhino.ColorGreenValue(lngColor) nBlue = Rhino.ColorBlueValue(lngColor) ' Calculate the grayscale based on the NTSC color gamut nGray = CByte(nRed * 0.30) + CByte(nGreen * 0.59) + CByte(nBlue * 0.11) ' Modify the layer's color Call Rhino.LayerColor(strlayer, RGB(nGray, nGray, nGray)) Next ' Turn on screen redrawing Call Rhino.EnableRedraw(True) End Sub ``` -------------------------------------------------------------------------------- # Copy Groups Source: https://developer.rhino3d.com/en/samples/rhinocommon/copy-groups/ Demonstrates how to duplicate objects with grouping. ```cs partial class Examples { public static Result CopyGroups(RhinoDoc doc) { var go = new Rhino.Input.Custom.GetObject(); go.SetCommandPrompt("Select objects to copy in place"); go.GroupSelect = true; go.SubObjectSelect = false; go.GetMultiple(1, 0); if (go.CommandResult() != Result.Success) return go.CommandResult(); var xform = Transform.Identity; var group_map = new Dictionary(); foreach (var obj_ref in go.Objects()) { if (obj_ref != null) { var obj = obj_ref.Object(); var duplicate = doc.Objects.Transform(obj_ref.ObjectId, xform, false); RhinoUpdateObjectGroups(ref obj, ref group_map); } } doc.Views.Redraw(); return Result.Success; } static void RhinoUpdateObjectGroups(ref RhinoObject obj, ref Dictionary group_map) { if (obj == null) return; int attrib_group_count = obj.Attributes.GroupCount; if (attrib_group_count == 0) return; var doc = obj.Document; if (doc == null) return; var groups = doc.Groups; int group_count = groups.Count; if (group_count == 0) return; if (group_map.Count == 0) for (int i = 0; i < group_count; i++) group_map.Add(i, -1); var attributes = obj.Attributes; var group_list = attributes.GetGroupList(); if (group_list == null) return; attrib_group_count = group_list.Length; for (int i = 0; i < attrib_group_count; i++) { int old_group_index = group_list[i]; int new_group_index = group_map[old_group_index]; if (new_group_index == -1) { new_group_index = doc.Groups.Add(); group_map[old_group_index] = new_group_index; } group_list[i] = new_group_index; } attributes.RemoveFromAllGroups(); for (int i = 0; i < attrib_group_count; i++) attributes.AddToGroup(group_list[i]); obj.CommitChanges(); } } ``` ```python import Rhino from Rhino.Commands import * from scriptcontext import doc def RhinoUpdateObjectGroups(obj, group_map): if obj == None: return attrib_group_count = obj.Attributes.GroupCount if attrib_group_count == 0: return doc = obj.Document if doc == None: return groups = doc.Groups group_count = groups.Count if group_count == 0: return if group_map.Count == 0: for i in range(0, group_count): group_map.append(-1) attributes = obj.Attributes group_list = attributes.GetGroupList() if group_list == None: return attrib_group_count = group_list.Length for i in range(0, attrib_group_count): old_group_index = group_list[i] new_group_index = group_map[old_group_index] if new_group_index == -1: new_group_index = doc.Groups.Add() group_map[old_group_index] = new_group_index group_list[i] = new_group_index attributes.RemoveFromAllGroups() for i in range(0, attrib_group_count): attributes.AddToGroup(group_list[i]) obj.CommitChanges() def RunCommand(): go = Rhino.Input.Custom.GetObject() go.SetCommandPrompt("Select objects to copy in place") go.GroupSelect = True go.SubObjectSelect = False go.GetMultiple(1, 0) if go.CommandResult() != Result.Success: return go.CommandResult() xform = Rhino.Geometry.Transform.Identity group_map = [] for obj_ref in go.Objects(): if obj_ref != None: obj = obj_ref.Object() duplicate = doc.Objects.Transform(obj_ref.ObjectId, xform, False) RhinoUpdateObjectGroups(obj, group_map) doc.Views.Redraw() return Result.Success if __name__ == "__main__": RunCommand() ``` -------------------------------------------------------------------------------- # Copying to Excel Source: https://developer.rhino3d.com/en/guides/rhinoscript/copying-to-excel/ This guide demonstrates how to copy from Rhino and paste into Microsoft Excel using RhinoScript. ## Overview You can copy information from Rhino and then paste it into Excel. The real question is "what do you want to copy and paste?" Copying information from Rhino and pasting it into Excel is fairly easy using RhinoScript. All you have to do is use the `ClipboardText` method to copy a text string into the Windows Clipboard. Then from Excel, just select the cell and paste. ## Details If you want to copy the string “Hello from Rhino!” into Excel, your script could be as simple as this: ```vbnet Call Rhino.ClipboardText("Hello from Rhino!") ``` Easy enough. But, what if you want to copy some delimited data so each "token" appears in a different column in Excel when pasted. Then, simply separate each token with a tab, or `vbTab`, character. For example: ```vbnet Call Rhino.ClipboardText("A" & vbTab & "B" & vbTab & "C" & vbTab & "1" & vbTab & "2" & vbTab & "3") ``` Likewise, if you want to copy some delimited data so each "token" appears in a different row in Excel when pasted, then separate each token with a line-feed, or `vbLf`, character. For example: ```vbnet Call Rhino.ClipboardText("A" & vbLf & "B" & vbLf & "C" & vbLf & "1" & vbLf & "2" & vbLf & "3") ``` You can get more elaborate by creating a formatted string that contains both tab and line-feed characters. In this example, we will copy something useful - curve lengths in this example... ```vbnet Sub CopyClipCrvLength() Dim curves, crv, length, str curves = Rhino.GetObjects("Select curves to copy length", 4, True, True) If IsArray(curves) Then str = str & "Id" & vbTab & "Length" & vbLf For Each crv In curves length = Rhino.CurveLength(crv) str = str & crv & vbTab & CStr(length) & vbLf Next Call Rhino.ClipboardText(str) End If End Sub ``` Now that you have the basic idea, you should be able to write your own scripts that copy any type of data you want from Rhino into Excel. ## Related Topics - [Reading Excel Files](/guides/rhinoscript/reading-excel-files) - Automating Excel From RhinoScript (Sample) - Automating Curve Properties to Excel From RhinoScript (Sample) - Exporting Point Coordinates to Excel (Sample) -------------------------------------------------------------------------------- # Count Block Instances Source: https://developer.rhino3d.com/en/samples/rhinoscript/count-block-instances/ Demonstrates how to count block instances using RhinoScript. ```vbnet Sub CountAllInstancesOfAllBlocks arrNames = Rhino.BlockNames(True) If IsArray(arrNames) Then For Each strName In arrNames Rhino.Print strName & " = " & CStr(Rhino.BlockInstanceCount(strName)) Next End If End Sub ``` -------------------------------------------------------------------------------- # Count Objects Source: https://developer.rhino3d.com/en/samples/rhinoscript/count-objects/ Demonstrates how to count all the different object types using RhinoScript. ```vbnet ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' RhinoCountObjects.rvb -- December 2007 ' If this code works, it was written by Dale Fugier. ' If not, I don't know who wrote it. ' Works with Rhino 4.0. Option Explicit ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' Counts all objects Sub CountAllObjects Dim objects, obj objects = Rhino.AllObjects(False,True) If IsArray(objects) Then Call DoCounting(objects, True) Else Rhino.Print "No objects to count." End If End Sub ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' Counts selected objects Sub CountSelectedObjects Dim objects objects = Rhino.GetObjects("Select objects to count", 0, True, True) If IsArray(objects) Then Call DoCounting(objects, False) End If End Sub ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' Do the counting Sub DoCounting(objects, bTotal) If Not IsArray(objects) Then Exit Sub ' Declare constants Const rhPoint = 1 Const rhPointCloud = 2 Const rhCurve = 4 Const rhSurface = 8 Const rhPolysrf = 16 Const rhMesh = 32 Const rhLight = 256 Const rhAnnotation = 512 Const rhBlock = 4096 Const rhTextDot = 8192 Const rhGrip = 16384 Const rhDetail = 32768 Const rhHatch = 65536 Const rhMorph = 131072 Const rhCage = 134217728 Const rhPhantom = 268435456 Const rhClip = 536870912 ' Declare variables Dim points : points = 0 Dim pointclouds : pointclouds = 0 Dim curves : curves = 0 Dim surfaces : surfaces = 0 Dim polysrfs : polysrfs = 0 Dim meshes : meshes = 0 Dim lights : lights = 0 Dim annotations : annotations = 0 Dim blocks : blocks = 0 Dim textdots : textdots = 0 Dim grips : grips = 0 Dim details : details = 0 Dim hatches : hatches = 0 Dim morphs : morphs = 0 Dim cages : cages = 0 Dim phantoms : phantoms = 0 Dim clips : clips = 0 Dim obj ' Count them For Each obj In objects If Not Rhino.IsObjectReference(obj) Then Select Case Rhino.ObjectType(obj) Case rhPoint points = points + 1 Case rhPointCloud pointclouds = pointclouds + 1 Case rhCurve curves = curves + 1 Case rhSurface surfaces = surfaces + 1 Case rhPolysrf polysrfs = polysrfs + 1 Case rhMesh meshes = meshes + 1 Case rhLight lights = lights + 1 Case rhAnnotation annotations = annotations + 1 Case rhBlock blocks = blocks + 1 Case rhTextDot textdots = textdots + 1 Case rhGrip grips = grips + 1 Case rhDetail details = details + 1 Case rhHatch hatches = hatches + 1 Case rhMorph morphs = morphs + 1 Case rhCage cages = cages + 1 Case rhPhantom phantoms = phantoms + 1 Case rhClip clips = clips + 1 End Select End If Next ' Report them If (bTotal = True) Then Rhino.Print "Total object count = " & CStr(UBound(objects) + 1) Else Rhino.Print "Selected object count = " & CStr(UBound(objects) + 1) End If If (points > 0 ) Then Rhino.Print " Points = " & CStr(points) If (pointclouds > 0 ) Then Rhino.Print " Point clouds = " & CStr(pointclouds) If (curves > 0 ) Then Rhino.Print " Curves = " & CStr(curves) If (surfaces > 0 ) Then Rhino.Print " Surfaces = " & CStr(surfaces) If (polysrfs > 0 ) Then Rhino.Print " PolySurfaces = " & CStr(polysrfs) If (meshes > 0 ) Then Rhino.Print " Meshes = " & CStr(meshes) If (lights > 0 ) Then Rhino.Print " Lights = " & CStr(lights) If (annotations > 0 ) Then Rhino.Print " Annotations = " & CStr(annotations) If (blocks > 0 ) Then Rhino.Print " Blocks instances = " & CStr(blocks) If (textdots > 0 ) Then Rhino.Print " Text dots = " & CStr(textdots) If (grips > 0 ) Then Rhino.Print " Grip objects = " & CStr(grips) If (details > 0 ) Then Rhino.Print " Detailed views = " & CStr(details) If (hatches > 0 ) Then Rhino.Print " Hatches = " & CStr(hatches) If (morphs > 0 ) Then Rhino.Print " Morph objects = " & CStr(morphs) If (cages > 0 ) Then Rhino.Print " Cage objects = " & CStr(cages) If (phantoms > 0 ) Then Rhino.Print " Phantoms objects = " & CStr(phantoms) If (clips > 0 ) Then Rhino.Print " Clipping planes = " & CStr(clips) End Sub ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' Add this script to the list of scripts to load at startup Rhino.AddStartupScript Rhino.LastLoadedScriptFile ' Define command aliases Rhino.AddAlias "CountAllObjects", "_-RunScript (CountAllObjects)" Rhino.AddAlias "CountSelectedObjects", "_-RunScript (CountSelectedObjects)" ``` -------------------------------------------------------------------------------- # Crash Dump Analysis Source: https://developer.rhino3d.com/en/guides/cpp/crash-dump-analysis/ This guide discusses how to analyze crash dump files in Visual Studio. ## Overview If Rhino crashes, two files are created on the user's desktop: *RhinoCrashDump.dmp* and *RhinoCrashDump.3dm*. The *.3dm* file is Rhino's last ditch effort to save the model. The *.dmp* file can be used in Visual Studio to find the place in the source code where a Rhino plugin crashed. ### Configuration Before you can analyze crashes, you'll need to set up Visual Studio to help you get symbols. 1. **Enable Symbol Servers** 1. Open Visual Studio 1. From the **Tools** menu, click **Options** 1. Select **Debugging** > **General** and enable the following options: * *Enable source server support* * *Print source server diagnostic messages to the Output window* * *Allow source server for partial trust assemblies (Managed only)* * *Always run untrusted source server commands without prompting* 1. Select **Debugging** > **Symbols** 1. In the *Symbol file (.pdb) locations* box, add: * http://s3.symbols.rhino3d.com/symbols/dujour * https://msdl.microsoft.com/download/symbols 1. Optionally add these symbol servers if you need symbols for video driver related crashes: * https://driver-symbols.nvidia.com * https://download.amd.com/dir/bin_2018 * https://download.amd.com/dir/bin * https://software.intel.com/sites/downloads/symbols 1. In the *Cache symbols in this directory* folder, enter a folder where Visual Studio will cache symbols. Depending on the number of crashes you debug, this folder can get quite large. 1. Under *Symbol search preferences:* select **Search for all modules unless excluded** (Note that this will make debugging your project slow. To speed this up, select *Automatically choose what module symbols to search for*) ### Debugging Crashes 1. Start Visual Studio 1. From the **File** menu, click **Open** 1. Browse to the **RhinoCrashDump.dmp** file in the extracted folder 1. Click **Debug with Mixed** ### Try it! Below is a sample C++ plugin that will crash Rhino. To test out crash dump analysis: 1. Download and build the TestSdkCrash plugin. 1. Launch Rhino and load the plugin using the `PlugInManager` command. 1. Run the `TestSdkCrash` command. 1. While the *McNeel Error Reporting* dialog is displayed, copy the *RhinoCrashDump.dmp* from the desktop to some other location, and then click *Don't Send*. 1. Follow the steps above to analyze the crash dump. ### Storing symbols In order to get the most out of your crashes, you need to save the symbols (.pdb) and source code from each of the builds. You'll also need to add source information into your .pdb files before you store them in the symbol server. ### More [Working with Debug Symbols and Source](https://learn.microsoft.com/en-us/shows/visual-studio-2022-launch-event/tips-and-tricks-tips-for-working-with-debug-symbols-and-source) -------------------------------------------------------------------------------- # Create a NURBS Circle Source: https://developer.rhino3d.com/en/samples/cpp/create-nurbs-circle/ Demonstrates how to use ON_NurbsCurve to create a circle. ```cpp CRhinoCommand::result CCommandTest::RunCommand( const CRhinoCommandContext& context ) { int dimension = 3; BOOL bIsRational = TRUE; int order = 3; int cv_count = 9; ON_NurbsCurve nc( dimension, bIsRational, order, cv_count ); nc.SetCV( 0, ON_4dPoint(1.0, 0.0, 0.0, 1.0) ); nc.SetCV( 1, ON_4dPoint(0.707107, 0.707107, 0.0, 0.707107) ); nc.SetCV( 2, ON_4dPoint(0.0, 1.0, 0.0, 1.0) ); nc.SetCV( 3, ON_4dPoint(-0.707107, 0.707107, 0.0, 0.707107) ); nc.SetCV( 4, ON_4dPoint(-1.0, 0.0, 0.0, 1.0) ); nc.SetCV( 5, ON_4dPoint(-0.707107, -0.707107, 0.0, 0.707107) ); nc.SetCV( 6, ON_4dPoint(0.0, -1.0, 0.0, 1.0) ); nc.SetCV( 7, ON_4dPoint(0.707107, -0.707107, 0.0, 0.707107) ); nc.SetCV( 8, ON_4dPoint(1.0, 0.0, 0.0, 1.0) ); nc.SetKnot( 0, 0.0 ); nc.SetKnot( 1, 0.0 ); nc.SetKnot( 2, 0.5*ON_PI ); nc.SetKnot( 3, 0.5*ON_PI ); nc.SetKnot( 4, ON_PI ); nc.SetKnot( 5, ON_PI ); nc.SetKnot( 6, 1.5*ON_PI ); nc.SetKnot( 7, 1.5*ON_PI ); nc.SetKnot( 8, 2.0*ON_PI ); nc.SetKnot( 9, 2.0*ON_PI ); if( nc.IsValid() ) { context.m_doc.AddCurveObject( nc ); context.m_doc.Redraw(); } return CRhinoCommand::success; } ``` -------------------------------------------------------------------------------- # Create an Ellipsoid Parametrically Source: https://developer.rhino3d.com/en/samples/rhinoscript/create-an-ellipsoid-parametrically/ Demonstrates one way of creating a Ellipsoid using RhinoScript. ```vbnet '------------------------------------------------------------------------------ ' ParametricEllipsoid.rvb -- February 2012 ' If this code works, it was written by Dale Fugier. ' If not, I don't know who wrote it. ' Works with Rhino 4.0. ' ' Notes, the surface of the ellipsoid may be parameterized in several ways. ' One possible choice which singles out the 'z'-axis is: ' x = a * Cos(u) * Cos(v) ' y = b * Cos(u) * Sin(v) ' z = c * Sin(u) ' where: ' -pi/2 <= u <= pi/2, And -pi <= v <= pi '------------------------------------------------------------------------------ Option Explicit '------------------------------------------------------------------------------ ' ParametricEllipsoid '------------------------------------------------------------------------------ Sub ParametricEllipsoid() ' Variables Dim a, b, c Dim dom_u(1), dom_v(1), num_u(1), num_v(1) Dim i, j, st(1), uv(1), pts(), crvs() ' Prompt for coefficents a = Rhino.GetInteger("A Coefficient", 1, 1) If IsNull(a) Then Exit Sub b = Rhino.GetInteger("B Coefficient", 1, 1) If IsNull(b) Then Exit Sub c = Rhino.GetInteger("C Coefficient", 1, 1) If IsNull(c) Then Exit Sub ' Define domain of ellipsoid dom_u(0) = -(Rhino.PI / 2.0) dom_u(1) = Rhino.PI / 2.0 dom_v(0) = -(Rhino.PI) dom_v(1) = Rhino.PI ' Define a domain of point samples ' To sample more points, increase num_u(1) and num_v(1) num_u(0) = 0 num_u(1) = 20 num_v(0) = 0 num_v(1) = 20 ' Storate for calculated point and temporary curves ReDim pts(num_u(1)) ReDim crvs(num_v(1)) ' Calculate ellipsoid points and interpolate For j = num_v(0) To num_v(1) For i = num_u(0) To num_u(1) st(0) = NormalizedAt(num_u(0), num_u(1), i) st(1) = NormalizedAt(num_v(0), num_v(1), j) uv(0) = ParameterAt(dom_u(0), dom_u(1), st(0)) uv(1) = ParameterAt(dom_v(0), dom_v(1), st(1)) pts(i) = PointAt(a, b, c, uv(0), uv(1)) Next crvs(j) = Rhino.AddInterpCurve(pts) Next ' Loft the results Call Rhino.AddLoftSrf(crvs) ' Delete the temporary curves 'Call Rhino.DeleteObjects(crvs) End Sub '------------------------------------------------------------------------------ ' PointAt ' Evaluates an ellipsoid at a parameter '------------------------------------------------------------------------------ Function PointAt(a, b, c, u, v) Dim pt(2) pt(0) = a * Cos(u) * Cos(v) pt(1) = b * Cos(u) * Sin(v) pt(2) = c * Sin(u) PointAt = pt End Function '------------------------------------------------------------------------------ ' ParameterAt ' Converts a normalized parameter to a domain value '------------------------------------------------------------------------------ Function ParameterAt(t0, t1, x) ParameterAt = (1.0 - x) * t0 + x * t1 End Function '------------------------------------------------------------------------------ ' NormalizedAt ' Converts a domain value to a normalized parameter '------------------------------------------------------------------------------ Function NormalizedAt(t0, t1, t) Dim x : x = t0 If (t0 <> t1) Then If (t = t1) Then x = 1.0 Else x = (t - t0)/(t1 - t0) End If End If NormalizedAt = x End Function '------------------------------------------------------------------------------ Rhino.AddStartUpScript Rhino.LastLoadedScriptFile Rhino.AddAlias "ParametricEllipsoid", "_-RunScript (ParametricEllipsoid)" ``` -------------------------------------------------------------------------------- # Create an Icosahedron Source: https://developer.rhino3d.com/en/samples/rhinoscript/create-an-icosahedron/ Demonstrates one way of creating a Icosahedron in RhinoScript. ```vbnet ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' Icosahedron.rvb -- September 2009 ' If this code works, it was written by Dale Fugier. ' If not, I don't know who wrote it. ' Works with Rhino 4.0. Option Explicit ' Creates a icosahedron ' Vertices: 12 ' Edges: 30 ' Faces: 20 ' Edges per face: 3 ' Edges per vertex: 5 ' Sin of angle at edge: 2 / 3 ' Surface area: 5 * sqrt(3) * edgelength^2 ' Volume: 5 * (3 + sqrt(5)) / 12 * edgelength^3 ' Circumscribed radius: sqrt(10 + 2 * sqrt(5)) / 4 * edgelength ' Inscribed radius: sqrt(42 + 18 * sqrt(5)) / 12 * edgelength ' Coordinates: a and b, where: ' a = 1 / 2 and b = 1 / (2 * phi) ' phi is the golden ratio = (1 + sqrt(5)) / 2 Sub Icosahedron() ' Declare local variables Dim radius, center Dim sqr5, phi, ratio, a, b Dim v(11), s(19) ' Prompt for center point center = Rhino.GetPoint("Center of icosahedral") If IsNull(center) Then Exit Sub ' Prompt for radius radius = Rhino.GetDistance(center, 1.0, "Radius") If IsNull(radius) Then Exit Sub ' This will make the script run faster Call Rhino.EnableRedraw(False) ' Phi - the square root of 5 plus 1 divided by 2 sqr5 = Sqr(5.0) phi = (1.0 + Sqr5) * 0.5 ' Golden ratio - the ratio of edgelength to radius ratio = Sqr(10.0 + (2.0 * sqr5)) / (4.0 * phi) a = (radius / ratio) * 0.5 b = (radius / ratio) / (2.0 * phi) ' Define the icosahedron's 12 vertices v(0) = Rhino.PointAdd(center, Array( 0, b, -a)) v(1) = Rhino.PointAdd(center, Array( b, a, 0)) v(2) = Rhino.PointAdd(center, Array(-b, a, 0)) v(3) = Rhino.PointAdd(center, Array( 0, b, a)) v(4) = Rhino.PointAdd(center, Array( 0, -b, a)) v(5) = Rhino.PointAdd(center, Array(-a, 0, b)) v(6) = Rhino.PointAdd(center, Array( 0, -b, -a)) v(7) = Rhino.PointAdd(center, Array( a, 0, -b)) v(8) = Rhino.PointAdd(center, Array( a, 0, b)) v(9) = Rhino.PointAdd(center, Array(-a, 0, -b)) v(10) = Rhino.PointAdd(center, Array( b, -a, 0)) v(11) = Rhino.PointAdd(center, Array(-b, -a, 0)) ' Create the icosahedron's 20 triangular faces s(0) = Rhino.AddSrfPt(Array(v(0), v(1), v(2))) s(1) = Rhino.AddSrfPt(Array(v(3), v(2), v(1))) s(2) = Rhino.AddSrfPt(Array(v(3), v(4), v(5))) s(3) = Rhino.AddSrfPt(Array(v(3), v(8), v(4))) s(4) = Rhino.AddSrfPt(Array(v(0), v(6), v(7))) s(5) = Rhino.AddSrfPt(Array(v(0), v(9), v(6))) s(6) = Rhino.AddSrfPt(Array(v(4), v(10), v(11))) s(7) = Rhino.AddSrfPt(Array(v(6), v(11), v(10))) s(8) = Rhino.AddSrfPt(Array(v(2), v(5), v(9))) s(9) = Rhino.AddSrfPt(Array(v(11), v(9), v(5))) s(10) = Rhino.AddSrfPt(Array(v(1), v(7), v(8))) s(11) = Rhino.AddSrfPt(Array(v(10), v(8), v(7))) s(12) = Rhino.AddSrfPt(Array(v(3), v(5), v(2))) s(13) = Rhino.AddSrfPt(Array(v(3), v(1), v(8))) s(14) = Rhino.AddSrfPt(Array(v(0), v(2), v(9))) s(15) = Rhino.AddSrfPt(Array(v(0), v(7), v(1))) s(16) = Rhino.AddSrfPt(Array(v(6), v(9), v(11))) s(17) = Rhino.AddSrfPt(Array(v(6), v(10), v(7))) s(18) = Rhino.AddSrfPt(Array(v(4), v(11), v(5))) s(19) = Rhino.AddSrfPt(Array(v(4), v(8), v(10))) ' Join all of the faces Rhino.UnselectAllObjects() Call Rhino.SelectObjects(s) Call Rhino.Command("_Join", False) Rhino.UnselectAllObjects() ' Don't forget to do this Call Rhino.EnableRedraw(True) End Sub ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Rhino.AddStartupScript Rhino.LastLoadedScriptFile Rhino.AddAlias "Icosahedron", "_NoEcho _-RunScript (Icosahedron)" ``` -------------------------------------------------------------------------------- # Create Block Definition Source: https://developer.rhino3d.com/en/samples/rhinocommon/create-block-definition/ Demonstrates how to create a block definition from scratch from user-specified objects, base-point, and name. ```cs partial class Examples { public static Rhino.Commands.Result CreateBlock(Rhino.RhinoDoc doc) { // Select objects to define block var go = new Rhino.Input.Custom.GetObject(); go.SetCommandPrompt( "Select objects to define block" ); go.ReferenceObjectSelect = false; go.SubObjectSelect = false; go.GroupSelect = true; // Phantoms, grips, lights, etc., cannot be in blocks. const ObjectType forbidden_geometry_filter = Rhino.DocObjects.ObjectType.Light | Rhino.DocObjects.ObjectType.Grip | Rhino.DocObjects.ObjectType.Phantom; const ObjectType geometry_filter = forbidden_geometry_filter ^ Rhino.DocObjects.ObjectType.AnyObject; go.GeometryFilter = geometry_filter; go.GetMultiple(1, 0); if (go.CommandResult() != Rhino.Commands.Result.Success) return go.CommandResult(); // Block base point Rhino.Geometry.Point3d base_point; var rc = Rhino.Input.RhinoGet.GetPoint("Block base point", false, out base_point); if (rc != Rhino.Commands.Result.Success) return rc; // Block definition name string idef_name = ""; rc = Rhino.Input.RhinoGet.GetString("Block definition name", false, ref idef_name); if (rc != Rhino.Commands.Result.Success) return rc; // Validate block name idef_name = idef_name.Trim(); if (string.IsNullOrEmpty(idef_name)) return Rhino.Commands.Result.Nothing; // See if block name already exists Rhino.DocObjects.InstanceDefinition existing_idef = doc.InstanceDefinitions.Find(idef_name); if (existing_idef != null) { Rhino.RhinoApp.WriteLine("Block definition {0} already exists", idef_name); return Rhino.Commands.Result.Nothing; } // Gather all of the selected objects var geometry = new System.Collections.Generic.List(); var attributes = new System.Collections.Generic.List(); for (int i = 0; i < go.ObjectCount; i++) { var rhinoObject = go.Object(i).Object(); if (rhinoObject != null) { geometry.Add(rhinoObject.Geometry); attributes.Add(rhinoObject.Attributes); } } // Gather all of the selected objects int idef_index = doc.InstanceDefinitions.Add(idef_name, string.Empty, base_point, geometry, attributes); if( idef_index < 0 ) { Rhino.RhinoApp.WriteLine("Unable to create block definition", idef_name); return Rhino.Commands.Result.Failure; } return Rhino.Commands.Result.Success; } } ``` ```python import Rhino import scriptcontext def CreateBlock(): # Select objects to define block go = Rhino.Input.Custom.GetObject() go.SetCommandPrompt( "Select objects to define block" ) go.ReferenceObjectSelect = False go.SubObjectSelect = False go.GroupSelect = True # Phantoms, grips, lights, etc., cannot be in blocks. forbidden_geometry_filter = Rhino.DocObjects.ObjectType.Light | Rhino.DocObjects.ObjectType.Grip | Rhino.DocObjects.ObjectType.Phantom geometry_filter = forbidden_geometry_filter ^ Rhino.DocObjects.ObjectType.AnyObject go.GeometryFilter = geometry_filter go.GetMultiple(1, 0) if go.CommandResult() != Rhino.Commands.Result.Success: return go.CommandResult() # Block base point rc, base_point = Rhino.Input.RhinoGet.GetPoint("Block base point", False) if rc != Rhino.Commands.Result.Success: return rc # Block definition name rc, idef_name = Rhino.Input.RhinoGet.GetString("Block definition name", False, "") if rc != Rhino.Commands.Result.Success: return rc # Validate block name idef_name = idef_name.strip() if not idef_name: return Rhino.Commands.Result.Nothing # See if block name already exists existing_idef = scriptcontext.doc.InstanceDefinitions.Find(idef_name, True) if existing_idef: print("Block definition", idef_name, "already exists") return Rhino.Commands.Result.Nothing # Gather all of the selected objects objrefs = go.Objects() geometry = [item.Object().Geometry for item in objrefs] attributes = [item.Object().Attributes for item in objrefs] # Add the instance definition idef_index = scriptcontext.doc.InstanceDefinitions.Add(idef_name, "", base_point, geometry, attributes) if idef_index<0: print("Unable to create block definition", idef_name) return Rhino.Commands.Result.Failure return Rhino.Commands.Result.Success if __name__=="__main__": CreateBlock() ``` -------------------------------------------------------------------------------- # Create Bounding Polyline of a Mesh Source: https://developer.rhino3d.com/en/samples/cpp/create-bounding-polyline-of-mesh/ Demonstrates how to create a bounding polyline of a mesh object. ```cpp CRhinoCommand::result CCommandTest::RunCommand( const CRhinoCommandContext& context ) { // Select an open mesh object CRhinoGetObject go; go.SetCommandPrompt( L"Select open mesh" ); go.SetGeometryFilter( CRhinoGetObject::mesh_object ); go.SetGeometryAttributeFilter( CRhinoGetObject::open_mesh ); go.GetObjects( 1, 1 ); if( go.CommandResult() != success ) return go.CommandResult(); // Validate the selection const CRhinoObjRef& ref = go.Object(0); const ON_Mesh* mesh = ref.Mesh(); if( 0 == mesh ) return failure; // Get the mesh's topology const ON_MeshTopology& mesh_top = mesh->Topology(); ON_SimpleArray lines( mesh_top.m_tope.Count() ); // Find all of the mesh edges that have only a single mesh face int i; for( i = 0; i < mesh_top.m_tope.Count(); i++ ) { const ON_MeshTopologyEdge& mesh_edge = mesh_top.m_tope[i]; if( mesh_edge.m_topf_count == 1 ) { ON_3fPoint p0 = mesh_top.TopVertexPoint( mesh_edge.m_topvi[0] ); ON_3fPoint p1 = mesh_top.TopVertexPoint( mesh_edge.m_topvi[1] ); ON_LineCurve* line = new ON_LineCurve( ON_3dPoint(p0), ON_3dPoint(p1) ); lines.Append( line ); } } ON_SimpleArray output; double tolerance = 2.1 * context.m_doc.AbsoluteTolerance(); // Join all of the line segments if( RhinoMergeCurves(lines, output, tolerance) ) { for( i = 0; i < output.Count(); i++ ) { CRhinoCurveObject* crv = new CRhinoCurveObject; crv->SetCurve( output[i] ); if( context.m_doc.AddObject(crv) ) crv->Select(); else delete crv; } } // Clean up so we don't leak memory for( i = 0; i < lines.Count(); i++ ) delete lines[i]; return success; } ``` -------------------------------------------------------------------------------- # Create Box Frames Source: https://developer.rhino3d.com/en/samples/rhinoscript/create-box-frames/ Demonstrates how to create a box frame with RhinoScript. ```vbnet ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' BoxFrame.rvb -- November 2009 ' If this code works, it was written by Dale Fugier. ' If not, I don't know who wrote it. ' Works with Rhino 4.0. ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Option Explicit Sub BoxFrame() ' Local constants Const X = 0 Const Y = 1 Const Z = 2 Const TOL = 0.01 ' Local variables Dim box_points, box_object, box_center, box_plane Dim offset(2), dist(2), pfact(2), nfact(2) Dim xform(2), boxes(2) ' Prompt the user to define a box box_points = Rhino.GetBox() If IsNull(box_points) Then Exit Sub ' Calculate half distances between corners dist(X) = Rhino.Distance(box_points(0), box_points(1)) * 0.5 dist(Y) = Rhino.Distance(box_points(0), box_points(3)) * 0.5 dist(Z) = Rhino.Distance(box_points(0), box_points(4)) * 0.5 ' Define axes offsets offset(X) = Rhino.GetReal("Thickness in X direction", 1.0, TOL, dist(X) - TOL) If IsNull(offset(X)) Then Exit Sub offset(Y) = Rhino.GetReal("Thickness in Y direction", 1.0, TOL, dist(Y) - TOL) If IsNull(offset(Y)) Then Exit Sub offset(Z) = Rhino.GetReal("Thickness in Z direction", 1.0, TOL, dist(Z) - TOL) If IsNull(offset(Z)) Then Exit Sub ' Do this for performance Call Rhino.EnableRedraw(False) ' Calculate increasing scale factors pfact(X) = (dist(X) + offset(X)) / dist(X) pfact(Y) = (dist(Y) + offset(Y)) / dist(Y) pfact(Z) = (dist(Z) + offset(Z)) / dist(Z) ' Calculate decreasing scale factors nfact(X) = (dist(X) - offset(X)) / dist(X) nfact(Y) = (dist(Y) - offset(Y)) / dist(Y) nfact(Z) = (dist(Z) - offset(Z)) / dist(Z) ' Some stuff need for the scale transformation box_center = Rhino.PointDivide(Rhino.PointAdd(box_points(0), box_points(6)), 2.0) box_plane = Rhino.PlaneFromPoints(box_points(0), box_points(1), box_points(3)) box_plane = Rhino.MovePlane(box_plane, box_center) ' Define scale transformations xform(0) = Rhino.XformScale(box_plane, pfact(0), nfact(1), nfact(2)) xform(1) = Rhino.XformScale(box_plane, nfact(0), pfact(1), nfact(2)) xform(2) = Rhino.XformScale(box_plane, nfact(0), nfact(1), pfact(2)) ' Create the box box_object = Rhino.AddBox(box_points) ' Create the boxes to difference boxes(0) = Rhino.TransformObject(box_object, xform(0), True) boxes(1) = Rhino.TransformObject(box_object, xform(1), True) boxes(2) = Rhino.TransformObject(box_object, xform(2), True) ' Do the difference Call Rhino.BooleanDifference(Array(box_object), boxes, True) ' Enable redraw Call Rhino.EnableRedraw(True) End Sub ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Rhino.AddStartupScript Rhino.LastLoadedScriptFile Rhino.AddAlias "BoxFrame", "_NoEcho _-RunScript (BoxFrame)" ``` -------------------------------------------------------------------------------- # Create Center Point on Closed Curve Source: https://developer.rhino3d.com/en/samples/rhinoscript/create-center-point-on-closed-curve/ Demonstrates how to mark the center points of closed planar curves with a point object using RhinoScript. ```vbnet Sub MarkCenterPoints() Dim curves, crv, pt, arr curves = Rhino.GetObjects("Select closed planar curves", 4, ,True) If IsArray(curves) Then Rhino.EnableRedraw False For Each crv In curves pt = vbNull If Rhino.IsCircle(crv) Then pt = Rhino.CircleCenterPoint(crv) Else arr = Rhino.CurveAreaCentroid(crv) If IsArray(arr) Then pt = arr(0) End If If IsArray(pt) Then Rhino.SelectObject Rhino.AddPoint(pt) End If Next Rhino.EnableRedraw True End If End Sub ``` -------------------------------------------------------------------------------- # Create Contour Curves Source: https://developer.rhino3d.com/en/samples/cpp/create-contour-curves/ Demonstrates how to create contour curves through surfaces, breps, and meshes using the MakeRhinoContours() function. ```cpp CRhinoCommand::result CCommandTest::RunCommand( const CRhinoCommandContext& context ) { unsigned int filter = CRhinoGetObject::surface_object | CRhinoGetObject::polysrf_object | CRhinoGetObject::mesh_object; // Prompt for object to contour CRhinoGetObject go; go.SetCommandPrompt( L"Select object to contour" ); go.SetGeometryFilter( filter ); go.GetObjects( 1, 1 ); if( go.CommandResult() != success ) return go.CommandResult(); // Validate selection const CRhinoObjRef& objref = go.Object(0); const ON_Geometry* geom = objref.Geometry(); if( !geom ) return failure; // Prompt for base point CRhinoGetPoint gp; gp.SetCommandPrompt( L"Contour plane base point" ); gp.GetPoint(); if( gp.CommandResult() != success ) return gp.CommandResult(); ON_3dPoint basept = gp.Point(); // Prompt for end point gp.DrawLineFromPoint( basept, true ); gp.SetCommandPrompt( L"Direction perpendicular to contour planes" ); gp.GetPoint(); if( gp.CommandResult() != success ) return gp.CommandResult(); // Verify base and end points are not the same ON_3dPoint endpt = gp.Point(); if( basept.DistanceTo(endpt) < ON_ZERO_TOLERANCE ) return nothing; // Prompt for distance between contours CRhinoGetDistance gd; gd.SetCommandPrompt( L"Distance between contours" ); gd.SetDefaultNumber( 1.0 ); gd.GetDistance(); if( gd.CommandResult() != success ) return gd.CommandResult(); // Make sure interval is reasonable double interval = fabs( gd.Distance() ); if( interval < ON_ZERO_TOLERANCE ) return nothing; // Create contour input CRhinoContourInput contour; contour.m_geom.Append( geom ); contour.m_basept = basept; contour.m_endpt = endpt; contour.m_interval = interval; contour.m_limit_range = false; // Create arrays for contour output ON_SimpleArray pline_array; ON_SimpleArray crv_array; // Make the contours. Note, this function allocates memory // for new curves and polylines. Thus, we are responsible // for clean up the memory. bool rc = MakeRhinoContours( contour, pline_array, crv_array ); if( !rc ) return failure; context.m_doc.UnselectAll(); // Process crves created by contouring surfaces and polysurfaces for( int i = 0; i < crv_array.Count(); i++) { ON_Curve* crv = crv_array[i]; if( crv ) { CRhinoCurveObject* crvobj = context.m_doc.AddCurveObject( *crv ); if( crvobj ) crvobj->Select(); // CRhinoDoc::AddCurveObject() makes a copy of the input. // Thus, we must delete crv, otherwise we will leak memory. delete crv; crv_array[i] = 0; } } // Process polylines created by contouring meshes for( int i = 0; i < pline_array.Count(); i++) { ON_Polyline* pline = pline_array[i]; if( pline ) { CRhinoCurveObject* crvobj = context.m_doc.AddCurveObject( *pline ); if( crvobj ) crvobj->Select(); // CRhinoDoc::AddCurveObject() makes a copy of the input. // Thus, we must delete pline, otherwise we will leak memory. delete pline; pline_array[i] = 0; } } context.m_doc.Redraw(); return success; } ``` -------------------------------------------------------------------------------- # Create Contour Curves Source: https://developer.rhino3d.com/en/samples/rhinocommon/create-contour-curves/ Demonstrates how to create contour curves on user-specified objects. ```cs partial class Examples { public static Result ContourCurves(RhinoDoc doc) { var filter = ObjectType.Surface | ObjectType.PolysrfFilter | ObjectType.Mesh; ObjRef[] obj_refs; var rc = RhinoGet.GetMultipleObjects("Select objects to contour", false, filter, out obj_refs); if (rc != Result.Success) return rc; var gp = new GetPoint(); gp.SetCommandPrompt("Contour plane base point"); gp.Get(); if (gp.CommandResult() != Result.Success) return gp.CommandResult(); var base_point = gp.Point(); gp.DrawLineFromPoint(base_point, true); gp.SetCommandPrompt("Direction perpendicular to contour planes"); gp.Get(); if (gp.CommandResult() != Result.Success) return gp.CommandResult(); var end_point = gp.Point(); if (base_point.DistanceTo(end_point) < RhinoMath.ZeroTolerance) return Result.Nothing; double distance = 1.0; rc = RhinoGet.GetNumber("Distance between contours", false, ref distance); if (rc != Result.Success) return rc; var interval = Math.Abs(distance); Curve[] curves = null; foreach (var obj_ref in obj_refs) { var geometry = obj_ref.Geometry(); if (geometry == null) return Result.Failure; if (geometry is Brep) { curves = Brep.CreateContourCurves(geometry as Brep, base_point, end_point, interval); } else { curves = Mesh.CreateContourCurves(geometry as Mesh, base_point, end_point, interval); } foreach (var curve in curves) { var curve_object_id = doc.Objects.AddCurve(curve); doc.Objects.Select(curve_object_id); } } if (curves != null) doc.Views.Redraw(); return Result.Success; } } ``` ```python from System import * from Rhino import * from Rhino.DocObjects import * from Rhino.Geometry import * from Rhino.Input import * from Rhino.Input.Custom import * from Rhino.Commands import * from scriptcontext import doc def RunCommand(): filter = ObjectType.Surface | ObjectType.PolysrfFilter | ObjectType.Mesh rc, obj_refs = RhinoGet.GetMultipleObjects("Select objects to contour", False, filter) if rc != Result.Success: return rc gp = GetPoint() gp.SetCommandPrompt("Contour plane base point") gp.Get() if gp.CommandResult() != Result.Success: return gp.CommandResult() base_point = gp.Point() gp.DrawLineFromPoint(base_point, True) gp.SetCommandPrompt("Direction perpendicular to contour planes") gp.Get() if gp.CommandResult() != Result.Success: return gp.CommandResult() end_point = gp.Point() if base_point.DistanceTo(end_point) < RhinoMath.ZeroTolerance: return Result.Nothing distance = 1.0 rc, distance = RhinoGet.GetNumber("Distance between contours", False, distance) if rc != Result.Success: return rc interval = Math.Abs(distance) for obj_ref in obj_refs: geometry = obj_ref.Geometry() if geometry == None: return Result.Failure if type(geometry) == Brep: curves = Brep.CreateContourCurves(geometry, base_point, end_point, interval) else: curves = Mesh.CreateContourCurves(geometry, base_point, end_point, interval) for curve in curves: curve_object_id = doc.Objects.AddCurve(curve) doc.Objects.Select(curve_object_id) if curves != None: doc.Views.Redraw() return Result.Success if __name__ == "__main__": RunCommand() ``` -------------------------------------------------------------------------------- # Create CutPlane Source: https://developer.rhino3d.com/en/samples/rhinoscript/create-cutplane/ Demonstrates how to script the CutPlane command using RhinoScript. ```vbnet Function CreateCutPlane(objs, p0, p1) ' Declare local variables Dim saved, s0, s1, cmd ' Set default return value CreateCutPlane = Null ' For speed, turn of screen redrawing Rhino.EnableRedraw False ' Save any selected objects saved = Rhino.SelectedObjects ' Unselect all objects Rhino.UnSelectAllObjects ' Select the objects to create the cut plane through Rhino.SelectObjects objs ' Script the cutplane command s0 = Rhino.Pt2Str(p0,,True) s1 = Rhino.Pt2Str(p1,,True) cmd = "_CutPlane " & s0 & s1 & "_Enter" Rhino.Command cmd, 0 ' Get the object created by CutPlane CreateCutPlane = Rhino.FirstObject ' Unselect all objects Rhino.UnSelectAllObjects ' If any objects were selected before calling ' this function, re-select them If IsArray(saved) Then Rhino.SelectObjects(saved) ' Don't forget to turn redrawing back on Rhino.EnableRedraw True End Function ``` -------------------------------------------------------------------------------- # Create Mesh from Brep Source: https://developer.rhino3d.com/en/samples/rhinocommon/create-mesh-from-brep/ Demonstrates how to create a mesh from a selected surface or polysurface. ```cs partial class Examples { public static Result CreateMeshFromBrep(RhinoDoc doc) { ObjRef obj_ref; var rc = RhinoGet.GetOneObject("Select surface or polysurface to mesh", true, ObjectType.Surface | ObjectType.PolysrfFilter, out obj_ref); if (rc != Result.Success) return rc; var brep = obj_ref.Brep(); if (null == brep) return Result.Failure; // you could choose anyone of these for example var jagged_and_faster = MeshingParameters.Coarse; var smooth_and_slower = MeshingParameters.Smooth; var default_mesh_params = MeshingParameters.Default; var minimal = MeshingParameters.Minimal; var meshes = Mesh.CreateFromBrep(brep, smooth_and_slower); if (meshes == null || meshes.Length == 0) return Result.Failure; var brep_mesh = new Mesh(); foreach (var mesh in meshes) brep_mesh.Append(mesh); doc.Objects.AddMesh(brep_mesh); doc.Views.Redraw(); return Result.Success; } } ``` ```python import Rhino from Rhino.Geometry import * from Rhino.Input import RhinoGet from Rhino.Commands import Result from Rhino.DocObjects import ObjectType import rhinoscriptsyntax as rs from scriptcontext import doc def RunCommand(): rc, objRef = RhinoGet.GetOneObject("Select surface or polysurface to mesh", True, ObjectType.Surface | ObjectType.PolysrfFilter) if rc != Result.Success: return rc brep = objRef.Brep() if None == brep: return Result.Failure jaggedAndFaster = MeshingParameters.Coarse smoothAndSlower = MeshingParameters.Smooth defaultMeshParams = MeshingParameters.Default minimal = MeshingParameters.Minimal meshes = Mesh.CreateFromBrep(brep, smoothAndSlower) if meshes == None or meshes.Length == 0: return Result.Failure brepMesh = Mesh() for mesh in meshes: brepMesh.Append(mesh) doc.Objects.AddMesh(brepMesh) doc.Views.Redraw() if __name__ == "__main__": RunCommand() ``` -------------------------------------------------------------------------------- # Create New View Source: https://developer.rhino3d.com/en/samples/cpp/create-new-view/ Demonstrates how to create a new view that has the properties of an existing view. ```cpp CRhinoCommand::result CCommandTest::RunCommand( const CRhinoCommandContext& context ) { AFX_MANAGE_STATE( ::RhinoApp().RhinoModuleState() ); ON_SimpleArray viewport_ids; ON_SimpleArray view_list; CRhinoView* view = 0; int i = 0; // Build a list of (current) viewport ids context.m_doc.GetViewList( view_list, true, false ); for( i = 0; i < view_list.Count(); i++ ) { CRhinoView* view = view_list[i]; if( view ) viewport_ids.Append( view->ActiveViewportID() ); } view_list.Empty(); // Create a new view context.m_doc.NewView( ON_3dmView() ); // Find the viewport (id) that was just created context.m_doc.GetViewList( view_list, true, false ); for( i = 0; i < view_list.Count(); i++ ) { view = view_list[i]; if( view ) { int rc = viewport_ids.Search( view->ActiveViewportID() ); if( rc < 0 ) break; else view = 0; } } // Make the necessary updates. if( view ) { ON_3dmView v = view->ActiveViewport().View(); v.m_name = L"New View"; view->ActiveViewport().SetView( v ); view->Redraw(); } return CRhinoCommand::success; } ``` -------------------------------------------------------------------------------- # Create NURBS Surface Source: https://developer.rhino3d.com/en/samples/cpp/create-nurbs-surface/ Demonstrates how to create a NURBS surface. ```cpp static bool CreateSurfacesExample( CRhinoDoc& doc ) { const int bIsRational = false; const int dim = 3; const int u_degree = 2; const int v_degree = 3; const int u_cv_count = 3; const int v_cv_count = 5; // The knot vectors do NOT have the 2 superfluous knots // at the start and end of the knot vector. If you are // coming from a system that has the 2 superfluous knots, // just ignore them when creating NURBS surfaces. double u_knot[ u_cv_count + u_degree - 1 ]; double v_knot[ v_cv_count + v_degree - 1 ]; // make up a quadratic knot vector with no interior knots u_knot[0] = u_knot[1] = 0.0; u_knot[2] = u_knot[3] = 1.0; // make up a cubic knot vector with one simple interior knot v_knot[0] = v_knot[1] = v_knot[2] = 0.0; v_knot[3] = 1.5; v_knot[4] = v_knot[5] = v_knot[6] = 2.0; // Rational control points can be in either homogeneous // or euclidean form. Non-rational control points do not // need to specify a weight. ON_3dPoint CV[u_cv_count][v_cv_count]; int i, j; for ( i = 0; i < u_cv_count; i++ ) { for ( j = 0; j < v_cv_count; j++ ) { CV[i][j].x = i; CV[i][j].y = j; CV[i][j].z = i-j; } } ON_NurbsSurface nurbs_surface( dim, bIsRational, u_degree+1, v_degree+1, u_cv_count, v_cv_count ); for ( i = 0; i < nurbs_surface.KnotCount(0); i++ ) nurbs_surface.SetKnot( 0, i, u_knot[i] ); for ( j = 0; j < nurbs_surface.KnotCount(1); j++ ) nurbs_surface.SetKnot( 1, j, v_knot[j] ); for ( i = 0; i < nurbs_surface.CVCount(0); i++ ) { for ( j = 0; j < nurbs_surface.CVCount(1); j++ ) { nurbs_surface.SetCV( i, j, CV[i][j] ); } } bool ok = false; if ( nurbs_surface.IsValid() ) { doc.AddSurfaceObject( nurbs_surface ); doc.Redraw(); ok = true; } return ok; } ``` -------------------------------------------------------------------------------- # Create Plane Surface Source: https://developer.rhino3d.com/en/samples/cpp/create-plane-surface/ Demonstrates how to create a plane surface. ```cpp CArgsRhinoGetPlane args; args.SetFirstPointPromptCorners( L"First corner of plane" ); args.SetSecondPointPromptCorners( L"Other corner or length" ); args.SetFirstPointPrompt3Point( L"Start of edge" ); args.SetSecondPointPrompt3Point( L"End of edge" ); args.SetThirdPointPrompt3Point( L"Width. Press Enter to use length" ); args.SetFirstPointPromptVertical( L"Start of edge" ); args.SetSecondPointPromptVertical( L"End of edge" ); args.SetThirdPointPromptVertical( L"Height. Press Enter to use width" ); args.SetFirstPointPromptCenter( L"Center of plane" ); args.SetSecondPointPromptCenter( L"Other corner or length" ); args.SetAllow3Point(); args.SetAllowCenter(); args.SetAllowVertical(); args.SetAllowRounded( false ); args.SetAllowDeformable( false ); ON_3dPoint corners[4]; CRhinoCommand::result rc = RhinoGetRectangle( args, corners ); if( rc == CRhinoCommand::success) { ON_3dPoint& p0 = corners[0]; ON_3dPoint& p1 = corners[1]; ON_3dPoint& p3 = corners[3]; ON_Interval domain0, domain1; domain0.Set( 0.0, p0.DistanceTo(p1) ); domain1.Set( 0.0, p0.DistanceTo(p3) ); ON_Plane plane( p0, p1, p3 ); ON_PlaneSurface ps( plane ); ps.SetExtents( 0, domain0, true ); ps.SetExtents( 1, domain1, true ); ps.SetDomain( 0, domain0.Min(), domain0.Max() ); ps.SetDomain( 1, domain1.Min(), domain1.Max() ); context.m_doc.AddSurfaceObject( ps ); context.m_doc.Redraw(); } ``` -------------------------------------------------------------------------------- # Create Principal Curvature Curves Source: https://developer.rhino3d.com/en/guides/cpp/creating-principal-curvature-curves/ This guide demonstrates how to use the ON_EvPrincipalCurvatures function in C/C++. ## Problem You are looking for a way to create Principal Curvature lines starting with points on a surface. There is an `ON_EvPrincipalCurvatures` function, it's not clear how it should be used. ## Solution Before using `ON_EvPrincipalCurvatures`, you will need to calculate the second derivative of the surface that the test location. Then, it is just a matter of creating up some curves based on the results. ## Sample The following is an example of how you might write such a command. ```cpp CRhinoCommand::result CCommandTest::RunCommand(const CRhinoCommandContext& context) { CRhinoGetObject go; go.SetCommandPrompt(L"Select surface"); go.SetGeometryFilter(CRhinoGetObject::surface_object); go.GetObjects(1, 1); if (go.CommandResult() != CRhinoCommand::success) return go.CommandResult(); const CRhinoObject* obj = go.Object(0).Object(); const ON_BrepFace* face = go.Object(0).Face(); if (nullptr == obj || nullptr == face) return CRhinoCommand::failure; CRhinoGetPoint gp; gp.SetCommandPrompt(L"Select point on surface"); gp.Constrain(*face, obj->Attributes().m_wire_density); gp.GetPoint(); if (gp.CommandResult() != CRhinoCommand::success) return gp.CommandResult(); double s = 0.0, t = 0.0; const ON_Surface* srf = gp.PointOnSurface(&s, &t); if (nullptr == srf) return CRhinoCommand::failure; ON_3dPoint P; ON_3dVector Ds, Dt, Dss, Dst, Dtt; if (!srf->Ev2Der(s, t, P, Ds, Dt, Dss, Dst, Dtt)) return CRhinoCommand::failure; // failed to evaluate derivatives ON_3dVector N; if (!srf->EvNormal(s, t, N)) return CRhinoCommand::failure; // failed to evaluate normal double gauss = 0.0, mean = 0.0, k[2] = { 0.0, 0.0 }; ON_3dVector K[2]; if (!ON_EvPrincipalCurvatures(Ds, Dt, Dss, Dst, Dtt, N, &gauss, &mean, &k[0], &k[1], K[0], K[1])) return CRhinoCommand::failure; // failed to evaluate principal curvatures for (int i = 0; i < 2; i++) { if (fabs(k[i]) <= 1.0e-4 || fabs(k[i]) >= 1.0e4) { // just draw a line as curvature is huge/tiny ON_Line line(P - K[i] * 5.0, P + K[i] * 5.0); context.m_doc.AddCurveObject(line); } else { double r = 1.0 / k[i]; ON_3dPoint center = P + r * N; ON_3dPoint start = center - r * K[i]; ON_3dPoint end = center + r * K[i]; ON_Arc arc(start, P, end); context.m_doc.AddCurveObject(arc); } } context.m_doc.Redraw(); return CRhinoCommand::success; } ``` -------------------------------------------------------------------------------- # Create Spiral Source: https://developer.rhino3d.com/en/samples/rhinocommon/create-spiral/ Demonstrates how to create a spiral object from an axis and a radius point. ```cs partial class Examples { public static Rhino.Commands.Result CreateSpiral(Rhino.RhinoDoc doc) { var axisStart = new Rhino.Geometry.Point3d(0, 0, 0); var axisDir = new Rhino.Geometry.Vector3d(1, 0, 0); var radiusPoint = new Rhino.Geometry.Point3d(0, 1, 0); Rhino.Geometry.NurbsCurve curve0 = GetSpirial0(); if (null != curve0) doc.Objects.AddCurve(curve0); Rhino.Geometry.NurbsCurve curve1 = GetSpirial1(); if (null != curve1) doc.Objects.AddCurve(curve1); doc.Views.Redraw(); return Rhino.Commands.Result.Success; } private static Rhino.Geometry.NurbsCurve GetSpirial0() { var axisStart = new Rhino.Geometry.Point3d(0, 0, 0); var axisDir = new Rhino.Geometry.Vector3d(1, 0, 0); var radiusPoint = new Rhino.Geometry.Point3d(0, 1, 0); return Rhino.Geometry.NurbsCurve.CreateSpiral(axisStart, axisDir, radiusPoint, 1, 10, 1.0, 1.0); } private static Rhino.Geometry.NurbsCurve GetSpirial1() { var railStart = new Rhino.Geometry.Point3d(0, 0, 0); var railEnd = new Rhino.Geometry.Point3d(0, 0, 10); var railCurve = new Rhino.Geometry.LineCurve(railStart, railEnd); double t0 = railCurve.Domain.Min; double t1 = railCurve.Domain.Max; var radiusPoint = new Rhino.Geometry.Point3d(1, 0, 0); return Rhino.Geometry.NurbsCurve.CreateSpiral(railCurve, t0, t1, radiusPoint, 1, 10, 1.0, 1.0, 12); } } ``` ```python # No Python sample available ``` -------------------------------------------------------------------------------- # Create Square Pipes Source: https://developer.rhino3d.com/en/samples/rhinoscript/create-square-pipes/ Demonstrates how to create square pipes using RhinoScript. ```vbnet Option Explicit Sub SquarePipe ' Declare local variables Dim arrCrvs, strCrv, dblLength Dim arrStart, dblParam, arrPlane Dim x0, x1, y0, y1, arrX, arrY Dim arrPoints(4), strSqr ' Pick the input, or path, curves arrCrvs = Rhino.GetObjects("Select curves", 4) If IsNull(strCrv) Then Exit Sub ' Specify the lengths of the sides of the square dblLength = Rhino.GetReal("Length of sides", 1.0, 0.0) If Not IsNumeric(dblLength) Or dblLength <= 0 Then Exit Sub Call Rhino.EnableRedraw(False) For Each strCrv In arrCrvs ' Determine the curve's starting point arrStart = Rhino.CurveStartPoint(strCrv) ' Determine the parameter at the starting point dblParam = Rhino.CurveClosestPoint(strCrv, arrStart) ' Detemine the curve's perpendicular plane at its starting point. ' We can use this plane to cook up the coordinates of the square. arrPlane = Rhino.CurvePerpFrame(strCrv, dblParam) ' Scale the vectors based on the user input arrX = Rhino.VectorScale(arrPlane(1), dblLength * 0.5) arrY = Rhino.VectorScale(arrPlane(2), dblLength * 0.5) ' Cook up some temporary points x0 = Rhino.PointAdd(arrStart, arrX) x1 = Rhino.PointAdd(arrStart, Rhino.VectorReverse(arrX)) y0 = Rhino.PointAdd(arrStart, arrY) y1 = Rhino.PointAdd(arrStart, Rhino.VectorReverse(arrY)) ' Define the points of the square arrPoints(0) = Rhino.PointAdd(x0, Rhino.VectorReverse(arrY)) arrPoints(1) = Rhino.PointAdd(y1, Rhino.VectorReverse(arrX)) arrPoints(2) = Rhino.PointAdd(x1, arrY) arrPoints(3) = Rhino.PointAdd(y0, arrX) arrPoints(4) = arrPoints(0) ' Create the square strSqr = Rhino.AddPolyline(arrPoints) ' Extrude the polyline along the input curve Call Rhino.ExtrudeCurve(strSqr, strCrv) ' Delete polyline Call Rhino.DeleteObject(strSqr) Next Call Rhino.EnableRedraw(True) End Sub ``` -------------------------------------------------------------------------------- # Create Surface from Edge Curves Source: https://developer.rhino3d.com/en/samples/cpp/create-surface-from-edge-curves/ Demonstrates how to create a surface object from four edge curves. ```cpp CRhinoCommand::result CCommandTest::RunCommand(const CRhinoCommandContext& context) { // Pick four curve objects CRhinoGetObject go; go.SetCommandPrompt( L"Select 4 curves" ); go.SetGeometryFilter( CRhinoGetObject::curve_object); go.GetObjects( 4, 4 ); if( go.CommandResult() != CRhinoCommand::success ) return go.CommandResult(); // Validate results int i, count = go.ObjectCount(); if( count != 4 ) return CRhinoCommand::failure; ON_NurbsCurve nc[4]; // Get nurb form of each curve for( i = 0; i < count; i++) { const ON_Curve* crv = go.Object(i).Curve(); if( !crv ) return CRhinoCommand::failure; if( !crv->GetNurbForm(nc[i]) ) return CRhinoCommand::failure; } // Create the surface ON_Brep* brep = RhinoCreateEdgeSrf( 4, nc ); if( !brep ) { RhinoApp().Print( L"Unable to create surface.\n" ); return CRhinoCommand::failure; } // Ready new brep object CRhinoBrepObject* obj = new CRhinoBrepObject; obj->SetBrep( brep ); // Add new objet to document if( !context.m_doc.AddObject( obj ) ) { delete obj; RhinoApp().Print( L"Unable to create surface.\n" ); return CRhinoCommand::failure; } context.m_doc.Redraw(); return CRhinoCommand::success; } ``` -------------------------------------------------------------------------------- # Create Surface From Points and Knots Source: https://developer.rhino3d.com/en/samples/rhinocommon/create-surface-from-points-and-knots/ Demonstrates how to create a surface from scratch from points and knots. ```cs partial class Examples { public static Result CreateSurfaceFromPointsAndKnots(RhinoDoc doc) { const bool is_rational = false; const int number_of_dimensions = 3; const int u_degree = 2; const int v_degree = 3; const int u_control_point_count = 3; const int v_control_point_count = 5; // The knot vectors do NOT have the 2 superfluous knots // at the start and end of the knot vector. If you are // coming from a system that has the 2 superfluous knots, // just ignore them when creating NURBS surfaces. var u_knots = new double[u_control_point_count + u_degree - 1]; var v_knots = new double[v_control_point_count + v_degree - 1]; // make up a quadratic knot vector with no interior knots u_knots[0] = u_knots[1] = 0.0; u_knots[2] = u_knots[3] = 1.0; // make up a cubic knot vector with one simple interior knot v_knots[0] = v_knots[1] = v_knots[2] = 0.0; v_knots[3] = 1.5; v_knots[4] = v_knots[5] = v_knots[6] = 2.0; // Rational control points can be in either homogeneous // or euclidean form. Non-rational control points do not // need to specify a weight. var control_points = new Point3d[u_control_point_count, v_control_point_count]; for (int u = 0; u < u_control_point_count; u++) { for (int v = 0; v < v_control_point_count; v++) { control_points[u,v] = new Point3d(u, v, u-v); } } // creates internal uninitialized arrays for // control points and knots var nurbs_surface = NurbsSurface.Create( number_of_dimensions, is_rational, u_degree + 1, v_degree + 1, u_control_point_count, v_control_point_count ); // add the knots for (int u = 0; u < nurbs_surface.KnotsU.Count; u++) nurbs_surface.KnotsU[u] = u_knots[u]; for (int v = 0; v < nurbs_surface.KnotsV.Count; v++) nurbs_surface.KnotsV[v] = v_knots[v]; // add the control points for (int u = 0; u < nurbs_surface.Points.CountU; u++) { for (int v = 0; v < nurbs_surface.Points.CountV; v++) { nurbs_surface.Points.SetControlPoint(u, v, control_points[u, v]); } } if (nurbs_surface.IsValid) { doc.Objects.AddSurface(nurbs_surface); doc.Views.Redraw(); return Result.Success; } return Result.Failure; } } ``` ```python from Rhino.Geometry import Point3d, NurbsSurface, ControlPoint from scriptcontext import doc def RunCommand(): bIsRational = False dim = 3 u_degree = 2 v_degree = 3 u_cv_count = 3 v_cv_count = 5 # make up a quadratic knot vector with no interior knots u_knot = [0.0, 0.0, 1.0, 1.0] # make up a cubic knot vector with one simple interior knot v_knot = [0.0, 0.0, 0.0, 1.5, 2.0, 2.0, 2.0] # Rational control points can be in either homogeneous # or euclidean form. Non-rational control points do not # need to specify a weight. CV = dict( ((i,j),None) for i in range(2) for j in range(3) ) for i in range(0, u_cv_count): for j in range(0, v_cv_count): CV[i,j] = Point3d(i, j, i-j) # creates internal uninitialized arrays for # control points and knots nurbs_surface = NurbsSurface.Create(dim, bIsRational, u_degree + 1, v_degree + 1, u_cv_count, v_cv_count) # add the knots for i in range(0, nurbs_surface.KnotsU.Count): nurbs_surface.KnotsU[i] = u_knot[i] for j in range(0, nurbs_surface.KnotsV.Count): nurbs_surface.KnotsV[j] = v_knot[j] # add the control points for i in range(0, nurbs_surface.Points.CountU): for j in range(0, nurbs_surface.Points.CountV): nurbs_surface.Points.SetControlPoint(i, j, ControlPoint(CV[i, j])) if nurbs_surface.IsValid: doc.Objects.AddSurface(nurbs_surface) doc.Views.Redraw() if __name__ == "__main__": RunCommand() ``` -------------------------------------------------------------------------------- # Creating a Custom Color Picker Source: https://developer.rhino3d.com/en/guides/cpp/creating-custom-color-picker/ This guide demonstrates how to replace Rhino's color picker using C/C++. ## How To To replace Rhino's color picking dialog, derive a new class from `CRhinoReplaceColorDialog` and override the `ColorDialog()` virtual function. Note, if more that one `CRhinoReplaceColorDialog`-derived classes exist, then the last `CRhinoReplaceColorDialog`-derived object created will be displayed. The following sample code demonstrates how to replace Rhino's color picking dialog. In this example, we will simply replace it with the Windows standard color picking dialog... ```cpp class CMyColorDialog : public CRhinoReplaceColorDialog { public: CMyColorDialog() : CRhinoReplaceColorDialog(::AfxGetStaticModuleState()) {}; virtual ~CMyColorDialog() {}; bool CMyColorDialog::ColorDialog( HWND hWndParent, ON_Color& color, bool bIncludeButtonColors, const wchar_t* lpsDialogTitle ) { CColorDialog dlg( color, CC_ANYCOLOR|CC_FULLOPEN, CWnd::FromHandle(hWndParent) ); if( IDOK != dlg.DoModal() ) return false; color = dlg.GetColor(); return true; } }; ``` Now, create a new Rhino command and add a pointer to the above class as a public data member. Do not forget to initialize it's value to `NULL` in the command classes constructor. For example: ```cpp CMyColorDialog* m_pMyColorDialog; // ... CTestCommand::CTestCommand() : pMyColorDialog(NULL) {} ``` Finally, in your new command classes `RunCommand()` member, install the new color picker. For example: ```cpp CRhinoCommand::result CCommandTest::RunCommand( const CRhinoCommandContext& context ) { if( m_pMyColorDialog ) { delete m_pMyColorDialog; m_pMyColorDialog = NULL; RhinoApp().Print( L"Rhino color dialog restored.\n" ); } else { m_pMyColorDialog = new CMyColorDialog; if( m_pMyColorDialog ) RhinoApp().Print( L"Rhino color dialog replaced.\n" ); else RhinoApp().Print( L"Error replacing Rhino color dialog.\n" ); } return CRhinoCommand::success; } ``` For more information on the `CRhinoReplaceColorDialog` class, see it's declaration in *rhinoSdkUtilities.h*. -------------------------------------------------------------------------------- # Creating a Custom CRhinoGetObject Class Source: https://developer.rhino3d.com/en/guides/cpp/creating-custom-crhinogetobject-class/ This guide demonstrates how to derive a class from CRhinoGetObject to handle special case object picking. ## Overview The `CRhinoGetObject` class that is used for interactively picking one or more objects is a large, full-featured class (see *rhinoSdkGetObject.h* for details). But, on occasion, the class does not offer enough options. For example, `CRhinoGetObject` is capable of picking curve objects. But, it is not capable of picking polyline curve objects that are closed. When the required object filtering exceeds the capabilities of the base class, it's time to derive your own. `CRhinoGetObject` has a virtual function named `CustomGeometryFilter()` that is called after all obvious geometry filter checks have been performed. Thus, if you derive a new class from `CRhinoGetObject` and override this virtual member, you can filter for most any geometric object or property. ## Sample The following example code demonstrates deriving from `CRhinoGetObject`. In this example, we want to allow the user to only select closed polylines... ```cpp class CRhGetClosedPolylineObject : public CRhinoGetObject { public: bool CustomGeometryFilter( const CRhinoObject* obj, const ON_Geometry* geom, RHINO_COMPONENT_INDEX idx ) const; }; bool CRhGetClosedPolylineObject::CustomGeometryFilter( const CRhinoObject* obj, const ON_Geometry* geom, RHINO_COMPONENT_INDEX idx ) const { if( geom ) { // is it a polyline? if( const ON_PolylineCurve* p = ON_PolylineCurve::Cast(geom) ) { if( p->IsClosed() && p->IsPolyline() > 3 ) return true; } // is is a polycurve that looks like a polyline? if( const ON_PolyCurve* p = ON_PolyCurve::Cast(geom) ) { if( p->IsClosed() && p->IsPolyline() > 3 ) return true; } // is it a [[rhino:nurbs|NURBs]] curve that looks like a polyline? if( const ON_Curve* p = ON_Curve::Cast(geom) ) { ON_NurbsCurve n; if( p->GetNurbForm(n) ) { if( n.IsClosed() && n.IsPolyline() > 3 ) return true; } } } return false; } ``` We can use the above class as follows: ```cpp CRhGetClosedPolylineObject go; go.SetCommandPrompt( L"Select closed polyline" ); go.SetGeometryFilter( CRhinoGetObject::curve_object ); go.GetObjects( 1, 1 ); if( go.CommandResult() == CRhinoCommand::success ) { // TODO... } ``` -------------------------------------------------------------------------------- # Creating and Deploying Plugin Toolbars Source: https://developer.rhino3d.com/en/guides/rhinocommon/create-deploy-plugin-toolbar/ This guide covers the creation and deployment of plugin toolbars. ## Question How can I create one or more toolbars for my plugin, and how can I deploy these toolbars with my plugin? ## Answer If you want to create Rhino-style toolbars, then use Rhino's `Toolbar` command. You can save your custom toolbars in your own Rhino User Interface (RUI) file. For details on creating toolbars, see the Rhino help file. If you give your custom RUI file the exact same name as the plugin RHP file and install it in the folder containing the RHP file, then Rhino will automatically open it the first time your plugin loads. -------------------------------------------------------------------------------- # Creating Blocks Source: https://developer.rhino3d.com/en/guides/cpp/creating-blocks/ This guide demonstrates how to create an instance definition using C/C++. ## Overview Rhino blocks, known in the SDK as instances, are single objects that combine one or more objects. Using blocks lets you: - Create parts libraries. - Update all instances by modifying the block definition. - Keep a smaller model size by using block instances instead of copying identical geometry. - Use the *BlockManager* command to view information about the blocks defined in the model. - Use the *Insert* command to place block instances into your model, which scales and rotates the instance. ## How To Creating instance definitions using C/C++ requires two steps: 1. Define the instance definition objects. Instance definition objects are similar to regular Rhino objects - the ones that you see on the screen. The difference is that instance definition objects reside in a different location in the document. To add instance definition objects to the document, use `CRhinoDoc::AddObject` and make sure you set the bInstanceDefinition parameter to true. 1. Add a new instance definition object to Rhino's instance definition table, which is located on the Rhino document. An instance definition defines the name of the instance and the instance definition objects used by it. **NOTE**: An instance definition's base point is always the world origin (0,0,0). Knowing this, you need to orient your instance definition geometry around the world origin. The *Block* command does this by prompting the user for a base point and then transforming the selected objects from the user's picked point to the world origin. If you are adding your own geometry on the fly, and not picking it, just create your objects knowing that the base point for your instance definition will be the world origin. ## Sample The following example code demonstrates how to select one or more objects and create a block definition with them. **NOTE**: Unlike Rhino's *Block* command, this example code does not delete the selected objects, nor does it automatically insert a block instance at the location defined by the user. ```cpp CRhinoCommand::result CCommandTest::RunCommand( const CRhinoCommandContext& context ) { // Select objects to define block CRhinoGetObject go; go.SetCommandPrompt( L"Select objects to define block" ); go.EnableReferenceObjectSelect( false ); go.EnableSubObjectSelect( false ); go.EnableGroupSelect( true ); // Phantoms, grips, lights, etc., cannot be in blocks. const unsigned int forbidden_geometry_filter = CRhinoGetObject::light_object | CRhinoGetObject::grip_object | CRhinoGetObject::phantom_object; const unsigned int geometry_filter = forbidden_geometry_filter ^ CRhinoGetObject::any_object; go.SetGeometryFilter( geometry_filter ); go.GetObjects( 1, 0 ); if( go.CommandResult() != success ) return go.CommandResult(); // Block base point CRhinoGetPoint gp; gp.SetCommandPrompt( L"Block base point" ); gp.GetPoint(); if( gp.CommandResult() != success ) return gp.CommandResult(); ON_3dPoint base_point = gp.Point(); // Block definition name CRhinoGetString gs; gs.SetCommandPrompt( L"Block definition name" ); gs.GetString(); if( gs.CommandResult() != success ) return gs.CommandResult(); // Validate block name ON_wString idef_name = gs.String(); idef_name.TrimLeftAndRight(); if( idef_name.IsEmpty() ) return nothing; // See if block name already exists CRhinoInstanceDefinitionTable& idef_table = context.m_doc.m_instance_definition_table; int idef_index = idef_table.FindInstanceDefinition( idef_name ); if( idef_index >= 0 ) { RhinoApp().Print( L"Block definition \"%s\" already exists.\n", idef_name ); return nothing; } // Create new block definition ON_InstanceDefinition idef; idef.SetName( idef_name ); // Gather all of the selected objects ON_SimpleArray objects( go.ObjectCount() ); int i; for( i = 0; i < go.ObjectCount(); i++ ) { const CRhinoObject* obj = go.Object(i).Object(); if( obj ) objects.Append( obj); } ON_Xform xform; xform.Translation( ON_origin - base_point ); // Duplicate all of the selected objects and add them // to the document as instance definition objects ON_SimpleArray idef_objects( objects.Count() ); for( i = 0; i < objects.Count(); i++ ) { const CRhinoObject* obj = objects[i]; if( obj ) { CRhinoObject* dupe = context.m_doc.TransformObject( obj, xform, false, false, false ); if( dupe) { context.m_doc.AddObject( dupe, false, true ); idef_objects.Append( dupe ); } } } if( idef_objects.Count() < 1 ) { RhinoApp().Print( L"Unable to duplicate block definition geometry.\n" ); return failure; } idef_index = idef_table.AddInstanceDefinition( idef, idef_objects ); if( idef_index < 0 ) { RhinoApp().Print( L"Unable to create block definition \"%s\".\n", idef_name ); return failure; } return success; } ``` ## Related Topics - [Dynamically Inserting Blocks](/guides/cpp/dynamically-inserting-blocks) -------------------------------------------------------------------------------- # Creating GUIDs Source: https://developer.rhino3d.com/en/guides/rhinoscript/creating-guids/ This guide demonstrates how to create a Globally Unique Identifier (GUID) in RhinoScript. ## Overview [Globally Unique Identifiers](https://en.wikipedia.org/wiki/Globally_unique_identifier) - or GUIDs - are unique identification numbers that are used to track items. Rhino uses GUIDs just for this purpose. GUIDs come in different formats, but are usually stored as 128-bit values, and are commonly displayed as 32 hexadecimal digits with groups separated by hyphens: `{3AEC4721-34KP-3152-B2BB-17442C41208P}` Let's write a script that creates GUIDs... ## GUID Generation There is actually a very easy way to generate GUIDs. The `Scriptlet.TypeLib` object includes a method that generates GUIDs. If you need a GUID, here is a short script that will supply you with one: ```vbnet ' Creates a Registry-formatted GUID string ' Ex: {xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx} Function CreateGuidRegistryFormat Dim objTypeLib Set objTypeLib = CreateObject("Scriptlet.TypeLib") CreateGuidRegistryFormat = Left(objTypeLib.Guid, 38) End Function ``` If you want to create a plain GUID - one without the surrounding curly brackets, then you can do something like this: ```vbnet ' Creates a plain-formatted GUID string ' Ex: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx Function CreateGuidPlainFormat Dim objTypeLib Set objTypeLib = CreateObject("Scriptlet.TypeLib") CreateGuidPlainFormat = Mid(objTypeLib.Guid, 2, 36) End Function ``` ## Related Topics - [Globally Unique Identifier (Wikipedia)](https://en.wikipedia.org/wiki/Globally_unique_identifier) - [Converting GUIDs to Strings](/guides/rhinoscript/converting-guids-to-strings) -------------------------------------------------------------------------------- # Creating Leaders Source: https://developer.rhino3d.com/en/guides/cpp/creating-leaders/ This brief guide demonstrates how to an annotation leader using C/C++. ## How To Leaders in Rhino are defined by the `ON_Leader2` class. To construct a leader, you must provide: 1. A plane, or an `ON_Plane` object, which defines the plane in which the leader will be located. 2. Two or more 2D points that line in the plane that you specified above. In the following sample code, we will construct a simple leader object. The leader will reside in the world x-y plane and will have four points... ```cpp CRhinoCommand::result CCommandLeader::RunCommand( const CRhinoCommandContext& context ) { // Some set of points that define the leader ON_3dPointArray points; points.Append( ON_3dPoint(1.0, 1.0, 0.0) ); points.Append( ON_3dPoint(5.0, 1.0, 0.0) ); points.Append( ON_3dPoint(5.0, 5.0, 0.0) ); points.Append( ON_3dPoint(9.0, 5.0, 0.0) ); // The plane in which the leader resides ON_Plane plane = ON_xy_plane; // Create the leader ON_Leader2 leader; leader.SetPlane( plane ); // Add the points to the leader int i; for( i = 0; i < points.Count(); i++ ) { // Make sure the points are on the plane ON_2dPoint p2; if( leader.m_plane.ClosestPointTo(points[i], &p2.x, &p2.y) ) { if( leader.m_points.Count() < 1 | p2.DistanceTo(*leader.m_points.Last()) > ON_SQRT_EPSILON ) leader.m_points.Append( p2 ); } } // Create the leader object CRhinoAnnotationLeader* leader_object = new CRhinoAnnotationLeader(); // Add our leader to the object leader_object->SetAnnotation( leader ); if( context.m_doc.AddObject(leader_object) ) context.m_doc.Redraw(); else delete leader_object; // error return success; } ``` -------------------------------------------------------------------------------- # Creating Linear Dimensions Source: https://developer.rhino3d.com/en/guides/opennurbs/creating-linear-dimensions/ This guide demonstrates how to create linear dimensions using openNURBS. ## Question The annotation classes are different in openNURBS 6. A sample on how to create a linear dimension would be really helpful. ## Answer The annotation classes have been overhauled in Rhino 6. For an overview of what's new and what's changed, see the following: [Annotation Objects](http://developer.rhino3d.com/guides/cpp/annotation-objects/) Every annotation object needs a plane, dimension style, location, and text content. In addition, each specific type of annotation needs its specific information. ## Example The following example code can be used to create linear dimensions using the openNURBS toolkit: ```cpp /* Description: Add an aligned linear dimension to the model. The dimension line is parallel to the segment connecting the dimension points. Parameters: model - [in] The model object. point0 - [in] point1 - [in] Locations of one of the points being dimensioned. The dimension line will be parallel to the segment connecting these points. line_point - [in] A point on the linear dimension line. plane_normal - [in] A vector perpendcular to the line between the extension points that defines the orientation of the dimension's plane. dim_style - [in] nullptr, model dimension style, or a valid override style. If invalid, then the current dimension style will be used. attributes - [in] nullptr or object attributes. */ static bool AddDimLinear( ONX_Model& model, ON_3dPoint point0, ON_3dPoint point1, ON_3dPoint line_point, ON_3dVector plane_normal, const ON_DimStyle* dim_style, const ON_3dmObjectAttributes* attributes ) { if (nullptr == dim_style) { const ON_ModelComponentReference& dim_style_ref = model.CurrentDimensionStyle(); dim_style = ON_DimStyle::Cast(dim_style_ref.ModelComponent()); } if (nullptr == dim_style) return false; const ON_UUID parent_id = dim_style->ParentIdIsNotNil() ? dim_style->ParentId() : dim_style->Id(); const ON_ModelComponentReference& parent_dim_style_ref = model.DimensionStyleFromId(parent_id); const ON_DimStyle* parent_dim_style = ON_DimStyle::Cast(parent_dim_style_ref.ModelComponent()); if (nullptr == parent_dim_style) return false; ON_DimLinear dim_linear; if (nullptr == ON_DimLinear::CreateAligned(point0, point1, line_point, plane_normal, parent_dim_style->Id(), &dim_linear)) return false; if (dim_style->ContentHash() != parent_dim_style->ContentHash()) { ON_DimStyle* override_style = new ON_DimStyle(*dim_style); override_style->SetParentId(parent_dim_style->Id()); override_style->ClearId(); override_style->ClearName(); override_style->OverrideFieldsWithDifferentValues(*dim_style, *parent_dim_style); if (override_style->HasOverrides()) dim_linear.SetOverrideDimensionStyle(override_style); else delete override_style; } if (nullptr == attributes) attributes = &ON_3dmObjectAttributes::DefaultAttributes; // 'model' will copy input and manage this memory const ON_ModelComponentReference& geometry_ref = model.AddModelGeometryComponent(&dim_linear, attributes); return !geometry_ref.IsEmpty(); } ``` You can test the above static functions by adding the following sample code to the *Example_Write* project included with the openNURBS toolkit: ```cpp ONX_Model model = ...; model.AddDefaultLayer(L"Default", ON_Color::Black); model.AddDefaultDimensionStyle(L"Default", ON::LengthUnitSystem::Millimeters, 0.001); ON_3dPoint point0(0.0, 0.0, 0.0); ON_3dPoint point1(10.0, 0.0, 0.0); ON_3dPoint line_point(5.0, 5.0, 0.0); ON_3dVector plane_normal = ON_3dVector::ZAxis; bool rc = AddDimLinear(model, point0, point1, line_point, plane_normal, nullptr, nullptr); model.Write(archive, 60); ``` -------------------------------------------------------------------------------- # Creating Macros Source: https://developer.rhino3d.com/en/guides/general/creating-command-macros/ A basic tutorial on creating macros (scripting together Rhino commands) You can create macros in Rhino to automate many tasks, customize your commands, and improve your workflow. There may be some confusion about the use of the term “scripting” here. Classically, it describes both the process of writing macros (what this section is about), as well as writing more sophisticated scripts in either [RhinoScript](/guides/rhinoscript/), [Rhino.Python](/guides/rhinopython/) or other programming languages. The two things are actually very different. Writing functions in RhinoScript or other programming languages is a lot more complex than creating macros, and requires some programming knowledge and skills. We don't cover that here. I use the term “Macro” here exclusively to describe the putting together of strings of ordinary Rhino commands and their options to create an automated function. This is scripting on its simplest of levels, and is easily accessible to any ordinary Rhino user, even if they have no knowledge of programming. All You need is a reasonable understanding of Rhino commands and their structure, as well as a logical mind and a taste for a little experimentation and debugging. #### The tools you need 1. Your brain. 2. The Rhino Help file - lists all Rhino commands and their sub-options. This is your most important reference. 3. The Rhino **MacroEditor**, to easily run and debug your macros. ## You've already used a macro or two... First, if you are a user of Rhino, you are already a macro user even though you may not know it. Many of the commands in Rhino are already “macroed” for you. When you click a toolbar button or call a command from the menu, it is often a preset macro. To see, Shift+right-click on the button **Extrude Straight**: ![Extrude](https://developer.rhino3d.com/images/extrudecrvbuttoneditor.gif) This is an example of the simplest macro, which sets a series of options within a single command so that you don’t have to specify each one every time you use it. **ExtrudeCrv** has several buttons with preset options, **Tapered, AlongCurve, ToPoint, Cap=Yes** (solid) etc. Check out the macros under all the **ExtrudeCrv** buttons to see how they are laid out. In a sense, you’re doing the same thing as if you clicked or typed the options one at a time at the command line. In fact, that’s all macros really are -- just a set of instructions to repeat a sequence of commands that you would otherwise input manually one at a time. This scripting of options for a single command can also be combined with data entry (i.e. coordinates or other numerical data). It is also possible to string together several commands in a row, for an automated sequence of events to manipulate or create objects. **Note:** Why the _Underscores ? These tell Rhino that the command which follows is in English (no matter what the language you are running Rhino in), which will make your macro universal. If you are running in English and don’t care, you can eliminate the underscores in your macros if you wish. It will not affect anything else. Also, why the exclamation point (!)? This cancels any previous command that might be running, for safety's sake. ## Getting started Say you have to place a series of 10x10x10 boxes with the center of the bottom face landing at the desired point, with that point to be specified by either by a mouse click at the desired location or by entering the coordinates by the keyboard. You could use the standard Box (**Corner to Corner + Height**) command, but by default this will place the insertion point at the first corner of the box. To have the insertion point where we want, it is easier to use the Box, Center command. This is in reality just the Box command with the center option, so you will have to activate it in your macro. Open the **MacroEditor** and type this in: ``` ! _Box _Center ``` (This is actually the macro under the Box, Center button if you check.) All entries (command words and numerical inputs) need to be separated by a single space. Now, we need to specify the center point. To do this, you need to tell Rhino to stop processing the command temporarily and wait for an input in the form of a click or a keyboard entry. Do this by inserting the command Pause. ``` ! _Box _Center _Pause ``` Once the data has been entered, you can specify the box size directly in the command. Since the Center option in Box wants a corner of the box as a second input, you can specify its X,Y coordinates: ``` ! _Box _Center _Pause r5,5 ``` (Why the `r`? We want this coordinate to be relative to the last picked point, that is to say, the box bottom center. Otherwise the corner will always land at X5, Y5.) At this point you can put in the height, which in this case is relative to the original starting point. ``` ! _Box _Center _Pause r5,5 10 ``` Since there is no further input necessary nor options possible, the macro completes and our box is there. Note that since we wanted a height equal to the width, another possibility would just to have been to use Enter instead of 10 for the last entry. ``` ! _Box _Center _Pause r5,5 _Enter ``` Now that the macro is running, [[rhino:macroscriptsetup|make a new toolbar button]] and paste the macro in. Give it a recognizable name, like “10x10x10 bottom centered box”. Note, once the macro is executed, right-clicking repeats the whole sequence of this macro, so you can use it many times in a row without clicking the button every time. ## OK, let’s get a bit more complicated… Some commands invoke dialog boxes with many options. Normally, this stops your macro and waits for you to click the desired options, then continue. Since you want to automate, you can bypass the dialog by putting a –hyphen (also known as a dash) before the command. Then you can script in all your options and the macro will run to completion without needing your intervention. Some commands have several levels of sub-options. If you want to see what’s available, type the command at the command line with the hyphen and look at what’s proposed for options. Click on the options and see if they have sub-options. #### Loft two open curves Let’s say you would like to repetitively `Loft` two *OPEN* curves together to form a surface. If you use the standard `Loft` command, you always have to go through the dialog. If you use the `–Loft` version, you can avoid this and things go much faster. Look at the following: ``` _-Loft _Pause _Type=_Normal _Simplify=_None _Closed=_No _Enter ``` Note that when you invoke the command, immediately a pause lets you pick your curves. If you remove the pause, the macro won’t work if you have not selected your curves before calling it. If you have already preselected your curves the pause is intelligently ignored. The command then sets all your specified options. Once that is done, it creates the surface and finishes. Try it with two open curves, either pre or post selecting them. Try modifying one or more of the options, like substituting Closed=Yes or Simplify=Rebuild. (For this you also have to add a line with Rebuild=20 or some other value.) #### Modifying it to use with closed curves Now, try it with two closed curves. You have a problem. Why? For closed curves, Loft needs another input from you -– the seam location. This is something that needs to be in the macro in the right sequence. So, you can either choose from various automatic seam options (which are on a sub-option level) or you can adjust it on screen. Either way, you need to modify the macro. Adding a pause in the right place lets you check and adjust the seam on screen: ``` _-Loft _Pause _Pause <-- _Type=_Normal _Simplify=_None _Closed=_No _Enter ``` Adding an `Enter` instead of the `Pause` tells Rhino you don’t care. Just leave the seam the way it is by default. ``` _-Loft _Pause _Enter <-- _Type=_Normal _Simplify=_None _Closed=_No _Enter ``` Or, you can specify another Loft seam option by stepping down into the seam sub-option level: ``` _-Loft _Pause _Natural <-- _Enter <-- _Type=_Normal _Simplify=_None _Closed=_No _Enter ``` (The `Enter` after Natural is necessary to exit the “seam” option level and get back up to the Loft options level.) Unfortunately, the same macro will not work correctly for both open and closed curves because of the extra seam option required. This is one of the limitations of the macro system and the way some Rhino commands have been written. ## Using macros to set your interface options quickly Macros can also be used to set various GUI and Document Properties options automatically without having to go wading into the Options dialog. I use the following to set the render mesh the way I want it. (Note the dash before -_DocumentProperties.) ``` -_DocumentProperties _Mesh _Custom _MaxAngle=0 _AspectRatio=0 _MinEdgeLength=0 _MaxEdgeLength=0 _MaxEdgeSrf=0.01 _GridQuads=16 _Refine=Yes _JaggedSeams=No _SimplePlanes=No _Enter _Enter ``` Why are there two Enters at the end? You went down two levels in -_DocumentProperties, first to the Mesh level, then to the Custom sublevel inside Mesh. You need one Enter to exit the sublevel and go back to the main level, and one more to exit the command. Some scripts might even need three enters. The following is from Jeff LaSor, for turning on or off the crosshair cursor: To script Crosshairs ON or OFF put the following on a button: ``` -_Options _Appearance _Visibility _Crosshairs _Enter _Enter _Enter ``` Notice the reference to each individual command option name. Specifying them inside the script is like clicking on them with the mouse. Also note the three Enter entries. Since each command option takes you down into a new set of sub-level command options, an Enter is required to take you back up. Since this script went down three levels, it needs to specify three Enters to get all the way out of the command. Or, if you just use an exclamation point `!` at the end (which in a script means “end now!”), it takes you all the way out regardless of how many sub-levels you're in. Note, if you want to continue your macro with something else, do not use `!`, use the Enters instead, otherwise your macro will always stop at the `!` and terminate. The script simply toggles the crosshairs ON and OFF. But if you wanted a script that always turned them ON and another that always turned them OFF, here's what they would look like: Always ON version: ``` -_Options _Appearance _Visibility _Crosshairs=_Show ! Always OFF version: -_Options _Appearance _Visibility _Crosshairs=_Hide ! ``` Note the use of the`!` here. Also, note you can assign directly the values options can take on to that option using the '=' operator. The Crosshairs option has two possible values, "Show" and "Hide", and thus, that's what is used in the assignment. (Thanks, Jeff) ## Other useful macro writing tools and commands There are some handy tricks for doing more complex macros. One is the discriminating use of various selection filters, particularly `SelLast`, which selects the last object created/transformed, `SelPrev`, which selects the previous input object, and `SelNone`, which deselects everything. There are also possibilities to name objects, group them (and name the group) and then recall them later by that object name or group name. ``` Select SelLast SelPrev SelNone SetObjectName SetGroupName SelGroup SelName Group Ungroup ``` To set a single object name (this in itself is a macro!): ``` _Properties _Pause _Object _Name [put your object name here] _Enter _Enter ``` To cancel a single object name (without deleting the object) ``` _Properties _Pause _Object _Name “ “ _Enter _Enter (quote space quote for the name) ``` ## Examples using the above tools Look at the following macro: ``` _Select _Pause _Setredrawoff _BoundingBox _World _Enter _Selnone _Sellast _OffsetSrf _Solid _Pause _Delete _Sellast _BoundingBox _World _Enter _Delete _Setredrawon ``` It creates an offset bounding box around an object. The offset is input by the user. See if you can follow the logical sequence. The Setredrawoff/on stop/restart the screen refresh, eliminates the display flickering as all is executed and speeds up the process. Beware, if you terminate the command before Setredrawon, you will think Rhino is dead, as the screen no longer updates. If this happens, don’t panic, typing the command `Setredrawon` will restore the display refresh. **As a final example,** the following macro creates a point centered on a 2D planar or text object and grouped with it. It assumes that you're in the same view the text was created in, and that the object is really 2D and planar. (Otherwise it will likely fail.) Note the use of a named group and various selection commands. The `NoEcho` command temporarily stops the reporting of information to the command line, which, combined with Setredrawoff/on makes the macro run without flashing and without too much info reported to command history. It will run without those as well, though. ``` _Select _Pause _Noecho _Setredrawoff _Group _Enter _SetGroupName TexTemp _BoundingBox _CPlane _Enter _SelNone _SelLast _PlanarSrf _SelPrev _Delete _SelLast _AreaCentroid _Delete _Sellast _SelGroup TexTemp _Ungroup _Group _Setredrawon ``` **Please feel free to add to or edit this tutorial!** This is a work in progress... ## Related Topics - [Python Basic Syntax](/guides/rhinopython/) - [Rhinoscript](/guides/rhinoscript/) - [Macro Syntax Help Topic](http://docs.mcneel.com/rhino/6/help/en-us/information/rhinoscripting.htm) - [Running a Macro](/guides/rhinoscript/running-scripts-from-macros/) -------------------------------------------------------------------------------- # Creating NURBS Cage Objects Source: https://developer.rhino3d.com/en/samples/cpp/creating-nurbs-cage-objects/ Demonstrates how to create a NURBS Cage objects. ```cpp CRhinoCommand::result CCommandFooBar::RunCommand( const CRhinoCommandContext& context ) { ON_3dPoint box_corners[8]; CArgsRhinoGetBox args; CRhinoCommand::result rc = RhinoGetBox( args, box_corners, 0 ); if( rc == CRhinoCommand::success ) { int degree[3] = {3,3,3}; // defaults int cv_count[3] = {4,4,4}; // defaults ON_NurbsCage nurbs_cage; if( nurbs_cage.Create( box_corners, degree[0]+1, degree[1]+1, degree[2]+1, cv_count[0], cv_count[1], cv_count[2]) ) { CRhinoCageObject* cage_object = new CRhinoCageObject(); if( cage_object ) { cage_object->SetCage( nurbs_cage ); context.m_doc.AddObject( cage_object ); context.m_doc.Redraw(); } } } return rc; } ``` -------------------------------------------------------------------------------- # Creating Plugins that use Cloud Zoo Source: https://developer.rhino3d.com/en/guides/rhinocommon/cloudzoo/cloudzoo-overview/ This guide discusses all the steps needed to create RhinoCommon plugins that support Cloud Zoo. ## Overview Add Cloud Zoo support to your Plug-In by following the steps below. Cloud Zoo allows Rhino users to add their license keys to their personal Rhino account or to a team composed of multiple Rhino accounts. The users may then login from any computer that has Rhino installed and run Rhino. Cloud Zoo enforces license restrictions by making sure that users can only run concurrently in as many computers as there are licenses for a specific product. This allows individual users to run Rhino and other Plug-Ins on any machine. In a team scenario, members can be anywhere in the world and have access to a license. Cloud Zoo does not require users to have a constant internet connection—only an occasional one every couple of weeks. This is possible because Cloud Zoo employs license lease mechanism wherein a lease--not a license itself--is issued by Cloud Zoo to a client running Rhino. A lease usually expires within a few weeks, but a new lease is frequently issued between Rhino and Cloud Zoo while a client is online. This allows for a buffer of a few weeks in case the computer is offline for extended periods of time. This design allows Rhino and other Plug-Ins to run reliably even in environments with poor internet connections. Cloud Zoo can also void a lease at any point in time. For example, when a license is removed by a user or by the developer (Such as when a customer returns a license for a refund), Cloud Zoo immediately voids all related leases, effectively ending the user's ability to use the software the license is intended for. Under certain scenarios, such as when adding or removing a license, Cloud Zoo will contact your server to make sure you allow such operations to succeed. Your server is not required to interact with the license lease process. ![Cloud Zoo Overview](https://developer.rhino3d.com/images/cz-overview.png) ## Required Steps To have your Plug-In support Cloud Zoo, you must: 1. [Register as an Issuer in Cloud Zoo.](/guides/rhinocommon/cloudzoo/cloudzoo-issuer) 2. [Add products to Cloud Zoo.](/guides/rhinocommon/cloudzoo/cloudzoo-add-products) 3. [Implement the required HTTPS callbacks.](/guides/rhinocommon/cloudzoo/cloudzoo-implement-http-callbacks) 4. [Modify Plug-In licensing code to support Cloud Zoo.](/guides/rhinocommon/cloudzoo/cloudzoo-modify-plugin-licensing-code) 5. (*Optional*) [Take advantage of Cloud Zoo endpoints for querying and modifying licenses in Cloud Zoo.](/guides/rhinocommon/cloudzoo/cloudzoo-optional-endpoints) -------------------------------------------------------------------------------- # Creating Points from Text Objects Source: https://developer.rhino3d.com/en/guides/cpp/creating-points-from-text-objects/ This brief guide demonstrates how to create point objects based on text entities using C/C++. ## Problem Imagine you have many text elements that display numeric values that identify elevation and you would like to convert these elements to points objects using the C/C++. The text elements denote the elevations of the locations and you would like create the 2D point by the location of the text and then use the number of the text as the z-coordinate. ## Solution To make picking text entities easier for the user, we will use a custom object picker that just filters `CRhinoAnnotationText` objects... ```cpp class CRhGetTextObject : public CRhinoGetObject { public: bool CustomGeometryFilter( const CRhinoObject* object, const ON_Geometry* geometry, ON_COMPONENT_INDEX component_index ) const { if( object && CRhinoAnnotationText::Cast(object) ) return true; return false; } }; ``` Here is the portion of the command that creates points from the text entities... ```cpp CRhinoCommand::result CCommandTest::RunCommand( const CRhinoCommandContext& context ) { CRhGetTextObject go; go.SetCommandPrompt( L"Select text" ); go.GetObjects( 1, 0 ); if( go.CommandResult() != success ) return go.CommandResult(); int i; for( i = 0; i < go.ObjectCount(); i++ ) { const CRhinoAnnotationText* text_obj = CRhinoAnnotationText::Cast( go.Object(i).Object() ); if( 0 == text_obj ) continue; ON_wString text_str( text_obj->String() ); text_str.TrimLeftAndRight(); double z = 0.0; if( RhinoParseNumber(text_str, &z) ) { ON_3dPoint text_pt = text_obj->m_text_block.Plane().Origin(); text_pt.z = z; context.m_doc.AddPointObject( text_pt ); } } context.m_doc.Redraw(); return success; } ``` -------------------------------------------------------------------------------- # Creating Sublayers Source: https://developer.rhino3d.com/en/guides/cpp/creating-sublayers/ This brief guide demonstrates how to create sublayers of a parent layer using C/C++. ## Problem You would like to create a "sublayer" (or a "child layer") of a parent layer. ## Solution All layers have a layer id field, returned by `ON_Layer::Id()`, that uniquely identifies that layer. Layers also maintain a parent id field, returned by `ON_Layer::ParentLayerId()`, that identifies the layer's parent. If a layer's parent id is a `null` UUID, then the layer does not have a parent and, thus, is considered a root layer. ## Sample The following sample demonstrates how to add a parent layer then then add a child layer to that parent. ```cpp CRhinoCommand::result CCommandTest::RunCommand(const CRhinoCommandContext& context) { CRhinoLayerTable& layer_table = context.m_doc.m_layer_table; // Define parent layer ON_Layer parent_layer; parent_layer.SetName(L"Parent"); // Add parent layer int parent_layer_index = layer_table.AddLayer(parent_layer); if (parent_layer_index >= 0) { // Get the layer we just added const CRhinoLayer& layer = layer_table[parent_layer_index]; // Define child layer ON_Layer child_layer; child_layer.SetName(L"Child"); // Assign parent layer's id as child's parent id child_layer.SetParentLayerId(layer.Id()); // Add child layer layer_table.AddLayer(child_layer); } return CRhinoCommand::success; } ``` -------------------------------------------------------------------------------- # CRhinoGetFileDialog to Preview Bitmaps Source: https://developer.rhino3d.com/en/samples/cpp/crhinogetfiledialog-to-preview-images/ Demonstrates how to use the CRhinoGetFileDialog class to preview bitmap images. ```cpp CRhinoCommand::result CCommandTest::RunCommand( const CRhinoCommandContext& context ) { ON_wString filename; // Prompt for a bitmap filename CRhinoGetFileDialog gf; gf.SetScriptMode( context.IsInteractive() ? FALSE : TRUE ); BOOL rc = gf.DisplayFileDialog( CRhinoGetFileDialog::open_bitmap_dialog, filename, CWnd::FromHandle( RhinoApp().MainWnd() ) ); if( !rc ) return cancel; // Verify filename filename = gf.FileName(); filename.TrimLeftAndRight(); if( filename.IsEmpty() ) return cancel; // Verify the file if( !CRhinoFileUtilities::FileExists(filename) ) { ON_wString error = L"The specified file was not found.\n"; if( context.IsInteractive() ) ::RhinoMessageBox( error, L"Test", MB_OK | MB_ICONEXCLAMATION ); else RhinoApp().Print( error ); return CRhinoCommand::failure; } // Verify the bitmap CRhinoDib dib; if( !dib.ReadFromFile(filename) ) { ON_wString error = L"The specified file cannot be identifed as a supported type.\n"; if( context.IsInteractive() ) ::RhinoMessageBox( error, L"Test", MB_OK | MB_ICONEXCLAMATION ); else RhinoApp().Print( error ); return CRhinoCommand::failure; } // To do... return success; } ``` -------------------------------------------------------------------------------- # Curve Bounding Box Source: https://developer.rhino3d.com/en/samples/rhinocommon/curve-bounding-box/ Demonstrates how to create a curve bounding box (world and plane oriented). ```cs partial class Examples { public static Rhino.Commands.Result CurveBoundingBox(Rhino.RhinoDoc doc) { // Select a curve object Rhino.DocObjects.ObjRef rhObject; var rc = Rhino.Input.RhinoGet.GetOneObject("Select curve", false, Rhino.DocObjects.ObjectType.Curve, out rhObject); if (rc != Rhino.Commands.Result.Success) return rc; // Validate selection var curve = rhObject.Curve(); if (curve == null) return Rhino.Commands.Result.Failure; // Get the active view's construction plane var view = doc.Views.ActiveView; if (view == null) return Rhino.Commands.Result.Failure; var plane = view.ActiveViewport.ConstructionPlane(); // Compute the tight bounding box of the curve in world coordinates var bbox = curve.GetBoundingBox(true); if (!bbox.IsValid) return Rhino.Commands.Result.Failure; // Print the min and max box coordinates in world coordinates Rhino.RhinoApp.WriteLine("World min: {0}", bbox.Min); Rhino.RhinoApp.WriteLine("World max: {0}", bbox.Max); // Compute the tight bounding box of the curve based on the // active view's construction plane bbox = curve.GetBoundingBox(plane); // Print the min and max box coordinates in cplane coordinates Rhino.RhinoApp.WriteLine("CPlane min: {0}", bbox.Min); Rhino.RhinoApp.WriteLine("CPlane max: {0}", bbox.Max); return Rhino.Commands.Result.Success; } } ``` ```python import Rhino import scriptcontext def CurveBoundingBox(): # Select a curve object rc, rhobject = Rhino.Input.RhinoGet.GetOneObject("Select curve", False, Rhino.DocObjects.ObjectType.Curve) if rc!=Rhino.Commands.Result.Success: return # Validate selection curve = rhobject.Curve() if not curve: return ## Get the active view's construction plane view = scriptcontext.doc.Views.ActiveView if not view: return plane = view.ActiveViewport.ConstructionPlane() # Compute the tight bounding box of the curve in world coordinates bbox = curve.GetBoundingBox(True) if not bbox.IsValid: return # Print the min and max box coordinates in world coordinates print("World min:", bbox.Min) print("World max:", bbox.Max) # Compute the tight bounding box of the curve based on the # active view's construction plane bbox = curve.GetBoundingBox(plane) # Print the min and max box coordinates in cplane coordinates print("CPlane min:", bbox.Min) print("CPlane max:", bbox.Max) if __name__=="__main__": CurveBoundingBox() ``` -------------------------------------------------------------------------------- # Curve Deviation Source: https://developer.rhino3d.com/en/samples/rhinocommon/curve-deviation/ Demonstrates how to determine the deviation between two curves. ```cs class DeviationConduit : Rhino.Display.DisplayConduit { private readonly Curve m_curve_a; private readonly Curve m_curve_b; private readonly Point3d m_min_dist_point_a ; private readonly Point3d m_min_dist_point_b ; private readonly Point3d m_max_dist_point_a ; private readonly Point3d m_max_dist_point_b ; public DeviationConduit(Curve curveA, Curve curveB, Point3d minDistPointA, Point3d minDistPointB, Point3d maxDistPointA, Point3d maxDistPointB) { m_curve_a = curveA; m_curve_b = curveB; m_min_dist_point_a = minDistPointA; m_min_dist_point_b = minDistPointB; m_max_dist_point_a = maxDistPointA; m_max_dist_point_b = maxDistPointB; } protected override void DrawForeground(Rhino.Display.DrawEventArgs e) { e.Display.DrawCurve(m_curve_a, Color.Red); e.Display.DrawCurve(m_curve_b, Color.Red); e.Display.DrawPoint(m_min_dist_point_a, Color.LawnGreen); e.Display.DrawPoint(m_min_dist_point_b, Color.LawnGreen); e.Display.DrawLine(new Line(m_min_dist_point_a, m_min_dist_point_b), Color.LawnGreen); e.Display.DrawPoint(m_max_dist_point_a, Color.Red); e.Display.DrawPoint(m_max_dist_point_b, Color.Red); e.Display.DrawLine(new Line(m_max_dist_point_a, m_max_dist_point_b), Color.Red); } } partial class Examples { public static Result CrvDeviation(RhinoDoc doc) { doc.Objects.UnselectAll(); ObjRef obj_ref1; var rc1 = RhinoGet.GetOneObject("first curve", true, ObjectType.Curve, out obj_ref1); if (rc1 != Result.Success) return rc1; Curve curve_a = null; if (obj_ref1 != null) curve_a = obj_ref1.Curve(); if (curve_a == null) return Result.Failure; // Since you already selected a curve if you don't unselect it // the next GetOneObject won't stop as it considers that curve // input, i.e., curveA and curveB will point to the same curve. // Another option would be to use an instance of Rhino.Input.Custom.GetObject // instead of Rhino.Input.RhinoGet as GetObject has a DisablePreSelect() method. doc.Objects.UnselectAll(); ObjRef obj_ref2; var rc2 = RhinoGet.GetOneObject("second curve", true, ObjectType.Curve, out obj_ref2); if (rc2 != Result.Success) return rc2; Curve curve_b = null; if (obj_ref2 != null) curve_b = obj_ref2.Curve(); if (curve_b == null) return Result.Failure; var tolerance = doc.ModelAbsoluteTolerance; double max_distance; double max_distance_parameter_a; double max_distance_parameter_b; double min_distance; double min_distance_parameter_a; double min_distance_parameter_b; DeviationConduit conduit; if (!Curve.GetDistancesBetweenCurves(curve_a, curve_b, tolerance, out max_distance, out max_distance_parameter_a, out max_distance_parameter_b, out min_distance, out min_distance_parameter_a, out min_distance_parameter_b)) { RhinoApp.WriteLine("Unable to find overlap intervals."); return Result.Success; } else { if (min_distance <= RhinoMath.ZeroTolerance) min_distance = 0.0; var max_dist_pt_a = curve_a.PointAt(max_distance_parameter_a); var max_dist_pt_b = curve_b.PointAt(max_distance_parameter_b); var min_dist_pt_a = curve_a.PointAt(min_distance_parameter_a); var min_dist_pt_b = curve_b.PointAt(min_distance_parameter_b); conduit = new DeviationConduit(curve_a, curve_b, min_dist_pt_a, min_dist_pt_b, max_dist_pt_a, max_dist_pt_b) {Enabled = true}; doc.Views.Redraw(); RhinoApp.WriteLine("Minimum deviation = {0} pointA({1}), pointB({2})", min_distance, min_dist_pt_a, min_dist_pt_b); RhinoApp.WriteLine("Maximum deviation = {0} pointA({1}), pointB({2})", max_distance, max_dist_pt_a, max_dist_pt_b); } var str = ""; RhinoGet.GetString("Press Enter when done", true, ref str); conduit.Enabled = false; return Result.Success; } } ``` ```python import rhinoscriptsyntax as rs import scriptcontext import Rhino def RunCommand(): crvA = rs.GetCurveObject("first curve")[0] crvA = rs.coercecurve(crvA) crvB = rs.GetCurveObject("second curve")[0] crvB = rs.coercecurve(crvB) if crvA == None or crvB == None: return Rhino.Commands.Result.Failure maxa, maxb, maxd, mina, minb, mind = rs.CurveDeviation(crvA, crvB) if mind <= Rhino.RhinoMath.ZeroTolerance: mind = 0.0; maxDistPtA = crvA.PointAt(maxa) maxDistPtB = crvB.PointAt(maxb) minDistPtA = crvA.PointAt(mina) minDistPtB = crvB.PointAt(minb) print("Minimum deviation = {0} pointA({1}, {2}, {3}), pointB({4}, {5}, {6})".format(mind, minDistPtA.X, minDistPtA.Y, minDistPtA.Z, minDistPtB.X, minDistPtB.Y, minDistPtB.Z)) print("Maximum deviation = {0} pointA({1}, {2}, {3}), pointB({4}, {5}, {6})".format(maxd, maxDistPtA.X, maxDistPtA.Y, maxDistPtA.Z, maxDistPtB.X, maxDistPtB.Y, maxDistPtB.Z)) if __name__=="__main__": RunCommand() ``` -------------------------------------------------------------------------------- # Curve Evaluation Source: https://developer.rhino3d.com/en/samples/cpp/curve-evaluation/ Demonstrates how to evaluate a curve. ```cpp CRhinoCommand::result CCommandTest::RunCommand( const CRhinoCommandContext& context ) { CRhinoGetObject go; go.SetCommandPrompt( L"Select curve for evaluation" ); go.SetGeometryFilter( CRhinoGetObject::curve_object ); go.GetObjects( 1, 1 ); if( go.CommandResult() != success ) return go.CommandResult(); const ON_Curve* crv = go.Object(0).Curve(); if( 0 == crv ) return failure; CRhinoGetPoint gp; gp.SetCommandPrompt( L"Pick point on curve for evaluation" ); gp.Constrain( *crv ); gp.GetPoint(); if( gp.CommandResult() != success ) return gp.CommandResult(); double t = 0.0; if( 0 == gp.PointOnCurve(&t) ) return nothing; CRhinoGetInteger gi; gi.SetCommandPrompt( L"Number of derivatives to calculate" ); gi.SetDefaultInteger( 1 ); gi.SetLowerLimit( 0 ); gi.SetUpperLimit( 4 ); gi.GetNumber(); if( gi.CommandResult() != success ) return gi.CommandResult(); int der_count = gi.Number(); // Allocate memory for results int v_stride = 3; int v_count = v_stride * ( der_count + 1 ); ON_SimpleArray v_array( v_count ); v_array.SetCount( v_count ); // Do the curve evaluation if( crv->Evaluate(t, der_count, v_stride, v_array.Array()) ) { const double* v = v_array.Array(); int i; for( i = 0; i < der_count + 1; i++ ) { RhinoApp().Print( L"Derivative %d = (%.3f,%.3f,%.3f)\n", i, v[0], v[1], v[2] ); v += v_stride; } } else { RhinoApp().Print( L"Failed to evaluate curve.\n" ); } return success; } ``` -------------------------------------------------------------------------------- # Curve Osculating Planes Source: https://developer.rhino3d.com/en/guides/rhinoscript/curve-osculating-planes/ This guide demonstrates how to calculate osculating planes. ## Problem Is it possible to calculate the osculating plane at point $$P$$ on a given curve with the methods provided by RhinoScript? ## Solution Yes. There are a number of methods included in RhinoScript that can be used to calculate a curve's osculating plane, such as `CurveClosestPoint`, `CurveTangent`, `CurveCurvature`, and `CurveEvaluate`. In this example, we will use the `CurveEvaluate` function to calculate the 2nd derivative of a curve at a parameter... ```vbnet Function CurveOsculatingPlane(crv, t) CurveOsculatingPlane = Null ' default return value If Not Rhino.IsCurveLinear(crv) Then Dim rc : rc = Rhino.CurveEvaluate(crv, t, 2) If IsArray(rc) Then CurveOsculatingPlane = Rhino.PlaneFromFrame(rc(0), rc(1), rc(2)) End If End If End Function ``` The following is an example of how you might use this function... ```vbnet Sub TestCurveOsculatingPlane Dim segs : segs = 10 Dim crv : crv = Rhino.GetObject("Select non-linear curve", 4) If Not IsNull(crv) Then Dim pts : pts = Rhino.DivideCurve(crv, segs) If IsArray(pts) Then Dim i, t, p For i = 0 To UBound(pts) t = Rhino.CurveClosestPoint(crv, pts(i)) p = CurveOsculatingPlane(crv, t) Rhino.AddPlaneSurface p, 1.0, 1.0 Next End If End If End Sub ``` -------------------------------------------------------------------------------- # Curve Properties to Excel Source: https://developer.rhino3d.com/en/samples/rhinoscript/curve-properties-to-excel/ Illustrates RhinoScript code that extracts curve properties into Excel. ```vbnet Option Explicit Sub ExtractProperties ' Declare variables Dim xlApp, xlBook, xlSheet ' Declare variable to hold the reference. Dim strObject Dim arrObjects Dim intCount Dim arrStart, arrEnd 'Set Default Values intCount = 0 ' Select some objects arrObjects = Rhino.GetObjects("Select objects for extraction",4) If Not IsArray(arrObjects) Then Exit Sub ' Open Excel object Set xlApp = CreateObject("excel.application") ' You may have to set Visible property to True ' if you want to see the application. xlApp.Visible = True ' Use xlApp to access Microsoft Excel's ' other objects. Set xlBook = xlApp.Workbooks.Add Set xlSheet = xlBook.Worksheets(1) 'Place titles on sheet xlApp.Cells(1, 1).Value = "Name" xlApp.Cells(1, 2).Value = "Length" xlApp.Cells(1,3).Value = "StartX" xlApp.Cells(1,4).Value = "StartY" xlApp.Cells(1,5).Value = "StartZ" xlApp.Cells(1,6).Value = "EndX" xlApp.Cells(1,7).Value = "EndY" xlApp.Cells(1,8).Value = "EndZ" 'Extract Properties of Curves For Each strObject In arrObjects 'Curves Processed If Rhino.IsCurve(strObject) Then xlApp.Cells(intCount + 2, 1).Value = Rhino.ObjectName(strObject) xlApp.Cells(intCount + 2, 2).Value = Rhino.CurveLength(strObject) 'Extract StartPoint arrStart = Rhino.CurveStartPoint(strObject) xlApp.Cells(intCount + 2, 3).Value = arrStart(0) xlApp.Cells(intCount + 2, 4).Value = arrStart(1) xlApp.Cells(intCount + 2, 5).Value = arrStart(2) 'Extract EndPoint arrEnd = Rhino.CurveEndPoint(strObject) xlApp.Cells(intCount + 2, 6).Value = arrEnd(0) xlApp.Cells(intCount + 2, 7).Value = arrEnd(1) xlApp.Cells(intCount + 2, 8).Value = arrEnd(2) End If intCount = intCount + 1 Next 'xlApp.Quit ' When you finish, use the Quit method to close Set xlApp = Nothing ' the application, then release the reference. End Sub ``` -------------------------------------------------------------------------------- # Curve Surface Intersection Source: https://developer.rhino3d.com/en/samples/rhinocommon/curve-surface-intersection/ Demonstrates how to calculate the intersection points of a user-specified Brep and a curve. ```cs partial class Examples { public static Result CurveSurfaceIntersect(RhinoDoc doc) { var gs = new GetObject(); gs.SetCommandPrompt("select brep"); gs.GeometryFilter = ObjectType.Brep; gs.DisablePreSelect(); gs.SubObjectSelect = false; gs.Get(); if (gs.CommandResult() != Result.Success) return gs.CommandResult(); var brep = gs.Object(0).Brep(); var gc = new GetObject(); gc.SetCommandPrompt("select curve"); gc.GeometryFilter = ObjectType.Curve; gc.DisablePreSelect(); gc.SubObjectSelect = false; gc.Get(); if (gc.CommandResult() != Result.Success) return gc.CommandResult(); var curve = gc.Object(0).Curve(); if (brep == null || curve == null) return Result.Failure; var tolerance = doc.ModelAbsoluteTolerance; Point3d[] intersection_points; Curve[] overlap_curves; if (!Intersection.CurveBrep(curve, brep, tolerance, out overlap_curves, out intersection_points)) { RhinoApp.WriteLine("curve brep intersection failed"); return Result.Nothing; } foreach (var overlap_curve in overlap_curves) doc.Objects.AddCurve(overlap_curve); foreach (var intersection_point in intersection_points) doc.Objects.AddPoint(intersection_point); RhinoApp.WriteLine("{0} overlap curves, and {1} intersection points", overlap_curves.Length, intersection_points.Length); doc.Views.Redraw(); return Result.Success; } } ``` ```python import rhinoscriptsyntax as rs from scriptcontext import doc import Rhino import System.Collections.Generic as scg import System as s def RunCommand(): srfid = rs.GetObject("select surface", rs.filter.surface | rs.filter.polysurface) if not srfid: return crvid = rs.GetObject("select curve", rs.filter.curve) if not crvid: return result = rs.CurveBrepIntersect(crvid, srfid) if result == None: print("no intersection") return curves, points = result for curve in curves: doc.Objects.AddCurve(rs.coercecurve(curve)) for point in points: rs.AddPoint(point) doc.Views.Redraw() if __name__ == "__main__": RunCommand() ``` -------------------------------------------------------------------------------- # Custom Geometry Filter Source: https://developer.rhino3d.com/en/samples/rhinocommon/custom-geometry-filter/ Demonstrates how to create a specialized GetObject with a custom geometry filter. ```cs partial class Examples { private static double m_tolerance; public static Result CustomGeometryFilter(RhinoDoc doc) { m_tolerance = doc.ModelAbsoluteTolerance; // only use a custom geometry filter if no simpler filter does the job // only curves var gc = new GetObject(); gc.SetCommandPrompt("select curve"); gc.GeometryFilter = ObjectType.Curve; gc.DisablePreSelect(); gc.SubObjectSelect = false; gc.Get(); if (gc.CommandResult() != Result.Success) return gc.CommandResult(); if (null == gc.Object(0).Curve()) return Result.Failure; Rhino.RhinoApp.WriteLine("curve was selected"); // only closed curves var gcc = new GetObject(); gcc.SetCommandPrompt("select closed curve"); gcc.GeometryFilter = ObjectType.Curve; gcc.GeometryAttributeFilter = GeometryAttributeFilter.ClosedCurve; gcc.DisablePreSelect(); gcc.SubObjectSelect = false; gcc.Get(); if (gcc.CommandResult() != Result.Success) return gcc.CommandResult(); if (null == gcc.Object(0).Curve()) return Result.Failure; Rhino.RhinoApp.WriteLine("closed curve was selected"); // only circles with a radius of 10 var gcc10 = new GetObject(); gcc10.SetCommandPrompt("select circle with radius of 10"); gc.GeometryFilter = ObjectType.Curve; gcc10.SetCustomGeometryFilter(CircleWithRadiusOf10GeometryFilter); // custom geometry filter gcc10.DisablePreSelect(); gcc10.SubObjectSelect = false; gcc10.Get(); if (gcc10.CommandResult() != Result.Success) return gcc10.CommandResult(); if (null == gcc10.Object(0).Curve()) return Result.Failure; RhinoApp.WriteLine("circle with radius of 10 was selected"); return Result.Success; } private static bool CircleWithRadiusOf10GeometryFilter (Rhino.DocObjects.RhinoObject rhObject, GeometryBase geometry, ComponentIndex componentIndex) { bool is_circle_with_radius_of10 = false; Circle circle; if (geometry is Curve && (geometry as Curve).TryGetCircle(out circle)) is_circle_with_radius_of10 = circle.Radius <= 10.0 + m_tolerance && circle.Radius >= 10.0 - m_tolerance; return is_circle_with_radius_of10; } } ``` ```python import rhinoscriptsyntax as rs import Rhino def circleWithRadiusOf10GeometryFilter (rhObject, geometry, componentIndex): isCircleWithRadiusOf10 = False c = rs.coercecurve(geometry) if c: b, circle = c.TryGetCircle() if b: isCircleWithRadiusOf10 = circle.Radius <= 10.0 + Rhino.RhinoMath.ZeroTolerance and circle.Radius >= 10.0 - Rhino.RhinoMath.ZeroTolerance return isCircleWithRadiusOf10 def RunCommand(): # only use a custom geometry filter if no simpler filter does the job # for curves - only a simple filter is needed if rs.GetObject("select curve", rs.filter.curve): #Rhino.DocObjects.ObjectType.Curve): print("curve vas selected") # for circles with a radius of 10 - a custom geometry filter is needed if rs.GetObject("select circle with radius of 10", rs.filter.curve, False, False, circleWithRadiusOf10GeometryFilter): print("circle with radius of 10 was selected") if __name__=="__main__": RunCommand() ``` -------------------------------------------------------------------------------- # Custom Picking Grip Objects Source: https://developer.rhino3d.com/en/guides/cpp/custom-picking-grip-objects/ This guide discusses how to write a custom grip object picker in C/C++. ## Problem Imagine you are trying to wrote code to pick grip objects, and you want that only grips at the boundary of a mesh to be selectable. You might have just spent a considerable amount of time trying to get the following to work: 1. Derive a class from `CRhinoGetObject`. 1. Override `CRhinoGetObject::CustomGeometryFilter`. What is missing is a pointer to a grip object inside `CRhinoGetObject::CustomGeometryFilter`. One might think this code would work... ```cpp CMyGetObject go; go.SetGeometryFilter( CRhinoGetObject::grip_object ); ``` but now `CRhinoGetObject::CustomGeometryFilter` override is not even called anymore. On the other hand, if you do not specify a `CRhinoGetObject::SetGeometryFilter` up front, the function is called but you don't get any grip object from the geometry parameter of `CRhinoGetObject::CustomGeometryFilter`. Is there a way around this problem? ## Solution Yes. In order to pick grip objects, using `CRhinoGetObject`, you must set the `CRhinoGetObject::grip_object` geometry filter. If not, then Rhino will ignore grips in an effort to improve picking performance. Also, if you want your `CRhinoGetObject::CustomGeometryFilter` override to be called, make sure to call `CRhinoGetObject::EnableSubObjectSelect` to disable subobject picking. For example: ```cpp CMyGetObject go; go.SetGeometryFilter( CRhinoObject::grip_object ); go.EnableSubObjectSelect( false ); go.GetObjects( 1, 0 ); if( go.CommandResult() == CRhinoCommand::success ) { // TODO... } ``` Regarding the picking of grips at the boundary of a mesh, here is a sample class that you can use... ```cpp class CRhGetMeshBoundaryGrip : public CRhinoGetObject { public: bool CustomGeometryFilter( const CRhinoObject* obj, const ON_Geometry* geo, ON_COMPONENT_INDEX ci ) const; }; bool CRhGetMeshBoundaryGrip::CustomGeometryFilter( const CRhinoObject* obj, const ON_Geometry* geo, ON_COMPONENT_INDEX ci ) const { if( 0 == obj ) return false; // Is grip object? const CRhinoGripObject* grip_obj = CRhinoGripObject::Cast( obj ); if( 0 == grip_obj ) return false; // Is mesh grip object? const CRhinoMeshObject* mesh_obj = CRhinoMeshObject::Cast( grip_obj->Owner() ); if( 0 == mesh_obj ) return false; // Is the grip on the border? const ON_Mesh* mesh = mesh_obj->Mesh(); if( mesh && !mesh->IsClosed() ) { const ON_MeshTopology& meshtop = mesh->Topology(); int topvi = meshtop.m_topv_map[grip_obj->m_grip_index]; const ON_MeshTopologyVertex& topv = meshtop.m_topv[topvi]; for( int i = 0; i < topv.m_tope_count; i++ ) { const ON_MeshTopologyEdge& tope = meshtop.m_tope[topv.m_topei[i]]; if( 1 == tope.m_topf_count ) return true; } } return false; } ``` -------------------------------------------------------------------------------- # Custom Python Source: https://developer.rhino3d.com/en/samples/rhinocommon/custom-python/ Demonstrates how to run a custom python script from within a command. ```cs partial class Examples { public static Rhino.Commands.Result CustomPython(RhinoDoc doc) { if (null == m_python) { m_python = Rhino.Runtime.PythonScript.Create(); if (null == m_python) { RhinoApp.WriteLine("Error: Unable to create an instance of the python engine"); return Rhino.Commands.Result.Failure; } } m_python.ScriptContextDoc = new CustomPythonDoc(doc); const string script = @" import rhinoscriptsyntax as rs rs.AddLine((0,0,0), (10,10,10)) "; m_python.ExecuteScript(script); return Rhino.Commands.Result.Success; } static Rhino.Runtime.PythonScript m_python; } // our fake RhinoDoc public class CustomPythonDoc { readonly RhinoDoc m_doc; public CustomPythonDoc(RhinoDoc doc) { m_doc = doc; } readonly CustomObjectTable m_table = new CustomObjectTable(); public CustomObjectTable Objects { get { return m_table; } } public Rhino.DocObjects.Tables.ViewTable Views { get { return m_doc.Views; } } } public class CustomObjectTable { public Guid AddLine(Rhino.Geometry.Point3d p1, Rhino.Geometry.Point3d p2) { Rhino.Geometry.Line l = new Rhino.Geometry.Line(p1, p2); if (l.IsValid) { Guid id = Guid.NewGuid(); m_lines_dict.Add(id, l); return id; } return Guid.Empty; } readonly System.Collections.Generic.Dictionary m_lines_dict = new System.Collections.Generic.Dictionary(); } ``` ```python # No Python sample available ``` -------------------------------------------------------------------------------- # Custom Undo Source: https://developer.rhino3d.com/en/samples/rhinocommon/custom-undo/ Demonstrates how to set up a custom set of actions when the Undo command is called. ```cs partial class Examples { static double MyFavoriteNumber = 0; public static Rhino.Commands.Result CustomUndo(RhinoDoc doc) { // Rhino automatically sets up an undo record when a command is run, // but... the undo record is not saved if nothing changes in the // document (objects added/deleted, layers changed,...) // // If we have a command that doesn't change things in the document, // but we want to have our own custom undo called then we need to do // a little extra work double d = MyFavoriteNumber; if (Rhino.Input.RhinoGet.GetNumber("Favorite number", true, ref d) == Rhino.Commands.Result.Success) { double current_value = MyFavoriteNumber; doc.AddCustomUndoEvent("Favorite Number", OnUndoFavoriteNumber, current_value); MyFavoriteNumber = d; } return Rhino.Commands.Result.Success; } // event handler for custom undo static void OnUndoFavoriteNumber(object sender, Rhino.Commands.CustomUndoEventArgs e) { // !!!!!!!!!! // NEVER change any setting in the Rhino document or application. Rhino // handles ALL changes to the application and document and you will break // the Undo/Redo commands if you make any changes to the application or // document. This is meant only for your own private plugin data // !!!!!!!!!! // This function can be called either by undo or redo // In order to get redo to work, add another custom undo event with the // current value. If you don't want redo to work, just skip adding // a custom undo event here double current_value = MyFavoriteNumber; e.Document.AddCustomUndoEvent("Favorite Number", OnUndoFavoriteNumber, current_value); double old_value = (double)e.Tag; RhinoApp.WriteLine("Going back to your favorite = {0}", old_value); MyFavoriteNumber = old_value; } } ``` ```python import Rhino import scriptcontext def OnUndoFavoriteNumber(sender, e): """!!!!!!!!!! NEVER change any setting in the Rhino document or application. Rhino handles ALL changes to the application and document and you will break the Undo/Redo commands if you make any changes to the application or document. This is meant only for your own private plugin data !!!!!!!!!! This function can be called either by undo or redo In order to get redo to work, add another custom undo event with the current value. If you don't want redo to work, just skip adding a custom undo event here """ current_value = scriptcontext.sticky["FavoriteNumber"] e.Document.AddCustomUndoEvent("Favorite Number", OnUndoFavoriteNumber, current_value) old_value = e.Tag print("Going back to your favorite =", old_value) scriptcontext.sticky["FavoriteNumber"]= old_value; def TestCustomUndo(): """ Rhino automatically sets up an undo record when a command is run, but... the undo record is not saved if nothing changes in the document (objects added/deleted, layers changed,...) If we have a command that doesn't change things in the document, but we want to have our own custom undo called then we need to do a little extra work """ current_value = 0 if "FavoriteNumber" in scriptcontext.sticky: current_value = scriptcontext.sticky["FavoriteNumber"] rc, new_value = Rhino.Input.RhinoGet.GetNumber("Favorite number", True, current_value) if rc!=Rhino.Commands.Result.Success: return scriptcontext.doc.AddCustomUndoEvent("Favorite Number", OnUndoFavoriteNumber, current_value); scriptcontext.sticky["FavoriteNumber"] = new_value if __name__=="__main__": TestCustomUndo() ``` -------------------------------------------------------------------------------- # Custom Undo Events Source: https://developer.rhino3d.com/en/guides/cpp/custom-undo-events/ This guide discusses custom undo events. ## Overview The Rhino C/C++ SDK supports adding custom undo events. This way plugins can take advantage of Rhino's built in *Undo*, *Redo*, *UndoSelected*, *UndoMultiple*, *RedoMultiple* and *ClearUndo* commands, and any future Rhino commands that deal with undo/redo operations. ## How To Basically, you create a class derived from `CRhinoUndoEventHandler`, pass it to Rhino, and let Rhino deal with all the details. Sample 1 below shows how to do this. In order to have stable plugins, developers must follow the rules for using `CRhinoDoc::AddCustomUndoEvent`... ```cpp // Description: // If you want to your plugin to do something when the Rhino // Undo/Redo command runs, the call AddCustomUndoEvent during // your command. // // This function is for expert plugin developers. If you // don't do a good job here, you will really break Rhino. // // Parameters: // undo_event_handler - [in] // Pointer to a class allocated with a call to new. // Never delete this class. // Never pass a pointer to a stack variable. // // Returns: // If a non zero number is returned, then this is the runtime // serial number Rhino has assigned to this undo event. // If zero is returned, then the user has disabled undo // and undo_event_handler was deleted. unsigned int AddCustomUndoEvent( CRhinoUndoEventHandler* undo_event_handler ); ``` When you are debugging your code, you may find the *AuditUndo* command useful. This command lists all the undo information saved in Rhino. You can also use and event watcher to monitor Rhino undo/redo activity. Sample 2 (below) is a simple undo event watcher that reports undo activity in the Rhino command windows... ## Samples ### Sample 1 This "plugin" has one piece of data, the account balance, and two commands, *EarnTenEuros* and *SpendFiveEuros*. The commands use `CUndoEuroTransaction` class to provide integrated Undo/Redo support... ```cpp //////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////// // // BEGIN CUndoEuroTransaction class definition // static double m_plugin_data_euro_balance = 0.0; class CUndoEuroTransaction : public CRhinoUndoEventHandler { public: CUndoEuroTransaction(); ~CUndoEuroTransaction(); // virtual CRhinoUndoEventHandler function void Undo( const CRhinoCommand* cmd, const wchar_t* action_description, bool bCreatedByRedo, unsigned int undo_event_sn ); double m_amount; }; CUndoEuroTransaction::CUndoEuroTransaction() { m_amount = 0.0; } CUndoEuroTransaction::~CUndoEuroTransaction() { } void CUndoEuroTransaction::Undo( const CRhinoCommand* cmd, const wchar_t* action_description, bool bCreatedByRedo, unsigned int undo_event_sn ) { // undo the change to m_plugin_data_euro_balance m_plugin_data_euro_balance -= m_amount; RhinoApp().Print(L"New balance %g euros.\n", m_plugin_data_euro_balance); } // // END CUndoEuroTransaction class definition // //////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////// // // BEGIN EarnTenEuros command // class CCommandEarnTenEuros : public CRhinoCommand { public: CCommandEarnTenEuros() {} ~CCommandEarnTenEuros() {} UUID CommandUUID() { // {658C21B8-221-4887-B4A4-09C39C3E38CA} static const GUID EarnTenEurosCommand_UUID = { 0x658C21B8, 0x221, 0x4887, { 0xB4, 0xA4, 0x09, 0xC3, 0x9C, 0x3E, 0x38, 0xCA } }; return EarnTenEurosCommand_UUID; } const wchar_t* EnglishCommandName() { return L"EarnTenEuros"; } CRhinoCommand::result RunCommand( const CRhinoCommandContext& ); }; // The one and only CCommandEarnTenEuros object static class CCommandEarnTenEuros theEarnTenEurosCommand; CRhinoCommand::result CCommandEarnTenEuros::RunCommand( const CRhinoCommandContext& context ) { double amount = 10.0; CUndoEuroTransaction* pUndoHandler = new CUndoEuroTransaction(); pUndoHandler->m_amount = amount; context.m_doc.AddCustomUndoEvent(pUndoHandler); m_plugin_data_euro_balance += amount; RhinoApp().Print(L"New balance %g euros.\n", m_plugin_data_euro_balance); return CRhinoCommand::success; } // // END EarnTenEuros command // //////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////// // // BEGIN SpendFiveEuros command // class CCommandSpendFiveEuros : public CRhinoCommand { public: CCommandSpendFiveEuros() {} ~CCommandSpendFiveEuros() {} UUID CommandUUID() { // {578712DE-D65C-4B9C-9EE6-401C57077057} static const GUID SpendFiveEurosCommand_UUID = { 0x578712DE, 0xD65C, 0x4B9C, { 0x9E, 0xE6, 0x40, 0x1C, 0x57, 0x07, 0x70, 0x57 } }; return SpendFiveEurosCommand_UUID; } const wchar_t* EnglishCommandName() { return L"SpendFiveEuros"; } CRhinoCommand::result RunCommand( const CRhinoCommandContext& ); }; // The one and only CCommandSpendFiveEuros object static class CCommandSpendFiveEuros theSpendFiveEurosCommand; CRhinoCommand::result CCommandSpendFiveEuros::RunCommand( const CRhinoCommandContext& context ) { double amount = -5.0; CUndoEuroTransaction* pUndoHandler = new CUndoEuroTransaction(); pUndoHandler->m_amount = amount; context.m_doc.AddCustomUndoEvent(pUndoHandler); m_plugin_data_euro_balance += amount; RhinoApp().Print(L"New balance %g euros.\n", m_plugin_data_euro_balance); return CRhinoCommand::success; } // // END SpendFiveEuros command // //////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////// ``` ### Sample 2 Using an event watcher to monitor undo events... ```cpp //////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////// // // BEGIN CMyUndoSnooper undo event watcher // class CMyUndoSnooper : public CRhinoEventWatcher { public: // virtual void UndoEvent( CRhinoEventWatcher::undo_event event, unsigned int undo_record_serialnumber, const CRhinoCommand* cmd ); }; void CMyUndoSnooper::UndoEvent( CRhinoEventWatcher::undo_event event, unsigned int undo_record_serialnumber, const CRhinoCommand* cmd ) { CRhinoApp& app = RhinoApp(); const wchar_t* cmd_name = cmd ? cmd->LocalCommandName() : 0; if( 0 == cmd_name ) cmd_name = L; switch(event) { case begin_recording: // Begin recording changes app.Print(L"UNDO SNOOP %5d: Begin recording %s changes\n", undo_record_serialnumber,cmd_name); break; case end_recording: app.Print(L"UNDO SNOOP %5d: End recording %s changes\n", undo_record_serialnumber,cmd_name); break; case begin_undo: // Begin undoing a changes app.Print(L"UNDO SNOOP %5d: Begin undoing %s changes\n", undo_record_serialnumber,cmd_name); break; case end_undo: app.Print(L"UNDO SNOOP %5d: End undoing %s changes\n", undo_record_serialnumber,cmd_name); break; case begin_redo: // Begin redoing a changes app.Print(L"UNDO SNOOP %5d: Begin redoing %s changes\n", undo_record_serialnumber,cmd_name); break; case end_redo: app.Print(L"UNDO SNOOP %5d: End redoing %s changes\n", undo_record_serialnumber,cmd_name); break; case purge_record: app.Print(L"UNDO SNOOP %5d: Purging %s changes\n", undo_record_serialnumber,cmd_name); break; } } // // END CMyUndoSnooper undo event watcher // //////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////// // // BEGIN TestUndoSnoop command // class CCommandTestUndoSnoop : public CRhinoTestCommand { public: CCommandTestUndoSnoop() { m_bRegistered = 0; } ~CCommandTestUndoSnoop() {} UUID CommandUUID() { // {A7474CED-3C3D-4BCD-9124-4058D619BEA0} static const GUID TestUndoSnoopCommand_UUID = { 0xA7474CED, 0x3C3D, 0x4BCD, { 0x91, 0x24, 0x40, 0x58, 0xD6, 0x19, 0xBE, 0xA0 } }; return TestUndoSnoopCommand_UUID; } const wchar_t* EnglishCommandName() { L"TestUndoSnoop"; } CRhinoCommand::result RunCommand( const CRhinoCommandContext& ); CMyUndoSnooper m_snooper; bool m_bRegistered; }; // The one and only CCommandTestUndoSnoop object static class CCommandTestUndoSnoop theTestUndoSnoopCommand; CRhinoCommand::result CCommandTestUndoSnoop::RunCommand( const CRhinoCommandContext& context ) { if( IDYES == RhinoMessageBox( L"Watch undo events?", L"Undo Snooper", MB_YESNO | MB_ICONQUESTION) ) { if( !m_bRegistered ) { m_bRegistered = true; m_snooper.Register(); } m_snooper.Enable( true ); } else { m_snooper.Enable( false ); } return CRhinoCommand::success; } // // END TestUndoSnoop command // //////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////// ``` -------------------------------------------------------------------------------- # Data from Ellipses Source: https://developer.rhino3d.com/en/guides/opennurbs/data-from-ellipses/ This guide discusses ellipses and their representation in openNURBS. ## Problem You have drawn an ellipse in Rhino, using the *Ellipse* command. While reading the *.3dm* file, using openNURBS, the ellipse is classified as as `ON::curve_object`. How does one get the ellipse's construction data, such as center point, major and minor axes, etc? ## Solution Unlike some curve types (e.g. Lines, Arcs, Polylines, etc.) which have their own `ON_Curve`-derived classes, there is no special class that is used to represent elliptical curves. Ellipses and elliptical arcs are simply NURBS, or `ON_NurbsCurve`, curves. To test to see if a curve is an ellipse or an elliptical arc, first try to cast the `ON_Curve` object as an `ON_NurbsCurve` object. If successful, then use `ON_NurbsCurve::IsEllipse()` to verify the NURBS curve is an ellipse and to obtain its construction data. ## Sample ```cpp ONX_Model model = ... ONX_ModelComponentIterator it(model, ON_ModelComponent::Type::ModelGeometry); const ON_ModelComponent* model_component = nullptr; for (model_component = it.FirstComponent(); nullptr != model_component; model_component = it.NextComponent()) { const ON_ModelGeometryComponent* model_geometry = ON_ModelGeometryComponent::Cast(model_component); if (nullptr != model_geometry) { // Test for NURBS curve object const ON_NurbsCurve* nurb = ON_NurbsCurve::Cast(model_geometry->Geometry(nullptr)); if (nullptr != nurb) { ON_Ellipse ellipse; double tolerance = model.m_settings.m_ModelUnitsAndTolerances.m_absolute_tolerance; bool rc = nurb->IsEllipse(nullptr, &ellipse, tolerance); if (rc) { // Center ON_3dPoint origin = ellipse.plane.origin; // Major and minor axes ON_3dVector xaxis = ellipse.radius[0] * ellipse.plane.xaxis; ON_3dVector yaxis = ellipse.radius[1] * ellipse.plane.yaxis; // Quad points ON_3dPoint p0(origin - xaxis); ON_3dPoint p1(origin + xaxis); ON_3dPoint p2(origin - yaxis); ON_3dPoint p3(origin + yaxis); // Foci ON_3dPoint f1, f2; ellipse.GetFoci(f1, f2); // TODO: do something with ellipse } } } } ``` -------------------------------------------------------------------------------- # Debug in RhinoWIP (Mac) Source: https://developer.rhino3d.com/en/guides/rhinocommon/debug-rhinowip-mac/ This guide explains how to debug using the RhinoWIP for Mac.

WARNING

This guide is deprecated. You can debug in the RhinoWIP using the RhinoCommon Visual Studio for Mac extension. See the Installing Tools (Mac) guide for more information. By the end of this guide, you should understand how to modify your plugin's C# project file in order to target and debug using RhinoWIP. ## Prerequisites This guide presumes you have [installed all the necessary tools](/guides/rhinocommon/installing-tools-mac) and know how to [build and debug a plugin using RhinoCommon with Rhinoceros](/guides/rhinocommon/your-first-plugin-mac). It also presumes you have downloaded and installed [the latest RhinoWIP](http://www.rhino3d.com/go/download/rhino-for-mac/wip/latest). ## Edit References Your plugin requires references to RhinoCommon dlls that are contained within the Rhino application bundle. The default RhinoCommon Plugin template that comes with the Rhino Xamarin Studio AddIn references *Eto*, *Rhino.UI*, and *RhinoCommon*: ![Bundle References](https://developer.rhino3d.com/images/debug-rhinowip-mac-01.png) ...which are all contained within the *Rhinoceros.app* bundle. We want to target those found in the *RhinoWIP.app* bundle. Unfortunately, Visual Studio for Mac does not allow you to browse to references that are contained within an application bundle. We will have to "manually" change these references so that they target the appropriate versions contained in RhinoWIP. #### Step-by-Step 1. In Visual Studio for Mac, *right/option-click* on the project name and select *Tools* > *Edit File*... ![Visual Studio for Mac Edit File](https://developer.rhino3d.com/images/debug-rhinowip-mac-02.png) 1. Use Visual Studio for Mac's *Search* > *Replace* function to find *\Applications\Rhinoceros.app* and replace it with *\Applications\RhinoWIP.app*... ![Search and Replace](https://developer.rhino3d.com/images/debug-rhinowip-mac-03.png) 1. Verify that these changes are only happening with the `` that contains `` entries. Accept your *\Applications\RhinoWIP.app* replacements to make the change. If you are not using Nuget packages, you will also need to add some changes to the default hint paths so they search the proper location within the app bundle: ..\..\Applications\RhinoWIP.app\Contents\Frameworks\RhCore.framework\Resources\RhinoCommon.dll False ..\..\Applications\RhinoWIP.app\Contents\Frameworks\RhCore.framework\Resources\Rhino.UI.dll False ..\..\Applications\RhinoWIP.app\Contents\Frameworks\RhCore.framework\Resources\Eto.dll False 1. *Save* and *Close* your project's *.csproj*. 1. The project will reload automatically. In the *Solution Explorer*, select any of the three references you just changed above. If you examine their properties (*right/option-click* > *Properties*), you will notice they are now referencing the *RhinoWIP.app* versions. 1. *Build* and *Run*. Your plugin's *debugging session* should now *launch with RhinoWIP*. -------------------------------------------------------------------------------- # Delete Block Instance Definition Source: https://developer.rhino3d.com/en/samples/rhinocommon/delete-block-instance-definition/ Demonstrates how to delete a block instance definition given the name of the block. ```cs partial class Examples { public static Result DeleteBlock(RhinoDoc doc) { // Get the name of the instance definition to rename string instance_definition_name = ""; var rc = RhinoGet.GetString("Name of block to delete", true, ref instance_definition_name); if (rc != Result.Success) return rc; if (string.IsNullOrWhiteSpace(instance_definition_name)) return Result.Nothing; // Verify instance definition exists var instance_definition = doc.InstanceDefinitions.Find(instance_definition_name, true); if (instance_definition == null) { RhinoApp.WriteLine("Block \"{0}\" not found.", instance_definition_name); return Result.Nothing; } // Verify instance definition can be deleted if (instance_definition.IsReference) { RhinoApp.WriteLine("Unable to delete block \"{0}\".", instance_definition_name); return Result.Nothing; } // delete block and all references if (!doc.InstanceDefinitions.Delete(instance_definition.Index, true, true)) { RhinoApp.WriteLine("Could not delete {0} block", instance_definition.Name); return Result.Failure; } return Result.Success; } } ``` ```python import rhinoscriptsyntax as rs from scriptcontext import doc def Delete(): blockName = rs.GetString("block to delete") instanceDefinition = doc.InstanceDefinitions.Find(blockName, True) if not instanceDefinition: print("{0} block does not exist".format(blockName)) return rs.DeleteBlock(blockName) if __name__ == "__main__": Delete() ``` -------------------------------------------------------------------------------- # Determine Active Viewport Source: https://developer.rhino3d.com/en/samples/rhinocommon/determine-active-viewport/ Demonstrates how to determine the active viewport name, even in a layout or a detail view. ```cs partial class Examples { public static Rhino.Commands.Result ActiveViewport(Rhino.RhinoDoc doc) { Rhino.Display.RhinoView view = doc.Views.ActiveView; if (view == null) return Rhino.Commands.Result.Failure; Rhino.Display.RhinoPageView pageview = view as Rhino.Display.RhinoPageView; if (pageview != null) { string layout_name = pageview.PageName; if (pageview.PageIsActive) { Rhino.RhinoApp.WriteLine("The layout {0} is active", layout_name); } else { string detail_name = pageview.ActiveViewport.Name; Rhino.RhinoApp.WriteLine("The detail {0} on layout {1} is active", detail_name, layout_name); } } else { string viewport_name = view.MainViewport.Name; Rhino.RhinoApp.WriteLine("The viewport {0} is active", viewport_name); } return Rhino.Commands.Result.Success; } } ``` ```python import Rhino import scriptcontext def ActiveViewport(): view = scriptcontext.doc.Views.ActiveView if view is None: return if isinstance(view, Rhino.Display.RhinoPageView): if view.PageIsActive: print("The layout", view.PageName, "is active") else: detail_name = view.ActiveViewport.Name print("The detail", detail_name, "on layout", view.PageName, "is active") else: print("The viewport", view.MainViewport.Name, "is active") if __name__ == "__main__": ActiveViewport() ``` -------------------------------------------------------------------------------- # Determine an Object's Layer Name Source: https://developer.rhino3d.com/en/samples/cpp/determine-objects-layer-name/ Demonstrates how to determine a selected object's layer name using C++. ```cpp CRhinoCommand::result CCommandTest::RunCommand( const CRhinoCommandContext& context ) { CRhinoGetObject go; go.SetCommandPrompt( L"Select object" ); go.GetObjects( 1, 1 ); if( go.CommandResult() != CRhinoCommand::success ) return go.CommandResult(); const CRhinoObjRef& objref = go.Object(0); const CRhinoObject* object = objref.Object(); if( !object ) return CRhinoCommand::failure; const CRhinoObjectAttributes& attributes = object->Attributes(); int layer_index = attributes.m_layer_index; const CRhinoLayerTable& layer_table = context.m_doc.m_layer_table; const CRhinoLayer& layer = layer_table[layer_index]; ON_wString layer_name = layer.LayerName(); RhinoApp().Print( L"The selected object's layer is \"%s\"\n", layer_name ); return CRhinoCommand::success; } ``` -------------------------------------------------------------------------------- # Determine Language Setting Source: https://developer.rhino3d.com/en/samples/rhinocommon/determine-language-setting/ Demonstrates how to determine Rhino's language setting. ```cs partial class Examples { public static Result DetermineCurrentLanguage(RhinoDoc doc) { var language_id = Rhino.ApplicationSettings.AppearanceSettings.LanguageIdentifier; var culture = new System.Globalization.CultureInfo(language_id); RhinoApp.WriteLine("The current language is {0}", culture.EnglishName); return Result.Success; } } ``` ```python import rhinoscriptsyntax as rs import System locale_id = rs.LocaleID() culture = System.Globalization.CultureInfo(locale_id) print(culture.EnglishName) ``` -------------------------------------------------------------------------------- # Determine Normal Direction of Brep Face Source: https://developer.rhino3d.com/en/samples/rhinocommon/determine-normal-direction-of-brep-face/ Demonstrates how to determine the normal direction of a Brep face at a specified point. ```cs partial class Examples { public static Result DetermineNormalDirectionOfBrepFace(RhinoDoc doc) { // select a surface var gs = new GetObject(); gs.SetCommandPrompt("select surface"); gs.GeometryFilter = ObjectType.Surface; gs.DisablePreSelect(); gs.SubObjectSelect = false; gs.Get(); if (gs.CommandResult() != Result.Success) return gs.CommandResult(); // get the selected face var face = gs.Object(0).Face(); if (face == null) return Result.Failure; // pick a point on the surface. Constain // picking to the face. var gp = new GetPoint(); gp.SetCommandPrompt("select point on surface"); gp.Constrain(face, false); gp.Get(); if (gp.CommandResult() != Result.Success) return gp.CommandResult(); // get the parameters of the point on the // surface that is clesest to gp.Point() double u, v; if (face.ClosestPoint(gp.Point(), out u, out v)) { var direction = face.NormalAt(u, v); if (face.OrientationIsReversed) direction.Reverse(); RhinoApp.WriteLine( string.Format( "Surface normal at uv({0:f},{1:f}) = ({2:f},{3:f},{4:f})", u, v, direction.X, direction.Y, direction.Z)); } return Result.Success; } } ``` ```python import rhinoscriptsyntax as rs import Rhino from Rhino.Commands import Result def RunCommand(): # select a surface gs = Rhino.Input.Custom.GetObject() gs.SetCommandPrompt("select surface") gs.GeometryFilter = Rhino.DocObjects.ObjectType.Surface gs.DisablePreSelect() gs.SubObjectSelect = False gs.Get() if gs.CommandResult() != Result.Success: return gs.CommandResult() # get the selected face face = gs.Object(0).Face() if face == None: return # pick a point on the surface. Constain # picking to the face. gp = Rhino.Input.Custom.GetPoint() gp.SetCommandPrompt("select point on surface") gp.Constrain(face, False) gp.Get() if gp.CommandResult() != Result.Success: return gp.CommandResult() # get the parameters of the point on the # surface that is clesest to gp.Point() b, u, v = face.ClosestPoint(gp.Point()) if b: dir = face.NormalAt(u, v) if face.OrientationIsReversed: dir.Reverse() print("Surface normal at uv({0:f},{1:f}) = ({2:f},{3:f},{4:f})".format( u, v, dir.X, dir.Y, dir.Z)) if __name__ == "__main__": RunCommand() ``` -------------------------------------------------------------------------------- # Determine Object Layer Source: https://developer.rhino3d.com/en/samples/rhinocommon/determine-object-layer/ Demonstrates how to determine which layer a user-specified object is on and print the name. ```cs partial class Examples { public static Rhino.Commands.Result DetermineObjectLayer(Rhino.RhinoDoc doc) { Rhino.DocObjects.ObjRef obref; Rhino.Commands.Result rc = Rhino.Input.RhinoGet.GetOneObject("Select object", true, Rhino.DocObjects.ObjectType.AnyObject, out obref); if (rc != Rhino.Commands.Result.Success) return rc; Rhino.DocObjects.RhinoObject rhobj = obref.Object(); if (rhobj == null) return Rhino.Commands.Result.Failure; int index = rhobj.Attributes.LayerIndex; string name = doc.Layers[index].Name; Rhino.RhinoApp.WriteLine("The selected object's layer is '{0}'", name); return Rhino.Commands.Result.Success; } } ``` ```python import Rhino import scriptcontext import System.Guid def DetermineObjectLayer(): rc, obref = Rhino.Input.RhinoGet.GetOneObject("Select object", True, Rhino.DocObjects.ObjectType.AnyObject) if rc!=Rhino.Commands.Result.Success: return rc rhobj = obref.Object() if rhobj is None: return Rhino.Commands.Result.Failure index = rhobj.Attributes.LayerIndex name = scriptcontext.doc.Layers[index].Name print("The selected object's layer is '", name, "'") return Rhino.Commands.Result.Success if __name__ == "__main__": DetermineObjectLayer() ``` -------------------------------------------------------------------------------- # Determine the Maximum Z Coordinate Value of a Surface Source: https://developer.rhino3d.com/en/samples/cpp/determine-max-z-of-surface/ Demonstrates how determine the maximum Z coordinate value of a surface or polysurface given some X,Y coordinate. ```cpp CRhinoCommand::result CCommandTest::RunCommand( const CRhinoCommandContext& context ) { double x = 0.0, y = 0.0; ON_wString sx, sy, sz; // Pick a brep to evaluate CRhinoGetObject go; go.SetCommandPrompt( L"Select surface or polysurface to evaluate" ); go.SetGeometryFilter( CRhinoGetObject::surface_object|CRhinoGetObject::polysrf_object ); go.GetObjects( 1, 1 ); if( go.CommandResult() != success ) return go.CommandResult(); // Get the brep geometry const CRhinoObjRef& ref = go.Object(0); const ON_Brep* brep = ref.Brep(); if( !brep ) return failure; // Prompt for an X coordinate value CRhinoGetNumber gn; gn.SetCommandPrompt( L"Value of X coordinate" ); gn.SetDefaultNumber( x ); gn.GetNumber(); if( gn.CommandResult() != success ) return gn.CommandResult(); x = gn.Number(); RhinoFormatNumber( x, sx ); // Prompt for a Y coordinate value gn.SetCommandPrompt( L"Value of Y coordinate" ); gn.SetDefaultNumber( y ); gn.GetNumber(); if( gn.CommandResult() != success ) return gn.CommandResult(); y = gn.Number(); RhinoFormatNumber( y, sy ); // Now that we have all of the input, we want to intersect // a line curve with the brep. To determine the magnitude of // the line, we will first get the brep's bounding box. ON_BoundingBox bbox; if( !brep->GetTightBoundingBox(bbox) ) return failure; // Calculate the height of the bounding box ON_3dVector v = bbox.Corner(0,0,1) - bbox.Corner(0,0,0); // Starting point of line ON_3dPoint p0( x, y, 0.0 ); // Ending point of line. To make sure the line // completely intersects the brep, we will double // the height. ON_3dPoint p1( x, y, v.Length() * 2 ); // Create the line curve ON_LineCurve line( p0, p1 ); // Intersect the line with the brep ON_SimpleArray curves; ON_3dPointArray points; bool rc = RhinoCurveBrepIntersect( line, *brep, context.m_doc.AbsoluteTolerance(), curves, points ); if( false == rc | 0 == points.Count() ) { RhinoApp().Print( L"No maximum surface Z coordinate at %s,%s found.\n", sx, sy ); return nothing; } // Because it is possible that our line might intersect // the brep in more than one place, find the intersection // point that is farthest from our input coordinate. int i; ON_3dPoint pt = p0; for( i = 0; i < points.Count(); i++ ) { if( p0.DistanceTo(points[i]) > p0.DistanceTo(pt) ) pt = points[i]; } // Print results RhinoFormatNumber( pt.z, sz ); RhinoApp().Print( L"Maximum surface Z coordinate at %s,%s is %s.\n", sx, sy, sz ); // Optional, add a point object context.m_doc.AddPointObject( pt ); // Delete any overlap intersection curves that might have been calculated for( i = 0; i < curves.Count(); i++ ) { delete curves[i]; curves[i] = 0; } context.m_doc.Redraw(); return success; } ``` -------------------------------------------------------------------------------- # Determining Curve Object Types Source: https://developer.rhino3d.com/en/guides/cpp/determining-curve-object-types/ This guide demonstrates how to determine the curve type using C/C++. ## Problem Given an `ON_Curve`, how can one determine which kind of `ON_Curve`-derived object it really is? (e.g. `ON_LineCurve`, `ON_ArcCurve`, `ON_PolylineCurve`, `ON_PolyCurve`, `ON_NurbsCurve`, etc.)? ## Solution Basically, you try to cast the `ON_Curve` object to one of the `ON_Curve`-derived classes using its `Cast` operator. If the cast operation is success, you are good to go. If it fails, then you know the test object is some other `ON_Curve`-derived object. ## Sample ```cpp const ON_LineCurve* GetLineCurve( const ON_Curve* crv ) { const ON_LineCurve* p = 0; if( crv != 0 ) p = ON_LineCurve::Cast( crv ); return p; } const ON_ArcCurve* GetArcCurve( const ON_Curve* crv ) { const ON_ArcCurve* p = 0; if( crv != 0 ) p = ON_ArcCurve::Cast( crv ); return p; } const ON_PolylineCurve* GetPolylineCurve( const ON_Curve* crv ) { const ON_PolylineCurve* p = 0; if( crv != 0 ) p = ON_PolylineCurve::Cast( crv ); return p; } const ON_PolyCurve* GetPolyCurve( const ON_Curve* crv ) { const ON_PolyCurve* p = 0; if( crv != 0 ) p = ON_PolyCurve::Cast( crv ); return p; } const ON_NurbsCurve* GetNurbsCurve( const ON_Curve* crv ) { const ON_NurbsCurve* p = 0; if( crv != 0 ) p = ON_NurbsCurve::Cast( crv ); return p; } ``` -------------------------------------------------------------------------------- # Determining if a Brep is a Box Source: https://developer.rhino3d.com/en/guides/cpp/determining-if-brep-is-box/ This brief guide discusses how to determine if a brep object is a box using C/C++. ## Problem You would like to determine whether or not a polysurface is a (brep) box. ## Solution For a polysurface to be a box, the following conditions must be true: 1. The brep must be solid. 1. The brep must have six faces. 1. Each of the six faces must be planar. 1. Each of the planar face must be either perpendicular or antiparallel to the other planar faces. ## Sample The following sample functions demonstrate how to determine whether or not a polysurface is a box. ```cpp bool IsBrepBox( const ON_Brep& brep ) { double zero_tolerance = 1.0e-6; // or whatever ON_3dVector N[6]; bool rc = brep.IsSolid(); if (rc) rc = (brep.m_F.Count() == 6); if (rc) { for (int i = 0; rc && i < 6; i++) { ON_Plane plane; if (brep.m_F[i].IsPlanar(&plane, zero_tolerance)) { N[i] = plane.zaxis; N[i].Unitize(); } else rc = false; } } if (rc) { for (int i = 0; rc && i < 6; i++) { int count = 0; for (int j = 0; rc && j < 6; j++) { double dot = fabs(N[i] * N[j]); if (fabs(dot) <= zero_tolerance) continue; if (fabs(dot - 1.0) <= zero_tolerance) count++; else rc = false; } if (rc) { if (2 != count) rc = false; } } } return rc; } ``` -------------------------------------------------------------------------------- # Determining Language Setting Source: https://developer.rhino3d.com/en/guides/cpp/determining-language-setting/ This guide demonstrates how to determine the language setting when developing localized C/C++ plugins. ## Overview Rhino provides support for multiple languages. Rhino's language setting is independent of the operating system's language setting. Thus, as a plugin developer, knowing Rhino's current language setting is important if you plan on supporting multiple languages. Rhino stores its current Locale ID (LCID) in a `CRhinoAppAppearanceSettings` object that is held within Rhino applications settings object, or `CRhinoAppSettings`. ## Sample The following is an example of how to determine Rhino's current language setting. ```cpp ON_wString wstr; CRhinoAppSettings settings = ::RhinoApp().AppSettings(); CRhinoAppAppearanceSettings appearance = settings.AppearanceSettings(); switch( appearance.m_language_identifier ) { case 1028: // zh-tw wstr = L"Chinese - Taiwan"; break; case 1029: // cs wstr = L"Czech"; break; case 1031: // de-de wstr = L"German - Germany"; break; case 1033: // en-us wstr = L"English - United States"; break; case 1034: // es-es wstr = L"Spanish - Spain"; break; case 1036: // fr-fr wstr = L"French - France"; break; case 1040: // it-it wstr = L"Italian - Italy"; break; case 1041: // ja wstr = L"Japanese"; break; case 1042: // ko wstr = L"Korean"; break; case 1045: // pl wstr = L"Polish"; break; case 2052: // zh-cn wstr = L"Chinese - China"; break; default: wstr = L"unknown"; break; } ::RhinoApp().Print( L"The current language is \"%s\ , wstr ); ``` -------------------------------------------------------------------------------- # Determining Selected Groups Source: https://developer.rhino3d.com/en/samples/rhinoscript/determining-selected-groups/ Demonstrates how to determine what object groups are selected using RhinoScript. ```vbnet Option Explicit ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' Description ' Returns an array of objects group names ' from selected objects. ' Parameters ' none ' Returns ' An array of object group names or vbNull if none ' of the selected objects belongs to a group. ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Function SelectedGroups() ' Declare locals Dim arrObjects, arrGroups(), strGroup Dim i, nCount, bAppend SelectedGroups = Null ' Default nCount = -1 ' Get the selected objects arrObjects = Rhino.SelectedObjects If Not IsArray(arrObjects) Then Exit Function ' Process each object. If the object belongs to an ' object group and that group name is not already ' in arrGroups, then append it to the end. For i = 0 To UBound(arrObjects) bAppend = False strGroup = Rhino.ObjectTopGroup(arrObjects(i)) If Not IsNull(strGroup) Then If (nCount = -1) Then bAppend = True ElseIf (FindGroup(strGroup, arrGroups) = -1) Then bAppend = True End If If bAppend = True Then nCount = nCount + 1 ReDim Preserve arrGroups(nCount) arrGroups(nCount) = strGroup End If End If Next ' Return the array of group names If (nCount > -1) Then SelectedGroups = arrGroups End Function ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' Description ' Searches an array of strings ' Parameters ' strGroup - the name to look for ' arrGroups - the array to search ' Returns ' >= 0 if strGroup is found in arrGroups ' -1 otherwise ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Function FindGroup(strGroup, arrGroups) Dim i FindGroup = -1 ' Default For i = 0 To UBound(arrGroups) If (StrComp(strGroup, arrGroups(i), 1) = 0) Then FindGroup = i Exit Function End If Next End Function ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' Test our SelectedGroups function ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Sub TestSelectedGroups Dim i, arrGroups arrGroups = SelectedGroups If IsArray(arrGroups) Then For i = 0 To UBound(arrGroups) Rhino.Print( arrGroups(i) ) Next End If End Sub ``` -------------------------------------------------------------------------------- # Determining the Active Viewport Source: https://developer.rhino3d.com/en/guides/cpp/determining-the-active-viewport/ This guide demonstrates how to determine the active viewport using C/C++. ## Problem You are trying to determine if the current active view is a detail or a standard view. You are having some trouble differentiating between an active page layout and an active detail in a page layout. ## Solution Standard Rhino views are represented by the `CRhinoView` class. Layout views are represented by the `CRhinoPageView` class. This class is derived from `CRhinoView`. A `CRhinoPageView` object maintains an array of `CRhinoDetailViewObject` objects - one for each detail in the layout. To determine whether a layout or one if it's details is active, get the UUID of the layout's active detail object. If the returned UUID is `nil`, then the layout itself is active. Otherwise, the detail object that has the returned UUID is active. ## Sample ```cpp CRhinoCommand::result CCommandTest::RunCommand( const CRhinoCommandContext& context ) { CRhinoView* view = RhinoApp().ActiveView(); if (nullptr == view) return CRhinoCommand::failure; CRhinoPageView* page_view = static_cast(view); if (page_view) { ON_wString layout_name = page_view->MainViewport().Name(); ON_UUID active_detail_uuid = page_view->ActiveDetailObject(); if (ON_UuidIsNotNil(active_detail_uuid)) { ON_wString detail_name = page_view->ActiveViewport().Name(); RhinoApp().Print(L"The detail \"%s\" on layout \"%s\" is active.\n", static_cast(detail_name), static_cast(layout_name) ); } else { RhinoApp().Print(L"The layout \"%s\" is active.\n", static_cast(layout_name) ); } } else { ON_wString viewport_name = view->ActiveViewport().Name(); RhinoApp().Print(L"The viewport \"%s\" is active.\n", static_cast(viewport_name) ); } return CRhinoCommand::success; } ``` -------------------------------------------------------------------------------- # Determining the Normal Direction of a Brep Face Source: https://developer.rhino3d.com/en/guides/cpp/determining-normal-direction-of-brep-face/ This guide demonstrates how to determine the normal direction of a Brep face using C/C++. ## Overview To determine the normal direction of a surface, you can use one of the following functions: ```cpp ON_Surface::NormalAt() ON_Surface::EvNormal() ``` To determine the normal direction of a face which is part of a Brep, you can also use the above functions, as an `ON_BrepFace` object is derived from `ON_Surface`. But, you will also need to take into account the orientation of the Brep face. If the orientation of the Brep face is opposite of the underlying, natural surface orientation, then you will need to reverse the direction of the calculated normal vector. It should also be noted that most surfaces in Rhino are really Breps with a single face. ## Sample The following sample code demonstrates how to interactively determine the normal direction of a selected surface or Brep face. ```cpp CRhinoCommand::result CCommandNormal::RunCommand( const CRhinoCommandContext& context ) { // Select a surface CRhinoGetObject go; go.SetCommandPrompt( L"Select surface" ); go.SetGeometryFilter( CRhinoGetObject::surface_object ); go.GetObjects( 1, 1 ); if( go.CommandResult() != success ) return go.CommandResult(); // Get the selected face const CRhinoObjRef& ref = go.Object(0); const ON_BrepFace* face = ref.Face(); if( 0 == face ) return failure; // Pick a point on the surface. Constrain // picking to the face. CRhinoGetPoint gp; gp.SetCommandPrompt( L"Select point on surface" ); gp.Constrain( *face ); gp.GetPoint(); if( gp.CommandResult() != success ) return gp.CommandResult(); ON_3dPoint pt = gp.Point(); // Get the parameters of the point on the // surface that is closest to pt double u, v; if( face->GetClosestPoint(pt, &u, &v) ) { ON_3dPoint pt; ON_3dVector du, dv, dir; if( face->EvNormal(u, v, pt, du, dv, dir) ) { // if the face orientation is opposite of // the natural surface orientation, then // reverse the direction of the vector. if( face->m_bRev ) dir.Reverse(); RhinoApp().Print( L"Surface normal at uv(%.2f,%.2f) = (%.2f,%.2f,%.2f)\n", u, v, dir.x, dir.y, dir.z ); } } return success; } ``` -------------------------------------------------------------------------------- # Developer Prerequisites Source: https://developer.rhino3d.com/en/guides/general/rhino-developer-prerequisites/ This guide describes the main requirements to develop for Rhino. There are a number of prerequisites required to do Rhino development. Broadly speaking, these can be divided into three categories, ranked in ascending order of difficulty: 1. [Hardware prerequisites](#hardware) 1. [Software prerequisites](#software) 1. [Programming Knowledge](#programming-knowledge) ## Hardware If you are reading this guide, you likely already have a computer that can run Rhino. (If not, Rhino has some minimum [System Requirements](http://www.rhino3d.com/system_requirements/) that you should review before acquiring any hardware). Generally speaking, any computer that can run Rhino *ought* to be able to run the developer tools outlined in the [Software](#software) section. If you are a Windows user and wish to develop plugins for Rhino for Mac, you will need an Apple Mac computer. Conversely, if you are an macOS user and you wish to develop for Rhino for Windows, you will need a computer that can run Rhino for Windows (however, virtual machines running Windows under macOS can potentially work just fine). ## Software Depending on what you want to do, the software prerequisites vary. However, in general, you will need: - [Rhinoceros](http://www.rhino3d.com/download) - A code editor. There are many options...here are a few: - [Visual Studio for Windows](https://www.visualstudio.com): Microsoft's flagship Integrated Development Environment (IDE) for Windows. - [Visual Studio Code](https://code.visualstudio.com/): The best free cross-platform editor See the [SDK-specific guides](/guides/) for the software prerequisites...normally found in the *"Installing Tools"* guides. ## Programming Knowledge Acquiring programming knowledge is the most labor intensive prerequisite. However, learning to program - even trying out a new language - is fun and enriching. Learning to program using Rhino is a great way to begin... ### Learning C# .NET If you wish to write plugins with RhinoCommon, you will need to understand a .NET compatible programming language like C# (or VB). We recommend [C#](https://en.wikipedia.org/wiki/C_Sharp_(programming_language)) (C Sharp) because it is modern, safe, and easy to learn - and you can develop in C# on both Windows and macOS. *Watch*... - [Beginning C# Programming](http://shop.oreilly.com/product/0636920036036.do) By Eric Lippert - Published by O'Reilly Media - [C# Fundamentals for Absolute Beginners](https://learn.microsoft.com/en-us/shows/csharp-fundamentals-for-absolute-beginners/) on Microsoft's Virtual Academy - [C# and .NET Essential Training](https://www.linkedin.com/learning/c-sharp-and-dot-net-essential-training) on LinkedIn Learning *Read*... - [Programming C# 5.0](http://shop.oreilly.com/product/0636920024064.do) By Ian Griffiths - Published by O'Reilly Media - [C# 5.0 in a Nutshell](http://shop.oreilly.com/product/0636920023951.do) By Joseph Albahari, Ben Albahari - Published by O'Reilly Media *Do*... - [Check out samples](/samples/#rhinocommon) on this site - [Ask for help on Discourse](http://discourse.mcneel.com/c/rhino-developer) ### Learning C/C++ To write plugins for Rhino using the C/C++ SDK, you first need to learn the [C++ programing language](https://en.wikipedia.org/wiki/C%2B%2B) itself. C/C++ is sometimes considered an "advanced" programming language. *Watch*... - [C++: A General Purpose Language](https://learn.microsoft.com/en-us/shows/cplusplus-language-library/) on Microsoft Virtual Academy - [C++ Essential Training](https://www.linkedin.com/learning/c-plus-plus-essential-training-15106801) with Bill Weinmann on LinkedIn Learning *Read*... - [The C Programming Language](https://en.wikipedia.org/wiki/The_C_Programming_Language) by Ian Kernighan and Dennis Ritchie - [Practical C++ Programming](http://shop.oreilly.com/product/9780596004194.do) by Steve Oualline - Published by O'Reilly Media - [C++ Primer Plus](http://www.amazon.com/Primer-Plus-Edition-Developers-Library/dp/0321776402) by Stephen Prata *Do*... - [Check out samples](/samples/#cc) on this site - [Ask for help on Discourse](http://discourse.mcneel.com/c/rhino-developer) ### Learning Python [Python](https://en.wikipedia.org/wiki/Python_(programming_language)) is a fantastic first language and an amazingly flexible additional language to add to your toolkit. *Watch*... - [Google's Python Class](https://developers.google.com/edu/python/) by Google for Education - [Up and Running with Python](http://www.lynda.com/Python-tutorials/Up-Running-Python/122467-2.html) with Joe Marini on Lynda.com *Read*... - [The Python Tutorial](https://docs.python.org/2/tutorial/index.html) - [RhinoPython Primer](http://www.rhino3d.com/download/IronPython/5.0/RhinoPython101) by Skylar Tibbits, Arthur van der Harten, Steve Baer, and David Rutten - [The Python Tutorial](https://docs.python.org/2/tutorial/index.html) by the Python Software Foundation - [Learn Python the Hard Way](http://learnpythonthehardway.org/book/) by Zed A. Shaw - despite the title, this is a beginner's book - [Automate The Boring Stuff With Python](https://automatetheboringstuff.com/) by Al Sweigart *Do*... - [Check out samples](/samples/#rhinopython) on this site - [Ask for help on Discourse](http://discourse.mcneel.com/c/scripting) ### Learning RhinoScript RhinoScript is a scripting tool based on Microsoft's VBScript language. RhinoScript runs in Rhino for Windows. *Read*... - [RhinoScript Primer](http://www.rhino3d.com/download/rhino/5.0/rhinoscript101) by David Rutten - [Microsoft VBScript User's Guide and Language Reference](https://msdn.microsoft.com/en-us/library/t0aew7h6(VS.85).aspx) *Do*... - [Check out samples](/samples/#rhinoscript) on this site - [Ask for help on Discourse](http://discourse.mcneel.com/c/scripting) ### Learning More - [Crafting Interpreters](https://craftinginterpreters.com/) by Robert Nystrom - [Clean Code](https://www.oreilly.com/library/view/clean-code-a/9780136083238/) by Robert C. Martin ## Related topics - [What is a Rhino Plugin?](/guides/general/what-is-a-rhino-plugin/) - C Sharp on Wikipedia - [C++ on Wikipedia](https://en.wikipedia.org/wiki/C%2B%2B) - [Python on Wikipedia](https://en.wikipedia.org/wiki/Python_(programming_language)) -------------------------------------------------------------------------------- # Deviation between two Curves Source: https://developer.rhino3d.com/en/samples/cpp/deviation-between-two-curves/ Demonstrates how to determine the deviation between two curves using the RhinoGetOverlapDistance() function. ```cpp CRhinoCommand::result CCommandTest::RunCommand( const CRhinoCommandContext& context ) { CRhinoGetObject go; go.SetCommandPrompt( L"Select curves to test" ); go.SetGeometryFilter( ON::curve_object ); go.EnableSubObjectSelect( true ); go.AcceptNothing(); go.GetObjects( 2, 2 ); if( go.CommandResult() != success ) return go.CommandResult(); if( go.ObjectCount() != 2 ) return failure; const ON_Curve* crv_a = go.Object(0).Curve(); if( !crv_a ) return failure; const ON_Curve* crv_b = go.Object(1).Curve(); if( !crv_b ) return failure; double tol = context.m_doc.AbsoluteTolerance(); int count = 0; double int_a[3][2]; double int_b[3][2]; double max_a, max_b, max_d; double min_a, min_b, min_d; bool rc = RhinoGetOverlapDistance( crv_a, // curve A 0, // optional domain for curve A crv_b, // curve B 0, // optional domain for curve B tol, // tolerance for common normal detection 0.0, // limits acceptable overlap distance &count, // overlap interval count (0 to 3) int_a, // curve A interval(s) int_b, // curve B interval(s) &max_a, // curve B parameter at maximum overlap distance point &max_b, // curve B parameter at maximum overlap distance point &max_d, // maximum overlap distance &min_a, // curve A parameter at minimum distance point &min_b, // curve B parameter at minimum distance point &min_d // minimum distance between curves ); if( min_d <= ON_ZERO_TOLERANCE ) min_d = 0.0; if( rc ) { if( count == 0 ) { RhinoApp().Print( L"Curves do not overlap.\n" ); } else { context.m_doc.AddPointObject( crv_a->PointAt(max_a) ); context.m_doc.AddPointObject( crv_b->PointAt(max_b) ); context.m_doc.AddPointObject( crv_a->PointAt(min_a) ); context.m_doc.AddPointObject( crv_b->PointAt(min_b) ); context.m_doc.Redraw(); int i; for( i = 0; i < count; i++ ) { ON_3dPoint sp_a = crv_a->PointAt( int_a[i][0] ); ON_3dPoint sp_b = crv_b->PointAt( int_b[i][0] ); double sd = sp_a.DistanceTo( sp_b ); ON_3dPoint ep_a = crv_a->PointAt( int_a[i][1] ); ON_3dPoint ep_b = crv_b->PointAt( int_b[i][1] ); double ed = ep_a.DistanceTo( ep_b ); RhinoApp().Print( L"Overlap interval %d of %d:\n", i+1, count ); RhinoApp().Print( L" start: distance = %g\n", sd ); RhinoApp().Print( L" crvA(%10g) = (%g, %g, %g)\n", int_a[i][0], sp_a.x, sp_a.y, sp_a.z ); RhinoApp().Print( L" crvB(%10g) = (%g, %g, %g)\n", int_b[i][0], sp_b.x, sp_b.y, sp_b.z ); RhinoApp().Print( L" end: distance = %g\n", ed ); RhinoApp().Print( L" crvA(%10g) = (%g, %g, %g)\n", int_a[i][1], ep_a.x, ep_a.y, ep_a.z ); RhinoApp().Print( L" crvB(%10g) = (%g, %g, %g)\n", int_b[i][1], ep_b.x, ep_b.y, ep_b.z ); } RhinoApp().Print( L"Minimum deviation = %g\n", min_d ); RhinoApp().Print( L"Maximum deviation = %g\n", max_d ); } } else { RhinoApp().Print( L"Unable to find overlap intervals.\n" ); } return success; } ``` -------------------------------------------------------------------------------- # Disconnected Recordset Sorting Source: https://developer.rhino3d.com/en/guides/rhinoscript/disconnected-recordset-sorting/ This guide demonstrates using a disconnected recordset to sort data. ## Overview If you are dealing with data which requires more than just key-value pairs and fits best in 2-D array, and you wanted to perform sorting and filtering on it, then Disconnected Recordsets will be an excellent option. A disconnected recordset is essentially a database that exists only in memory; it is not tied to a physical database. You create the recordset, add records to it, and then manipulate the data, just like any other recordset. The only difference is that the moment the script terminates, the recordset, which is stored only in memory, disappears as well. To use a disconnected recordset to sort data, you must first create the recordset, adding any fields needed to store the data. After you have created the fields, you then use the `AddNew` method to add new records to the recordset, using the same process used to add new records to a physical database. After the recordset has been populated, a single line of code can then `Sort` the data on the specified field or fields. ## Example The following example demonstrates how to sort an array of 3D points in ascending x, y, z order. The code can be quickly modified to sort the point in any order by simply modifying the `Sort` statement... ```vbnet Sub SortPoints ' Local constants Const adDouble = 5 ' Local variables Dim arrPoints, arrPoint, x, y, z Dim objRecordSet ' Get the coordinates of selected point objects arrPoints = Rhino.GetPointCoordinates If IsNull(arrPoints) Then Exit Sub ' Create a disconnected recordset object Set objRecordSet = CreateObject("ADODB.Recordset") ' Define the fields of the recordset Call objRecordSet.Fields.Append("X", adDouble) Call objRecordSet.Fields.Append("Y", adDouble) Call objRecordSet.Fields.Append("Z", adDouble) ' Open the recordset objRecordSet.Open ' Add the curve data to the recordset For Each arrPoint In arrPoints objRecordSet.AddNew objRecordSet("X").Value = arrPoint(0) objRecordSet("Y").Value = arrPoint(1) objRecordSet("Z").Value = arrPoint(2) objRecordSet.Update Next ' Sort the recordset by x,y,z in ascending order objRecordSet.Sort = "X ASC, Y ASC, Z ASC" ' Iterate through the sorted recordset and print each record's values objRecordSet.MoveFirst Do Until objRecordSet.EOF x = objRecordSet("X").Value y = objRecordSet("Y").Value z = objRecordSet("Z").Value Call Rhino.Print(Rhino.Pt2Str(Array(x, y, z))) objRecordSet.MoveNext Loop objRecordSet.Close End Sub ``` ## Related Topics - [Recordset Object (ADO) on MSDN](http://msdn.microsoft.com/en-us/library/windows/desktop/ms681510(v=vs.85).aspx) - [Use a disconnected recordset to sort large data sets in VBScript on Microsoft Technet](http://technet.microsoft.com/en-us/magazine/2008.09.heyscriptingguy.aspx?pr=PuzzleAnswer) - [How Can I Delete a Record From a Disconnected Recordset? on Microsoft Technet](http://blogs.technet.com/b/heyscriptingguy/archive/2006/10/11/how-can-i-delete-a-record-from-a-disconnected-recordset.aspx) -------------------------------------------------------------------------------- # Display Conduit Source: https://developer.rhino3d.com/en/samples/rhinocommon/display-conduit/ Demonstrates a basic display conduit that draws a custom axis in the Rhino viewport. ```cs partial class Examples { static MyConduit m_conduit; public static Result DisplayConduit(RhinoDoc doc) { // The following code lets you toggle the conduit on and off by repeatedly running the command if (m_conduit != null) { m_conduit.Enabled = false; m_conduit = null; } else { m_conduit = new MyConduit { Enabled = true }; } doc.Views.Redraw(); return Result.Success; } } class MyConduit : Rhino.Display.DisplayConduit { protected override void CalculateBoundingBox(CalculateBoundingBoxEventArgs e) { base.CalculateBoundingBox(e); e.BoundingBox.Union(e.Display.Viewport.ConstructionPlane().Origin); } protected override void PreDrawObjects(DrawEventArgs e) { base.PreDrawObjects(e); var c_plane = e.Display.Viewport.ConstructionPlane(); var x_color = Rhino.ApplicationSettings.AppearanceSettings.GridXAxisLineColor; var y_color = Rhino.ApplicationSettings.AppearanceSettings.GridYAxisLineColor; var z_color = Rhino.ApplicationSettings.AppearanceSettings.GridZAxisLineColor; e.Display.PushDepthWriting(false); e.Display.PushDepthTesting(false); e.Display.DrawPoint(c_plane.Origin, System.Drawing.Color.White); e.Display.DrawArrow(new Line(c_plane.Origin, new Vector3d(c_plane.XAxis) * 10.0), x_color); e.Display.DrawArrow(new Line(c_plane.Origin, new Vector3d(c_plane.YAxis) * 10.0), y_color); e.Display.DrawArrow(new Line(c_plane.Origin, new Vector3d(c_plane.ZAxis) * 10.0), z_color); e.Display.PopDepthWriting(); e.Display.PopDepthTesting(); } } ``` ```python # No Python sample available ``` -------------------------------------------------------------------------------- # Display Conduit Arrowheads Source: https://developer.rhino3d.com/en/samples/rhinocommon/display-conduit-arrowheads/ Demonstrates how to drawing arrowheads in a Display Conduit. ```cs class DrawArrowHeadsConduit : Rhino.Display.DisplayConduit { private readonly Line m_line; private readonly int m_screen_size; private readonly double m_world_size; public DrawArrowHeadsConduit(Line line, int screenSize, double worldSize) { m_line = line; m_screen_size = screenSize; m_world_size = worldSize; } protected override void DrawForeground(Rhino.Display.DrawEventArgs e) { e.Display.DrawArrow(m_line, System.Drawing.Color.Black, m_screen_size, m_world_size); } } partial class Examples { static DrawArrowHeadsConduit m_draw_conduit; public static Result ConduitArrowHeads(RhinoDoc doc) { if (m_draw_conduit != null) { RhinoApp.WriteLine("Turn off existing arrowhead conduit"); m_draw_conduit.Enabled = false; m_draw_conduit = null; } else { // get arrow head size var go = new GetOption(); go.SetCommandPrompt("ArrowHead length in screen size (pixels) or world size (percentage of arrow length)?"); go.AddOption("screen"); go.AddOption("world"); go.Get(); if (go.CommandResult() != Result.Success) return go.CommandResult(); int screen_size = 0; double world_size = 0.0; if (go.Option().EnglishName == "screen") { var gi = new GetInteger(); gi.SetLowerLimit(0, true); gi.SetCommandPrompt("Length of arrow head in pixels"); gi.Get(); if (gi.CommandResult() != Result.Success) return gi.CommandResult(); screen_size = gi.Number(); } else { var gi = new GetInteger(); gi.SetLowerLimit(0, true); gi.SetUpperLimit(100, false); gi.SetCommandPrompt("Length of arrow head in percentage of total arrow length"); gi.Get(); if (gi.CommandResult() != Result.Success) return gi.CommandResult(); world_size = gi.Number() / 100.0; } // get arrow start and end points var gp = new GetPoint(); gp.SetCommandPrompt("Start of line"); gp.Get(); if (gp.CommandResult() != Result.Success) return gp.CommandResult(); var start_point = gp.Point(); gp.SetCommandPrompt("End of line"); gp.SetBasePoint(start_point, false); gp.DrawLineFromPoint(start_point, true); gp.Get(); if (gp.CommandResult() != Result.Success) return gp.CommandResult(); var end_point = gp.Point(); var v = end_point - start_point; if (v.IsTiny(Rhino.RhinoMath.ZeroTolerance)) return Result.Nothing; var line = new Line(start_point, end_point); m_draw_conduit = new DrawArrowHeadsConduit(line, screen_size, world_size); // toggle conduit on/off m_draw_conduit.Enabled = true; RhinoApp.WriteLine("Draw arrowheads conduit enabled."); } doc.Views.Redraw(); return Result.Success; } } ``` ```python import Rhino import System.Drawing import scriptcontext import rhinoscriptsyntax as rs class DrawArrowHeadsConduit(Rhino.Display.DisplayConduit): def __init__(self, line, screenSize, worldSize): self.line = line self.screenSize = screenSize self.worldSize = worldSize def DrawForeground(self, e): e.Display.DrawArrow(self.line, System.Drawing.Color.Black, self.screenSize, self.worldSize) def RunCommand(): # get arrow head size go = Rhino.Input.Custom.GetOption() go.SetCommandPrompt("ArrowHead length in screen size (pixles) or world size (percentage of arrow lenght)?") go.AddOption("screen") go.AddOption("world") go.Get() if (go.CommandResult() != Rhino.Commands.Result.Success): return go.CommandResult() screenSize = 0 worldSize = 0.0 if (go.Option().EnglishName == "screen"): gi = Rhino.Input.Custom.GetInteger() gi.SetLowerLimit(0,True) gi.SetCommandPrompt("Length of arrow head in pixels") gi.Get() if (gi.CommandResult() != Rhino.Commands.Result.Success): return gi.CommandResult() screenSize = gi.Number() else: gi = Rhino.Input.Custom.GetInteger() gi.SetLowerLimit(0, True) gi.SetUpperLimit(100, False) gi.SetCommandPrompt("Lenght of arrow head in percentage of total arrow lenght") gi.Get() if (gi.CommandResult() != Rhino.Commands.Result.Success): return gi.CommandResult() worldSize = gi.Number()/100.0 # get arrow start and end points gp = Rhino.Input.Custom.GetPoint() gp.SetCommandPrompt("Start of line") gp.Get() if (gp.CommandResult() != Rhino.Commands.Result.Success): return gp.CommandResult() ptStart = gp.Point() gp.SetCommandPrompt("End of line") gp.SetBasePoint(ptStart, False) gp.DrawLineFromPoint(ptStart, True) gp.Get() if (gp.CommandResult() != Rhino.Commands.Result.Success): return gp.CommandResult() ptEnd = gp.Point() v = ptEnd - ptStart if (v.IsTiny(Rhino.RhinoMath.ZeroTolerance)): return Rhino.Commands.Result.Nothing line = Rhino.Geometry.Line(ptStart, ptEnd) conduit = DrawArrowHeadsConduit(line, screenSize, worldSize) conduit.Enabled = True scriptcontext.doc.Views.Redraw() rs.GetString("Pausing for user input") conduit.Enabled = False scriptcontext.doc.Views.Redraw() return Rhino.Commands.Result.Success if __name__=="__main__": RunCommand() ``` -------------------------------------------------------------------------------- # Display Order Source: https://developer.rhino3d.com/en/samples/rhinocommon/display-order/ Demonstrates the order of how objects are drawn in the Rhino viewport and how to change it. ```cs partial class Examples { private static List m_line_objects = new List(); public static Result DisplayOrder(RhinoDoc doc) { // make lines thick so draw order can be easily seen var dm = DisplayModeDescription.GetDisplayModes().Single(x => x.EnglishName == "Wireframe"); var original_thikcness = dm.DisplayAttributes.CurveThickness; dm.DisplayAttributes.CurveThickness = 10; DisplayModeDescription.UpdateDisplayMode(dm); AddLine(Point3d.Origin, new Point3d(10,10,0), Color.Red, doc); AddLine(new Point3d(10,0,0), new Point3d(0,10,0), Color.Blue, doc); AddLine(new Point3d(8,0,0), new Point3d(8,10,0), Color.Green, doc); AddLine(new Point3d(0,3,0), new Point3d(10,3,0), Color.Yellow, doc); doc.Views.Redraw(); Pause("draw order: 1st line drawn in front, last line drawn in the back. Any key to continue ..."); //all objects have a DisplayOrder of 0 by default so changing it to 1 moves it to the front. Here we move the 2nd line (blue) to the front m_line_objects[1].Attributes.DisplayOrder = 1; m_line_objects[1].CommitChanges(); doc.Views.Redraw(); Pause("Second (blue) line now in front. Any key to continue ..."); for (int i = 0; i < m_line_objects.Count; i++) { m_line_objects[i].Attributes.DisplayOrder = i; m_line_objects[i].CommitChanges(); } doc.Views.Redraw(); Pause("Reverse order of original lines, i.e., Yellow 1st and Red last. Any key to continue ..."); // restore original line thickness dm.DisplayAttributes.CurveThickness = original_thikcness; DisplayModeDescription.UpdateDisplayMode(dm); doc.Views.Redraw(); return Result.Success; } private static void Pause(string msg) { ObjRef obj_ref; var rc = RhinoGet.GetOneObject(msg, true, ObjectType.AnyObject, out obj_ref); } private static void AddLine(Point3d from, Point3d to, Color color, RhinoDoc doc) { var guid = doc.Objects.AddLine(from, to); var obj = doc.Objects.Find(guid); m_line_objects.Add(obj); obj.Attributes.ObjectColor = color; obj.Attributes.ColorSource = ObjectColorSource.ColorFromObject; obj.CommitChanges(); } } ``` ```python from System.Collections.Generic import * from System.Drawing import * from Rhino import * from Rhino.Commands import * from Rhino.Display import * from Rhino.Geometry import * from Rhino.Input import * from Rhino.DocObjects import * from scriptcontext import doc m_line_objects = [] def RunCommand(): # make lines thick so draw order can be easily seen wfdm = [dm for dm in DisplayModeDescription.GetDisplayModes() if dm.EnglishName == "Wireframe"][0] original_thikcness = wfdm.DisplayAttributes.CurveThickness wfdm.DisplayAttributes.CurveThickness = 10 DisplayModeDescription.UpdateDisplayMode(wfdm) AddLine(Point3d.Origin, Point3d(10,10,0), Color.Red, doc) AddLine(Point3d(10,0,0), Point3d(0,10,0), Color.Blue, doc) AddLine(Point3d(8,0,0), Point3d(8,10,0), Color.Green, doc) AddLine(Point3d(0,3,0), Point3d(10,3,0), Color.Yellow, doc) doc.Views.Redraw() Pause("draw order: 1st line drawn in front, last line drawn in the back. Any key to continue ...") #all objects have a DisplayOrder of 0 by default so changing it to 1 moves it to the front. Here we move the 2nd line (blue) to the front m_line_objects[1].Attributes.DisplayOrder = 1 m_line_objects[1].CommitChanges() doc.Views.Redraw() Pause("Second (blue) line now in front. Any key to continue ...") for i in range(1, m_line_objects.Count): m_line_objects[i].Attributes.DisplayOrder = i m_line_objects[i].CommitChanges() doc.Views.Redraw() Pause("Reverse order of original lines, i.e., Yellow 1st and Red last. Any key to continue ...") # restore original line thickness wfdm.DisplayAttributes.CurveThickness = original_thikcness DisplayModeDescription.UpdateDisplayMode(wfdm) doc.Views.Redraw() return Result.Success def Pause(msg): rc, obj_ref = RhinoGet.GetOneObject(msg, True, ObjectType.AnyObject) def AddLine(from_pt, to_pt, color, doc): guid = doc.Objects.AddLine(from_pt, to_pt) obj = doc.Objects.Find(guid) m_line_objects.Add(obj) obj.Attributes.ObjectColor = color obj.Attributes.ColorSource = ObjectColorSource.ColorFromObject obj.CommitChanges() if __name__ == "__main__": RunCommand() ``` -------------------------------------------------------------------------------- # Display Precision Source: https://developer.rhino3d.com/en/samples/rhinocommon/display-precision/ Demonstrates how to change the display precision in a Rhino model. ```cs partial class Examples { public static Result DisplayPrecision(RhinoDoc doc) { var gi = new GetInteger(); gi.SetCommandPrompt("New display precision"); gi.SetDefaultInteger(doc.ModelDistanceDisplayPrecision); gi.SetLowerLimit(0, false); gi.SetUpperLimit(7, false); gi.Get(); if (gi.CommandResult() != Result.Success) return gi.CommandResult(); var distance_display-precision = gi.Number(); if (distance_display-precision != doc.ModelDistanceDisplayPrecision) doc.ModelDistanceDisplayPrecision = distance_display-precision; return Result.Success; } } ``` ```python from Rhino.Commands import Result from scriptcontext import doc import rhinoscriptsyntax as rs def RunCommand(): distance_display_precision = rs.GetInteger("Display precision", doc.ModelDistanceDisplayPrecision, 0, 7) if distance_display_precision == None: return Result.Nothing if distance_display_precision != doc.ModelDistanceDisplayPrecision: doc.ModelDistanceDisplayPrecision = distance_display_precision return Result.Success if __name__ == "__main__": RunCommand() ``` -------------------------------------------------------------------------------- # Disposing of Variables Source: https://developer.rhino3d.com/en/guides/rhinoscript/disposing-of-variables/ This guide discusses VBScript variables, their scope, and how to clean them up. ## Overview VBScript’s garbage collector runs at the end of every statement and procedure, and does not do a search of all memory. Rather, it keeps track of everything allocated in the statement or procedure; if anything has gone out of scope, it frees it immediately. Global variables, ones declared outside of a procedure, do not go out of scope until VBScript is reset or destroyed. Thus, for memory efficiency, it is best not use global variables. With this said, one can conclude that it is more memory efficient to write scripts that contain a number of small, efficient procedures, so variables that are no longer needed are garbage collected, than it is to write a single, massive procedure. But, sometimes is neither possible nor convenient to write a script in a granular fashion. In such cases, it is possible to manually clean up unused objects and variables along the way to keep your single, massive procedure memory efficient. ## Example The following example demonstrates how to create a single procedure that is capable of cleaning up a number of different variable types. The idea is that when a variable is no longer needed, you can call a single procedure to clean it up, thus making your scripts use less memory. For example, lets say your script used an array variable to store a massive amount of data. When you were finished with the variable, you could dispose of the array, and recover its memory, like this: ```vbnet Call Dispose(arr) ``` This examples cleans up dictionary objects, arrays, and simple variables. But, it could be extended to include other types of objects, such as file streams, database recordsets, or user-defined class objects. ```vbnet ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' Dispose.rvb -- April 2010 ' If this code works, it was written by Dale Fugier. ' If not, I don't know who wrote it. ' Works with Rhino 4.0. Option Explicit ' Disposes of dictionaries, arrays, and variables Sub Dispose(ByRef obj) If IsObject(obj) Then If LCase(TypeName(obj)) = "dictionary" Then obj.RemoveAll ' Remove all key, item pairs End If Set Obj = Nothing ' Disassociate ElseIf IsArray(obj) Then Erase obj ' Clear the array End If obj = Empty ' Uninitialize End Sub ``` ## Related Topics - [VBScript Variables](/guides/rhinoscript/vbscript-variables) -------------------------------------------------------------------------------- # Distance on a Curve from a Point Source: https://developer.rhino3d.com/en/guides/rhinoscript/distance-on-a-curve-from-a-point/ This guide demonstrates how to determine a point on a curve that is a specified distance from another point using RhinoScript. ## Problem How do you create a point that is a given distance from another point, where the distance is to be measured along the curve? ## Solution The problem can be easily solved by using RhinoScript's `CurveArcLengthPoint` function. The `CurveArcLengthPoint` function returns the point on the curve that is a specified arc length from the start of the curve. By first determining the distance of the known point from the start of the curve, you can then determine how far to offset another point. For example: ```vbnet Option Explicit Sub OffsetPointOnCurve ' Select the curve Dim crv : crv = Rhino.GetObject("Select curve", 4) If IsNull(crv) Then Exit Sub ' Select a point on the curve to offset from Dim pt : pt = Rhino.GetPointOnCurve(crv, "Select point on curve") If IsNull(pt) Then Exit Sub ' Specify the offset distance Dim dist : dist = Rhino.GetReal("Distance to offset point") If IsNull(dist) Then Exit Sub ' Get the closest point on the curve from the test point Dim t : t = Rhino.CurveClosestPoint(crv, pt) ' Get the curve's domain Dim dom : dom = Rhino.CurveDomain(crv) ' Get the total length of the curve Dim l : l = Rhino.CurveLength(crv) ' Determine the length from the start of the curve to the test point Dim ls : ls = Rhino.CurveLength(crv,, Array(Dom(0), t)) ' Offset a point in each direction Rhino.AddPoint Rhino.CurveArcLengthPoint(crv, ls + dist, True) Rhino.AddPoint Rhino.CurveArcLengthPoint(crv, l - ls + dist, False) ' Add the test point for reference Rhino.AddPoint pt End Sub ``` -------------------------------------------------------------------------------- # Divide a Curve by Length Source: https://developer.rhino3d.com/en/samples/cpp/divide-curve-by-length/ Demonstrates how to divide a curve object by a specified length. ```cpp CRhinoCommand::result CCommandTest::RunCommand( const CRhinoCommandContext& context ) { CRhinoGetObject go; go.SetCommandPrompt( L"Select curve to divide" ); go.SetGeometryFilter( CRhinoGetObject::curve_object ); go.GetObjects( 1, 1 ); if( go.CommandResult() != CRhinoCommand::success ) return go.CommandResult(); const CRhinoObjRef& objref = go.Object(0); const ON_Curve* crv = objref.Curve(); if( !crv ) return CRhinoCommand::failure; double crv_length = 0.0; crv->GetLength( &crv_length ); if( crv_length < ON_ZERO_TOLERANCE ) return CRhinoCommand::failure; ON_wString s; s.Format( L"Curve length is %g. Segment Length", crv_length ); CRhinoGetNumber gn; gn.SetCommandPrompt( s ); gn.SetLowerLimit( 0.0, TRUE ); gn.SetUpperLimit( crv_length, TRUE ); gn.GetNumber(); if( gn.CommandResult() != CRhinoCommand::success ) return gn.CommandResult(); double seg_length = gn.Number(); int seg_count = (int)floor( crv_length / seg_length ); double fractional_end = (seg_count * seg_length) / crv_length; double t0, t1; crv->GetDomain( &t0, &t1 ); crv->GetNormalizedArcLengthPoint( fractional_end, &t1 ); seg_count++; ON_SimpleArray t( seg_count ); t.SetCount( seg_count ); for( int i = 0; i < seg_count; i++ ) { double param = (double)i / ((double)seg_count-1); t[i] = param; } ON_Interval sub_domain( t0, t1 ); crv->GetNormalizedArcLengthPoints( seg_count, (double*)&t[0], (double*)&t[0], 0.0, 1.0e-8, &sub_domain ); for( int i = 0; i < seg_count; i++ ) { ON_3dPoint pt = crv->PointAt( t[i] ); context.m_doc.AddPointObject( pt ); } context.m_doc.Redraw(); return CRhinoCommand::success; } ``` -------------------------------------------------------------------------------- # Divide a Curve by Segments Source: https://developer.rhino3d.com/en/samples/cpp/divide-curve-by-segments/ Demonstrates how to divide a selected curve object by a specified number of segments. ```cpp CRhinoCommand::result CCommandTest::RunCommand(const CRhinoCommandContext& context) { CRhinoGetObject go; go.SetCommandPrompt( L"Select curve to divide" ); go.SetGeometryFilter( CRhinoGetObject::curve_object ); go.GetObjects( 1, 1 ); if( go.Result() == CRhinoGet::cancel ) return CRhinoCommand::cancel; if( go.Result() != CRhinoGet::object | go.ObjectCount() <= 0 ) return CRhinoCommand::failure; CRhinoObjRef& objref = go.Object(0); const ON_Curve* crv = objref.Curve(); if( !crv ) return CRhinoCommand::failure; CRhinoGetInteger gi; gi.SetCommandPrompt( L"Number of segments" ); gi.SetDefaultInteger( 2 ); gi.SetLowerLimit( 2 ); gi.SetUpperLimit( 100 ); gi.GetInteger(); if( gi.Result() == CRhinoGet::cancel ) return CRhinoCommand::cancel; if( gi.Result() != CRhinoGet::number ) return CRhinoCommand::failure; int count = gi.Number(); count++; ON_SimpleArray t( count ); t.SetCount( count ); int i; for( i = 0; i < count; i++ ) { double param = (double)i / ((double)count-1); t[i] = param; } if( crv->GetNormalizedArcLengthPoints(count, (double*)&t[0], (double*)&t[0]) ) { for( i = 0; i < count; i++ ) { ON_3dPoint pt = crv->PointAt( t[i] ); context.m_doc.AddPointObject( pt ); } context.m_doc.Redraw(); } return CRhinoCommand::success; } ``` -------------------------------------------------------------------------------- # Divide by Length Points Source: https://developer.rhino3d.com/en/samples/rhinocommon/divide-by-length-points/ Demonstrates how to divide a user-selected curve into a set of spaced points along the curve. ```cs partial class Examples { public static Rhino.Commands.Result DivideByLengthPoints(Rhino.RhinoDoc doc) { const ObjectType filter = Rhino.DocObjects.ObjectType.Curve; Rhino.DocObjects.ObjRef objref; Rhino.Commands.Result rc = Rhino.Input.RhinoGet.GetOneObject("Select curve to divide", false, filter, out objref); if (rc != Rhino.Commands.Result.Success || objref == null) return rc; Rhino.Geometry.Curve crv = objref.Curve(); if (crv == null || crv.IsShort(Rhino.RhinoMath.ZeroTolerance)) return Rhino.Commands.Result.Failure; double crv_length = crv.GetLength(); string s = string.Format("Curve length is {0:f3}. Segment length", crv_length); double seg_length = crv_length / 2.0; rc = Rhino.Input.RhinoGet.GetNumber(s, false, ref seg_length, 0, crv_length); if (rc != Rhino.Commands.Result.Success) return rc; Rhino.Geometry.Point3d[] points; crv.DivideByLength(seg_length, true, out points); if (points == null) return Rhino.Commands.Result.Failure; foreach (Rhino.Geometry.Point3d point in points) doc.Objects.AddPoint(point); doc.Views.Redraw(); return Rhino.Commands.Result.Success; } } ``` ```python import Rhino import scriptcontext def DivideByLengthPoints(): filter = Rhino.DocObjects.ObjectType.Curve rc, objref = Rhino.Input.RhinoGet.GetOneObject("Select curve to divide", False, filter) if not objref or rc!=Rhino.Commands.Result.Success: return rc crv = objref.Curve() if not crv or crv.IsShort(Rhino.RhinoMath.ZeroTolerance): return Rhino.Commands.Result.Failure crv_length = crv.GetLength() s = "Curve length is {0:.3f}. Segment length".format(crv_length) seg_length = crv_length / 2.0 rc, length = Rhino.Input.RhinoGet.GetNumber(s, False, seg_length, 0, crv_length) if rc!=Rhino.Commands.Result.Success: return rc t_vals = crv.DivideByLength(length, True) if not t_vals: return Rhino.Commands.Result.Failure [scriptcontext.doc.Objects.AddPoint(crv.PointAt(t)) for t in t_vals] scriptcontext.doc.Views.Redraw() return Rhino.Commands.Result.Success if __name__=="__main__": DivideByLengthPoints() ``` -------------------------------------------------------------------------------- # Divide Curve by Segments Source: https://developer.rhino3d.com/en/samples/rhinocommon/divide-curve-by-segments/ Demonstrates how to divide a user-selected curve into length segments. ```cs partial class Examples { public static Result DivideCurveBySegments(RhinoDoc doc) { const ObjectType filter = ObjectType.Curve; ObjRef objref; var rc = RhinoGet.GetOneObject("Select curve to divide", false, filter, out objref); if (rc != Result.Success || objref == null) return rc; var curve = objref.Curve(); if (curve == null || curve.IsShort(RhinoMath.ZeroTolerance)) return Result.Failure; var segment_count = 2; rc = RhinoGet.GetInteger("Divide curve into how many segments?", false, ref segment_count); if (rc != Result.Success) return rc; Point3d[] points; curve.DivideByCount(segment_count, true, out points); if (points == null) return Result.Failure; foreach (var point in points) doc.Objects.AddPoint(point); doc.Views.Redraw(); return Result.Success; } } ``` ```python from Rhino.DocObjects import * from Rhino.Input import * from Rhino.Commands import * from Rhino.Geometry import * from Rhino import * from scriptcontext import doc def RunCommand(): rc, objref = RhinoGet.GetOneObject("Select curve to divide", False, ObjectType.Curve) if rc != Result.Success or objref == None: return rc curve = objref.Curve() if curve == None or curve.IsShort(RhinoMath.ZeroTolerance): return Result.Failure segment_count = 2 rc, segment_count = RhinoGet.GetInteger("Divide curve into how many segments?", False, segment_count) if rc != Result.Success: return rc curve_params = curve.DivideByCount(segment_count, True) if curve_params == None: return Result.Failure points = [curve.PointAt(t) for t in curve_params] for point in points: doc.Objects.AddPoint(point) doc.Views.Redraw() return Result.Success if __name__ == "__main__": RunCommand() ``` -------------------------------------------------------------------------------- # Divide Curve Straight Source: https://developer.rhino3d.com/en/samples/rhinocommon/divide-curve-straight/ Demonstrates how to divide a curve using equi-distance points. ```cs partial class Examples { private static void NextintersectParamAndPoint(Curve[] overlapCurves, Point3d[] intersectPoints, Curve curve, out double intersectParam, out Point3d intersectPoint) { var intersect_params_and_points = new Dictionary(); foreach (var point in intersectPoints) { double curve_param; curve.ClosestPoint(point, out curve_param); intersect_params_and_points[curve_param] = point; } foreach (var overlap_curve in overlapCurves) { intersect_params_and_points[overlap_curve.Domain.Min] = overlap_curve.PointAt(overlap_curve.Domain.Min); intersect_params_and_points[overlap_curve.Domain.Max] = overlap_curve.PointAt(overlap_curve.Domain.Max); } var min_t = intersect_params_and_points.Keys.Min(); intersectParam = min_t; intersectPoint = intersect_params_and_points[intersectParam]; } public static Result DivideCurveStraight(RhinoDoc doc) { // user input ObjRef[] obj_refs; var rc = RhinoGet.GetMultipleObjects("Select curve to divide", false, ObjectType.Curve | ObjectType.EdgeFilter, out obj_refs); if (rc != Result.Success || obj_refs == null) return rc; double distance_between_divisions = 5; rc = RhinoGet.GetNumber("Distance between divisions", false, ref distance_between_divisions, 1.0, Double.MaxValue); if (rc != Result.Success) return rc; // generate the points var points = new List(); foreach (var obj_ref in obj_refs) { var curve = obj_ref.Curve(); if (curve == null) return Result.Failure; var t0 = curve.Domain.Min; points.Add(curve.PointAt(t0)); var sphere_center = curve.PointAt(t0); var t = t0; var rest_of_curve = curve; while (true) { var sphere = new Sphere(sphere_center, distance_between_divisions); Curve[] overlap_curves; Point3d[] intersect_points; var b = Intersection.CurveBrep(rest_of_curve, sphere.ToBrep(), 0.0, out overlap_curves, out intersect_points); if (!b || (overlap_curves.Length == 0 && intersect_points.Length == 0)) break; double intersect_param; Point3d intersect_point; NextintersectParamAndPoint(overlap_curves, intersect_points, rest_of_curve, out intersect_param, out intersect_point); points.Add(intersect_point); t = intersect_param; sphere_center = intersect_point; rest_of_curve = curve.Split(t)[1]; } } foreach (var point in points) doc.Objects.AddPoint(point); doc.Views.Redraw(); return Result.Success; } } ``` ```python import rhinoscriptsyntax as rs import Rhino from Rhino.Input import * from Rhino.DocObjects import * from Rhino.Commands import * from Rhino.Geometry import * from Rhino.Geometry.Intersect import * def nextIntersectParamAndPoint(overlapCurves, intersectPoints, curve): intersectParamsAndPoints = [(curve.ClosestPoint(point)[1], point) for point in intersectPoints] for overlapCurve in overlapCurves: intersectParamsAndPoints.append((overlapCurve.Domain.Min, overlapCurve.PointAt(overlapCurve.Domain.Min))) intersectParamsandPoints.append((overlapCurve.Domain.Max, overlapCurve.PointAt(overlapCurve.Domain.max))) return min(intersectParamsAndPoints, key = lambda t: t[0]) def divide_curve(): # get user input res, obj_refs = RhinoGet.GetMultipleObjects("Curves to divide", False, ObjectType.EdgeFilter | ObjectType.Curve) if res != Result.Success: return res curves = [obj_ref.Curve() for obj_ref in obj_refs] distance_between_divisions = rs.GetReal( message = "Distance between divisions", number = 5.0, minimum = 1.0) if distance_between_divisions == None: return # generate the points points = [] for curve in curves: t0 = curve.Domain.Min points.append(curve.PointAt(t0)) sphere_center = curve.PointAt(t0) t = t0 rest_of_curve = curve while True: sphere = Sphere(sphere_center, distance_between_divisions) b, overlapCurves, intersectPoints = Intersection.CurveBrep(rest_of_curve, sphere.ToBrep(), 0.0) if b == False or (overlapCurves.Length == 0 and intersectPoints.Length == 0): break t, point = nextIntersectParamAndPoint(overlapCurves, intersectPoints, rest_of_curve) points.append(point) sphere_center = point rest_of_curve = curve.Split(t)[1] rs.AddPoints(points) rs.Redraw() if __name__ == "__main__": divide_curve() ``` -------------------------------------------------------------------------------- # Divide Curve to Dashed Source: https://developer.rhino3d.com/en/samples/rhinoscript/divide-curve-to-dashed/ Demonstrates how to chop up a curve into segments and spaces. ```vbnet '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' DivideCurveDashed.rvb -- October 2010 ' If this code works, it was written by Dale Fugier. ' If not, I don't know who wrote it. ' Works with Rhino 4.0 and 5.0. '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Option Explicit ' Divides a curve into "dashed" segments Sub DivideCurveDashed Dim curve, slength, dlength Dim i, length, pt, t, dom, results curve = Rhino.GetObject("Select curve to divide", 4, True) If IsNull(curve) Then Exit Sub slength = Rhino.GetReal("Segment length", 1.0) If IsNull(slength) Then Exit Sub If (slength <= 0) Then Exit Sub dlength = Rhino.GetReal("Dash length", 1.0) If IsNull(dlength) Then Exit Sub If (dlength <= 0) Then Exit Sub Call Rhino.EnableRedraw(False) i = 0 Do If (i Mod 2 = 0) Then length = slength Else length = dlength End If pt = Rhino.CurveArcLengthPoint(curve, length) If IsNull(pt) Then Exit Do t = Rhino.CurveClosestPoint(curve, pt) If (i Mod 2 = 0) Then results = Rhino.SplitCurve(curve, t, True) curve = results(1) Else dom = Rhino.CurveDomain(curve) curve = Rhino.TrimCurve(curve, Array(t, dom(1)), True) End If i = i + 1 Loop While True Call Rhino.EnableRedraw(True) End Sub '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' Drag & drop and alias creation stuff Rhino.AddStartUpScript Rhino.LastLoadedScriptFile Rhino.AddAlias "DivideCurveDashed", "_-RunScript (DivideCurveDashed)" ``` -------------------------------------------------------------------------------- # Divide Curve With Equidistant Points Source: https://developer.rhino3d.com/en/samples/rhinoscript/divide-curve-with-equidistant-points/ Demonstrates equidistance curve division using RhinoScript. ```vbnet Option Explicit ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' '''''''''' ' Description ' Divides a curve based on the linear distance between points. ' Sub DivideCurveEquiDistant Dim crv crv = Rhino.GetObject("Select curve to divide", 4, True) If IsNull(crv) Then Exit Sub Dim crv_length crv_length = Rhino.CurveLength(crv) Dim length length = Rhino.GetReal("Curve length is " & CStr(crv_length) & ". Division length", 1.0) If Not IsNumeric(length) Then Exit Sub If (crv_length < length) Then Rhino.Print "Specified divison length exceeds curve length." Exit Sub End If Rhino.EnableRedraw False Dim dom, t, pt dom = Rhino.CurveDomain(crv) t = dom(0) pt = Rhino.EvaluateCurve(crv, t) Rhino.AddPoint pt Dim bDone: bDone = False While (bDone = False) t = EquiDistantParameter(crv, t, length) If IsNull(t) Then bDone = True Else pt = Rhino.EvaluateCurve(crv, t) Rhino.AddPoint pt End If Wend Rhino.EnableRedraw True End Sub ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' Description ' Finds the parameter on a curve that is a specified ' linear distance from a known parameter. ' Function EquiDistantParameter(crv, t, length) EquiDistantParameter = Null Dim pt pt = Rhino.EvaluateCurve(crv, t) Dim sphere sphere = Rhino.AddSphere(pt, length) Dim csx csx = Rhino.CurveSurfaceIntersection(crv, sphere) Rhino.DeleteObject sphere If Not IsArray(csx) Then Exit Function Dim i For i = 0 To UBound(csx) If csx(i,0) = 1 Then If (csx(i,5) > t) Then EquiDistantParameter = csx(i,5) Exit Function End If End If Next End Function ``` -------------------------------------------------------------------------------- # Do NOT Test for Equality Source: https://developer.rhino3d.com/en/guides/cpp/do-not-test-for-equality/ This brief guide contains a technical tip for floating point programming. ## A Warning You almost never want to write code like the following: ```cpp double x; double y; ... if (x == y) {...} ``` Most floating point operations involve at least a tiny loss of precision and so even if two numbers are equal for all practical purposes, they may not be exactly equal down to the last bit, and so the equality test is likely to fail. For example, the following code snippet prints `-1.778636e-015`. Although in theory, squaring should undo a square root, the round-trip operation is slightly inaccurate. ```cpp double x = 10; double y = sqrt(x); y *= y; if (x == y) RhinoApp().Print(L"Square root is exact\n"); else RhinoApp().Print(L"%f\n", x-y); ``` In most cases, the equality test above should be written as something like the following: ```cpp double tolerance = ... if (fabs(x - y) < tolerance) {...} ``` Here, `tolerance` is some threshold that defines what is "close enough" for equality. This begs the question of: how close is close enough? This cannot be answered in the abstract; you have to know something about your particular problem to know how close is close enough in your context. -------------------------------------------------------------------------------- # Draft Angle Contouring Source: https://developer.rhino3d.com/en/guides/cpp/draft-angle-contouring/ This guide demonstrates how to create contour curves based on draft angle using C/C++. ## Problem Rhino's draft angle analysis is very useful. However, it would be great it it could create contour curves at specific angles. For example: ![Draft Angle](https://developer.rhino3d.com/images/draft-angle-contouring-01.png) Notice the red curve on the right-hand image above. This is what you would like to automate. Is there an Rhino function that will help do this? ## Solution There is not an function that will help you do this. But, it is possible to write your own tool. Draft angle analysis works by calculating the angles between mesh vertex normals and the unit normal (in most cases this is the world z-axis). It is possible to perform this calculation from a plugin command. From these angles, it is possible to determine whether or not a contour line would pass through a mesh vertex or if it would cross between two mesh vertices. ## Sample The follow sample code demonstrates this process: ```cpp CRhinoCommand::result CCommandTest::RunCommand( const CRhinoCommandContext& context ) { // Pick a mesh object CRhinoGetObject go; go.SetCommandPrompt( L"Select mesh for draft angle contour" ); go.SetGeometryFilter( CRhinoGetObject::mesh_object ); go.GetObjects( 1, 1 ); if( go.CommandResult() != success ) return go.CommandResult(); const ON_Mesh* pMesh = go.Object(0).Mesh(); if( 0 == pMesh ) return failure; // Copy mesh so we can tweak it if necessary ON_Mesh mesh( *pMesh ); // To make our life easy, convert all quads to triangles. mesh.ConvertQuadsToTriangles(); // For draft angle analysis, mesh must have vertex normals. if( !mesh.HasVertexNormals() ) { if( !mesh.ComputeVertexNormals() ) return failure; } // Specify a draft angle CRhinoGetNumber gn; gn.SetCommandPrompt( L"Draft angle" ); gn.SetDefaultNumber( m_angle ); gn.SetLowerLimit( 0.0, TRUE ); gn.SetUpperLimit( 90.0, TRUE ); gn.GetNumber(); if( gn.CommandResult() != success ) return gn.CommandResult(); m_angle = gn.Number(); // degrees ////////////////////////////////////////////////////////////// double A = m_angle * ( ON_PI / 180.0 ); // alpha ON_3dVector D = ON_zaxis; // unit normal int fvcnt = 3; // triangles // Process each mesh face int fi, i, j; for( fi = 0; fi < mesh.m_F.Count(); fi++ ) { const ON_MeshFace& f = mesh.m_F[fi]; // For each face vertex, calculate the draft angle double d[3], a[3]; for( i = fvcnt - 1; i >= 0; i-- ) { ON_3dVector N = mesh.m_N[f.vi[i]]; d[i] = RHINO_CLAMP( N * D, -1.0, 1.0 ); a[i] = acos(d[i]) - A; } // Determine if any of the angles meet our criteria. // If so, calculate a point on an edge at that angle. int P_count = 0; ON_3dPoint P[3]; for( i = 0; i < fvcnt; i++ ) { j = (i + 1) % fvcnt; // If zero, then draft angle point passes through vertex if( a[i] == 0 ) P[P_count++] = mesh.m_V[f.vi[i]]; // See if draft angle point crosses the edge between two vertices else if( a[i] * a[j] < 0.0 ) { double t = a[i] / (a[i] - a[j]); P[P_count++] = ((1 - t) * mesh.m_V[f.vi[i]]) + (t * mesh.m_V[f.vi[j]]); } } // If we have calculated enough points, create some geometry if( P_count == 2 ) context.m_doc.AddCurveObject( ON_Line(P[0], P[1]) ); else if( P_count == 3 ) { context.m_doc.AddCurveObject( ON_Line(P[0], P[1]) ); context.m_doc.AddCurveObject( ON_Line(P[1], P[2]) ); context.m_doc.AddCurveObject( ON_Line(P[2], P[0]) ); } } context.m_doc.Redraw(); return success; } ``` -------------------------------------------------------------------------------- # Draw Mesh Source: https://developer.rhino3d.com/en/samples/rhinocommon/draw-mesh/ Demonstrates how to create a mesh from an existing surface and draw it in a display conduit. ```cs partial class Examples { public static Result DrawMesh(RhinoDoc doc) { var gs = new GetObject(); gs.SetCommandPrompt("select sphere"); gs.GeometryFilter = ObjectType.Surface; gs.DisablePreSelect(); gs.SubObjectSelect = false; gs.Get(); if (gs.CommandResult() != Result.Success) return gs.CommandResult(); Sphere sphere; gs.Object(0).Surface().TryGetSphere(out sphere); if (sphere.IsValid) { var mesh = Mesh.CreateFromSphere(sphere, 10, 10); if (mesh == null) return Result.Failure; var conduit = new DrawBlueMeshConduit(mesh) {Enabled = true}; doc.Views.Redraw(); var in_str = ""; Rhino.Input.RhinoGet.GetString("press to continue", true, ref in_str); conduit.Enabled = false; doc.Views.Redraw(); return Result.Success; } else return Result.Failure; } } class DrawBlueMeshConduit : DisplayConduit { readonly Mesh m_mesh; readonly Color m_color; readonly DisplayMaterial m_material; readonly BoundingBox m_bbox; public DrawBlueMeshConduit(Mesh mesh) { // set up as much data as possible so we do the minimum amount of work possible inside // the actual display code m_mesh = mesh; m_color = System.Drawing.Color.Blue; m_material = new DisplayMaterial(); m_material.Diffuse = m_color; if (m_mesh != null && m_mesh.IsValid) m_bbox = m_mesh.GetBoundingBox(true); } // this is called every frame inside the drawing code so try to do as little as possible // in order to not degrade display speed. Don't create new objects if you don't have to as this // will incur an overhead on the heap and garbage collection. protected override void CalculateBoundingBox(CalculateBoundingBoxEventArgs e) { base.CalculateBoundingBox(e); // Since we are dynamically drawing geometry, we needed to override // CalculateBoundingBox. Otherwise, there is a good chance that our // dynamically drawing geometry would get clipped. // Union the mesh's bbox with the scene's bounding box e.BoundingBox.Union(m_bbox); } protected override void PreDrawObjects(DrawEventArgs e) { base.PreDrawObjects(e); var vp = e.Display.Viewport; if (vp.DisplayMode.EnglishName.ToLower() == "wireframe") e.Display.DrawMeshWires(m_mesh, m_color); else e.Display.DrawMeshShaded(m_mesh, m_material); } } ``` ```python #! python 3 import Rhino import System import scriptcontext as sc def RunCommand(): gs = Rhino.Input.Custom.GetObject() gs.SetCommandPrompt("Select sphere") gs.GeometryFilter = Rhino.DocObjects.ObjectType.Surface gs.SubObjectSelect = False gs.Get() if gs.CommandResult() != Rhino.Commands.Result.Success: return gs.CommandResult() rc, sphere = gs.Object(0).Surface().TryGetSphere() if not rc or not sphere.IsValid: return Rhino.Commands.Result.Cancel mesh = Rhino.Geometry.Mesh.CreateFromSphere(sphere, 10, 10) if not mesh: return Rhino.Commands.Result.Failure conduit = DrawBlueMeshConduit(mesh) conduit.Enabled = True sc.doc.Views.Redraw() out_str = "" rc = Rhino.Input.RhinoGet.GetString("Press to continue", True, out_str); conduit.Enabled = False sc.doc.Views.Redraw() return Rhino.Commands.Result.Success class DrawBlueMeshConduit(Rhino.Display.DisplayConduit): def __init__(self, mesh): super().__init__() self.mesh = mesh self.color = System.Drawing.Color.Blue self.material = Rhino.Display.DisplayMaterial() self.material.Diffuse = self.color if mesh != None and mesh.IsValid: self.bbox = mesh.GetBoundingBox(True) def CalculateBoundingBox(self, args): args.BoundingBox.Union(self.bbox) def PreDrawObjects(self, drawEventArgs): gvp = drawEventArgs.Display.Viewport if gvp.DisplayMode.Id != Rhino.Display.DisplayModeDescription.WireframeId: drawEventArgs.Display.DrawMeshShaded(self.mesh, self.material) drawEventArgs.Display.DrawMeshWires(self.mesh, self.color) if __name__ == "__main__": RunCommand() ``` -------------------------------------------------------------------------------- # Drawing Arrowheads in Display Conduits Source: https://developer.rhino3d.com/en/guides/cpp/drawing-arrowheads-in-display-conduits/ This guide discusses and demonstrates how to draw arrowheads in Rhino Display Conduit. ## Problem You are trying to get your head around drawing arrowheads through conduits in the Rhino C/C++ SDK. You create an instance of the `CRhinoArrowhead` class and set 2D information (point and direction) and then need to set a parent (any type of `CRhinoAnnotationObject`). As `CRhinoAnnotationObject` is a virtual class, for your purposes the best version seems to be the leader class. But (not surprisingly) the arrowhead behaves like a leader, sitting in world space, and is not aligned or scaled to the viewport's construction as desired. Is there any other way to draw an arrowhead that does not involve using the `CRhinoArrowhead` class? ## Solution Drawing arrowheads in a conduit by using an `CRhinoArrowhead` object might appear to be the correct approach, but this object is fairly specialized and designed to be used in conjunction with other annotation objects. So in this case, this might not work well. If you add your lines to the document, you can set the objects' decorations. Then Rhino would draw the arrowheads for you. But adding temporary geometry to the document just for display purposes is awkward and frowned upon. You might be better off just drawing your own arrowheads. It's really not that hard. Draw arrowheads by calling the display pipeline's `DrawPolygon()` member with the fill parameter set to true. The trick is defining the polygon you want to draw. Below is some sample code that draws a 2D arrowhead at the end of a line similar to a leader object. Perhaps this will give you an idea how to implement arrowhead drawing in your project. ```cpp #pragma region SampleDrawArrowheadConduit conduit class CSampleDrawArrowheadConduit : public CRhinoDisplayConduit { public: CSampleDrawArrowheadConduit(ON_Plane plane, ON_Line line, double scale); bool ExecConduit(CRhinoDisplayPipeline& dp, UINT channel, bool& terminate); protected: bool GetArrowHead(ON_2dVector dir, ON_2dPoint tip, double scale, ON_3dPointArray& triangle); protected: static double m_default_arrow_size; bool m_bDraw; ON_Line m_line; ON_Plane m_plane; ON_3dPointArray m_arrowhead; }; double CSampleDrawArrowheadConduit::m_default_arrow_size = 1.0; CSampleDrawArrowheadConduit::CSampleDrawArrowheadConduit(ON_Plane plane, ON_Line line, double scale) : CRhinoDisplayConduit(CSupportChannels::SC_CALCBOUNDINGBOX | CSupportChannels::SC_DRAWOVERLAY) { m_bDraw = false; m_plane = plane; m_line = line; double x = 0.0, y = 0.0; m_plane.ClosestPointTo(line.from, &x, &y); ON_2dPoint from(x, y); m_plane.ClosestPointTo(line.to, &x, &y); ON_2dPoint to(x, y); ON_2dVector dir(from - to); dir.Unitize(); m_bDraw = GetArrowHead(dir, from, scale, m_arrowhead); } bool CSampleDrawArrowheadConduit::ExecConduit(CRhinoDisplayPipeline& dp, UINT channel, bool& terminate) { if (channel == CSupportChannels::SC_CALCBOUNDINGBOX) { m_pChannelAttrs->m_BoundingBox.Union(m_line.BoundingBox()); } else if (channel ==CSupportChannels::SC_DRAWOVERLAY) { dp.DrawLine(m_line.from, m_line.to, m_pDisplayAttrs->m_ObjectColor | 0xFF000000, m_pDisplayAttrs->m_nLineThickness); if (m_bDraw) dp.DrawPolygon(m_arrowhead.Array(), m_arrowhead.Count(), m_pDisplayAttrs->m_ObjectColor | 0xFF000000, true); } return true; } bool CSampleDrawArrowheadConduit::GetArrowHead(ON_2dVector dir, ON_2dPoint tip, double scale, ON_3dPointArray& triangle) { double arrow_size = CSampleDrawArrowheadConduit::m_default_arrow_size * scale; ON_2dPointArray corners(3); corners.SetCount(3); ON_2dVector up(-dir.y, dir.x); corners[0].Set(tip.x, tip.y); corners[1].x = tip.x + arrow_size * (0.25 * up.x - dir.x); corners[1].y = tip.y + arrow_size * (0.25 * up.y - dir.y); corners[2].x = corners[1].x - 0.5 * arrow_size * up.x; corners[2].y = corners[1].y - 0.5 * arrow_size * up.y; triangle.Reserve(corners.Count()); triangle.SetCount(corners.Count()); for (int i = 0; i < corners.Count(); i++) triangle[i] = m_plane.PointAt(corners[i].x, corners[i].y); return true; } #pragma endregion ``` Here is the above sample conduit used in a test command... ```cpp CRhinoCommand::result CCommandSampleDrawArrowhead::RunCommand( const CRhinoCommandContext& context ) { ON_Line line; ON_Plane plane; CRhinoGetPoint gp; gp.SetCommandPrompt(L"Start of line"); gp.GetPoint(); if (gp.CommandResult() != CRhinoCommand::success) return gp.CommandResult(); line.from = gp.Point(); plane = RhinoActiveCPlane(); plane.SetOrigin(line.from); gp.SetCommandPrompt(L"End of line"); gp.Constrain(plane); gp.SetBasePoint(line.from); gp.DrawLineFromPoint(line.from, TRUE); gp.GetPoint(); if (gp.CommandResult() != CRhinoCommand::success) return gp.CommandResult(); line.to = plane.ClosestPointTo(gp.Point()); if (!line.IsValid()) return CRhinoCommand::nothing; CSampleDrawArrowheadConduit conduit(plane, line, 1.0); conduit.Enable(); context.m_doc.Redraw(); CRhinoGetString gs; gs.SetCommandPrompt(L"Press to continue"); gs.AcceptNothing(); gs.GetString(); conduit.Disable(); context.m_doc.Redraw(); return CRhinoCommand::success; } ``` -------------------------------------------------------------------------------- # Drawing Steel Shapes Source: https://developer.rhino3d.com/en/samples/rhinoscript/drawing-steel-shapes/ Demonstrates how to draw steel shapes using RhinoScript. ```vbnet ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' Steel.rvb -- September 2008 ' If this code works, it was written by Dale Fugier. ' If not, I don't know who wrote it. ' Works with Rhino 4.0. Option Explicit ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' Draws Steel Wide Flanges ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Sub SteelWideFlange Dim cmdname, configfile, section Dim filename, entries, entry, value, data, bname Dim ip, d, b, tf, tw, c Dim p0, p1, p2, p3 Dim e0, e1, e2, e3, a0 cmdname = "Steel Wide Flange" configfile = "STEEL.INI" section = "STEEL_WIDE_FLANGE" filename = Rhino.FindFile(configfile) If IsNull(filename) Then Call MsgBox("Unable to locate " & configfile & ".", 17, cmdname) Exit Sub End If entries = Rhino.GetSettings(filename, section) If IsNull(entries) Then Exit Sub entry = Rhino.ListBox(entries, "Select a wide flange", cmdname) If IsNull(entry) Then Exit Sub bname = Replace(entry, ".", "_") If Rhino.IsBlock(bname) = False Then value = Rhino.GetSettings(filename, section, entry) If IsNull(value) Then Exit Sub data = Rhino.Strtok(value, ", ;") If IsNull(data) Or (UBound(data) <> 3) Then Exit Sub ' Hide the dirty work Call Rhino.EnableRedraw(False) ip = Array(0,0,0) d = CDbl(data(0)) b = CDbl(data(1)) tf = CDbl(data(2)) tw = CDbl(data(3)) ' Top p0 = Rhino.Polar(Rhino.Polar(ip, 0, b/2), 270, tf) p1 = Rhino.Polar(ip, 0, b/2) p2 = Rhino.Polar(ip, 180, b/2) p3 = Rhino.Polar(p0, 180, b) e0 = Rhino.AddPolyline(Array(p0, p1, p2, p3)) ' Bottom c = Rhino.Polar(ip, 270, d/2) e1 = Rhino.MirrorObject(e0, c, Rhino.Polar(c, 0, 1), True) ' Right side p1 = Rhino.Polar(p0, 180, (b-tw)/2) p2 = Rhino.Polar(p1, 270, (d-(tf*2))) p3 = Rhino.Polar(p2, 0, (b-tw)/2) e2 = Rhino.AddPolyline(Array(p0, p1, p2, p3)) Call Rhino.Command("_-FilletCorners _SelID " & e2 & " _Enter " & CStr(0.9*tf), 0) ' Left side e3 = Rhino.MirrorObject(e2, c, ip, True) ' Join a0 = Rhino.JoinCurves(Array(e0, e1, e2, e3), True) ' Create the block Call Rhino.SelectObjects(a0) Call Rhino.Command("_-Block 0 " & Chr(34) & bname & Chr(34) & " _Enter _Enter _Enter", 0) ' Delete the block inserted by the Block command Call Rhino.DeleteObjects(Rhino.LastCreatedObjects) ' Enable redrawing Call Rhino.EnableRedraw(True) End If ' Insert the block Call Rhino.Command("_-Insert " & Chr(34) & bname & Chr(34) & " _Block", 0) End Sub ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' Draws Steel Channels ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Sub SteelChannel Dim cmdname, configfile, section Dim filename, entries, entry, value, data, bname Dim ip, d, b, tf, tw, tip Dim p0, p1, p2, p3, p4, p5 Dim e0, e1, a0 cmdname = "Steel Channel" configfile = "STEEL.INI" section = "STEEL_CHANNEL" filename = Rhino.FindFile(configfile) If IsNull(filename) Then Call MsgBox("Unable to locate " & configfile & ".", 17, cmdname) Exit Sub End If entries = Rhino.GetSettings(filename, section) If IsNull(entries) Then Exit Sub entry = Rhino.ListBox(entries, "Select a channel", cmdname) If IsNull(entry) Then Exit Sub bname = Replace(entry, ".", "_") If Rhino.IsBlock(bname) = False Then value = Rhino.GetSettings(filename, section, entry) If IsNull(value) Then Exit Sub data = Rhino.Strtok(value, ", ;") If IsNull(data) Or (UBound(data) <> 3) Then Exit Sub ' Hide the dirty work Call Rhino.EnableRedraw(False) ip = Array(0,0,0) d = CDbl(data(0)) b = CDbl(data(1)) tf = CDbl(data(2)) tw = CDbl(data(3)) tip = tf * 0.7 ' Top p0 = Rhino.Polar(Rhino.Polar(ip, 90, d/2), 0, b) p1 = Rhino.Polar(ip, 90, d/2) p2 = Rhino.Polar(ip, 270, d/2) p3 = Rhino.Polar(p0, 270, d) e0 = Rhino.AddPolyline(Array(p0, p1, p2, p3)) ' Right p1 = Rhino.Polar(p0, 270, tip) p2 = Rhino.Polar(Rhino.Polar(p0, 180, b-tw), 270, tf) p3 = Rhino.Polar(p2, 270, d-(tf*2)) p4 = Rhino.Polar(Rhino.Polar(p0, 270, d), 90, tip) p5 = Rhino.Polar(p0, 270, d) e1 = Rhino.AddPolyline(Array(p0, p1, p2, p3, p4, p5)) Call Rhino.Command("_-FilletCorners _SelID " & e1 & " _Enter " & CStr(0.9*tip), 0) ' Join a0 = Rhino.JoinCurves(Array(e0, e1), True) ' Create the block Call Rhino.SelectObjects(a0) Call Rhino.Command("_-Block 0 " & Chr(34) & bname & Chr(34) & " _Enter _Enter _Enter", 0) ' Delete the block inserted by the Block command Call Rhino.DeleteObjects(Rhino.LastCreatedObjects) ' Enable redrawing Call Rhino.EnableRedraw(True) End If ' Insert the block Call Rhino.Command("_-Insert " & Chr(34) & bname & Chr(34) & " _Block", 0 ) End Sub ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' Draws Steel Angles ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Sub SteelAngle Dim cmdname, configfile, section, tsection Dim filename, entries, entry, value, data, bname Dim tentries, tentry, tvalue Dim ip, xl, yl, thk Dim p0, p1, p2, p3, p4 Dim e0, e1, a0 cmdname = "Steel Angle" configfile = "STEEL.INI" section = "STEEL_ANGLE" tsection = "STEEL_ANGLE_THICKNESS" filename = Rhino.FindFile(configfile) If IsNull(filename) Then Call MsgBox("Unable to locate " & configfile & ".", 17, cmdname) Exit Sub End If entries = Rhino.GetSettings(filename, section) If IsNull(entries) Then Exit Sub entry = Rhino.ListBox(entries, "Select an angle", cmdname) If IsNull(entry) Then Exit Sub tentries = Rhino.GetSettings(filename, tsection) If IsNull(tentries) Then Exit Sub tentry = Rhino.ListBox(tentries, "Select an angle thickness", cmdname) If IsNull(tentry) Then Exit Sub tvalue = Rhino.GetSettings(filename, tsection, tentry) If IsNull(value) Then Exit Sub bname = Replace(entry, ".", "_") & Replace(tvalue, ".", "_") If Rhino.IsBlock(bname) = False Then value = Rhino.GetSettings(filename, section, entry) If IsNull(value) Then Exit Sub data = Rhino.Strtok(value, ", ;") If IsNull(data) Or (UBound(data) <> 1) Then Exit Sub ' Hide the dirty work Call Rhino.EnableRedraw(False) ip = Array(0,0,0) xl = CDbl(data(0)) yl = CDbl(data(1)) thk = CDbl(tvalue) ' Top p0 = Rhino.Polar(ip, 90, yl) p1 = Rhino.Polar(ip, 0, xl) e0 = Rhino.AddPolyline(Array(ip, p0, ip, p1)) ' Right p1 = Rhino.Polar(p0, 0, thk) p2 = Rhino.Polar(p1, 270, yl - thk) p3 = Rhino.Polar(p2, 0, xl - thk) p4 = Rhino.Polar(p3, 270, thk) e1 = Rhino.AddPolyline(Array(p0, p1, p2, p3, p4)) Call Rhino.Command("_-FilletCorners _SelID " & e1 & " _Enter " & CStr(0.9*thk), 0 ) ' Join a0 = Rhino.JoinCurves(Array(e0, e1), True) ' Create the block Call Rhino.SelectObjects(a0) Call Rhino.Command("_-Block 0 " & Chr(34) & bname & Chr(34) & " _Enter _Enter _Enter", 0) ' Delete the block inserted by the Block command Call Rhino.DeleteObjects(Rhino.LastCreatedObjects) ' Enable redrawing Call Rhino.EnableRedraw(True) End If ' Insert the block Call Rhino.Command("_-Insert " & Chr(34) & bname & Chr(34) & " _Block", 0 ) End Sub ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' Draws Steel T-Sections ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Sub SteelTSection Dim cmdname, configfile, section Dim filename, entries, entry, value, data, bname Dim ip, d, b, tf, tw, tip Dim p0, p1, p2, p3, p4, p5 Dim e0, e1, e2, e3, a0 cmdname = "Steel T-Sections" configfile = "STEEL.INI" section = "STEEL_TSECTION" filename = Rhino.FindFile(configfile) If IsNull(filename) Then Call MsgBox("Unable to locate " & configfile & ".", 17, cmdname) Exit Sub End If entries = Rhino.GetSettings(filename, section) If IsNull(entries) Then Exit Sub entry = Rhino.ListBox(entries, "Select a channel", cmdname) If IsNull(entry) Then Exit Sub bname = Replace(entry, ".", "_") If Rhino.IsBlock(bname) = False Then value = Rhino.GetSettings(filename, section, entry) If IsNull(value) Then Exit Sub data = Rhino.Strtok(value, ", ;") If IsNull(data) Or (UBound(data) <> 3) Then Exit Sub ' Hide the dirty work Call Rhino.EnableRedraw(False) ip = Array(0,0,0) d = CDbl(data(0)) b = CDbl(data(1)) tf = CDbl(data(2)) tw = CDbl(data(3)) ' Top p0 = Rhino.Polar(Rhino.Polar(ip, 0, b/2), 270, tf) p1 = Rhino.Polar(ip, 0, b/2) p2 = Rhino.Polar(ip, 180, b/2) p3 = Rhino.Polar(p0, 180, b) e0 = Rhino.AddPolyline(Array(p0, p1, p2, p3)) ' Right p1 = Rhino.Polar(p0, 180, (b-tw)/2) p2 = Rhino.Polar(p1, 270, d-tf) p3 = Rhino.Polar(p2, 270, d-(tf*2)) e1 = Rhino.AddPolyline(Array(p0, p1, p2, p3)) Call Rhino.Command("_-FilletCorners _SelID " & e1 & " _Enter " & CStr(0.9*tf), 0) ' Mirror left e2 = Rhino.MirrorObject(e1, ip, Rhino.Polar(ip, 270, 1), True) ' Bottom e3 = Rhino.AddPolyline(Array(p3, Rhino.Polar(p3, 180, tw))) ' Join a0 = Rhino.JoinCurves(Array(e0, e1, e2, e3), True) ' Create the block Call Rhino.SelectObjects(a0) Call Rhino.Command("_-Block 0 " & Chr(34) & bname & Chr(34) & " _Enter _Enter _Enter", 0) ' Delete the block inserted by the Block command Call Rhino.DeleteObjects(Rhino.LastCreatedObjects) ' Enable redrawing Call Rhino.EnableRedraw(True) End If ' Insert the block Call Rhino.Command("_-Insert " & Chr(34) & bname & Chr(34) & " _Block", 0 ) End Sub ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' Startup and Aliases ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Rhino.AddStartupScript Rhino.LastLoadedScriptFile Rhino.AddAlias "SteelWideFlange", "_NoEcho _-RunScript (SteelWideFlange)" Rhino.AddAlias "SteelChannel", "_NoEcho _-RunScript (SteelChannel)" Rhino.AddAlias "SteelAngle", "_NoEcho _-RunScript (SteelAngle)" Rhino.AddAlias "SteelTSection", "_NoEcho _-RunScript (SteelTSection)" ``` -------------------------------------------------------------------------------- # Duplicate Mesh Boundary Source: https://developer.rhino3d.com/en/samples/rhinocommon/duplicated-mesh-boundary/ Demonstrates how to create a bounding polyline from naked edges of an open mesh. ```cs partial class Examples { public static Result DupMeshBoundary(RhinoDoc doc) { var gm = new GetObject(); gm.SetCommandPrompt("Select open mesh"); gm.GeometryFilter = ObjectType.Mesh; gm.GeometryAttributeFilter = GeometryAttributeFilter.OpenMesh; gm.Get(); if (gm.CommandResult() != Result.Success) return gm.CommandResult(); var mesh = gm.Object(0).Mesh(); if (mesh == null) return Result.Failure; var polylines = mesh.GetNakedEdges(); foreach (var polyline in polylines) { doc.Objects.AddPolyline(polyline); } return Result.Success; } } ``` ```python from Rhino.Commands import * from Rhino.Input.Custom import * from Rhino.DocObjects import * from scriptcontext import doc def RunCommand(): gm = GetObject() gm.SetCommandPrompt("Select open mesh") gm.GeometryFilter = ObjectType.Mesh gm.GeometryAttributeFilter = GeometryAttributeFilter.OpenMesh gm.Get() if gm.CommandResult() != Result.Success: return gm.CommandResult() mesh = gm.Object(0).Mesh() if mesh == None: return Result.Failure polylines = mesh.GetNakedEdges() for polyline in polylines: doc.Objects.AddPolyline(polyline) doc.Views.Redraw() return Result.Success if __name__ == "__main__": RunCommand() ``` -------------------------------------------------------------------------------- # Duplicate Object Source: https://developer.rhino3d.com/en/samples/cpp/duplicate-object/ Demonstrates how to make a copy of a CRhinoObject-derived object. ```cpp const CRhinoObject* object = ..... // some object CRhinoObject* duplicate = object->Duplicate(); if( duplicate ) { if( context.m_doc.AddObject(duplicate) ) context.m_doc.Redraw; else delete duplicate; } ``` -------------------------------------------------------------------------------- # Duplicate Object Source: https://developer.rhino3d.com/en/samples/rhinocommon/duplicate-object/ Demonstrates how to clone (or copy, or duplicate) a Rhino object. ```cs partial class Examples { public static Result DuplicateObject(RhinoDoc doc) { ObjRef obj_ref; var rc = RhinoGet.GetOneObject("Select object to duplicate", false, ObjectType.AnyObject, out obj_ref); if (rc != Result.Success) return rc; var rhino_object = obj_ref.Object(); var geometry_base = rhino_object.DuplicateGeometry(); if (geometry_base != null) if (doc.Objects.Add(geometry_base) != Guid.Empty) doc.Views.Redraw(); return Result.Success; } } ``` ```python from System import * from Rhino import * from Rhino.Commands import * from Rhino.DocObjects import * from Rhino.Input import * from scriptcontext import doc def RunCommand(): rc, obj_ref = RhinoGet.GetOneObject("Select object to duplicate", False, ObjectType.AnyObject) if rc != Result.Success: return rc rhino_object = obj_ref.Object() geometry_base = rhino_object.DuplicateGeometry() if geometry_base != None: if doc.Objects.Add(geometry_base) != Guid.Empty: doc.Views.Redraw() return Result.Success if __name__ == "__main__": RunCommand() ``` -------------------------------------------------------------------------------- # Duplicate Surface Border Source: https://developer.rhino3d.com/en/samples/rhinocommon/duplicate-surface-border/ Demonstrates how to duplicate the borders of a user-specified surface or polysurface. ```cs partial class Examples { public static Rhino.Commands.Result DupBorder(Rhino.RhinoDoc doc) { const ObjectType filter = Rhino.DocObjects.ObjectType.Surface | Rhino.DocObjects.ObjectType.PolysrfFilter; Rhino.DocObjects.ObjRef objref; Rhino.Commands.Result rc = Rhino.Input.RhinoGet.GetOneObject("Select surface or polysurface", false, filter, out objref); if (rc != Rhino.Commands.Result.Success || objref == null) return rc; Rhino.DocObjects.RhinoObject rhobj = objref.Object(); Rhino.Geometry.Brep brep = objref.Brep(); if (rhobj == null || brep == null) return Rhino.Commands.Result.Failure; rhobj.Select(false); Rhino.Geometry.Curve[] curves = brep.DuplicateEdgeCurves(true); double tol = doc.ModelAbsoluteTolerance * 2.1; curves = Rhino.Geometry.Curve.JoinCurves(curves, tol); for (int i = 0; i < curves.Length; i++) { Guid id = doc.Objects.AddCurve(curves[i]); doc.Objects.Select(id); } doc.Views.Redraw(); return Rhino.Commands.Result.Success; } } ``` ```python import Rhino import scriptcontext def DupBorder(): filter = Rhino.DocObjects.ObjectType.Surface | Rhino.DocObjects.ObjectType.PolysrfFilter rc, objref = Rhino.Input.RhinoGet.GetOneObject("Select surface or polysurface", False, filter) if rc != Rhino.Commands.Result.Success: return rc rhobj = objref.Object() brep = objref.Brep() if not rhobj or not brep: return Rhino.Commands.Result.Failure rhobj.Select(False) curves = brep.DuplicateEdgeCurves(True) tol = scriptcontext.doc.ModelAbsoluteTolerance * 2.1 curves = Rhino.Geometry.Curve.JoinCurves(curves, tol) for curve in curves: id = scriptcontext.doc.Objects.AddCurve(curve) scriptcontext.doc.Objects.Select(id) scriptcontext.doc.Views.Redraw() return Rhino.Commands.Result.Success if __name__=="__main__": DupBorder() ``` -------------------------------------------------------------------------------- # Duplicate the Borders of Surfaces Source: https://developer.rhino3d.com/en/samples/cpp/duplicate-borders-of-surfaces/ Demonstrates how to duplicate the borders of surfaces and polysurfaces. ```cpp CRhinoCommand::result CCommandTest::RunCommand( const CRhinoCommandContext& context ) { CRhinoGetObject go; go.SetCommandPrompt( L"Select surface or polysurface" ); go.SetGeometryFilter( CRhinoGetObject::surface_object | CRhinoGetObject::polysrf_object ); go.AcceptNothing(); go.GetObjects( 1, 1 ); if( go.CommandResult() != success ) return go.CommandResult(); const CRhinoObjRef& object_ref = go.Object(0); const CRhinoObject* object = object_ref.Object(); const ON_Brep* brep = object_ref.Brep(); if( !object | !brep ) return failure; object->Select( false ); ON_SimpleArray curve_array( brep->m_E.Count() ); for( int i = 0; i < brep->m_E.Count(); i++ ) { const ON_BrepEdge& edge = brep->m_E[i]; // Find only the naked edges if( edge.m_ti.Count() == 1 && edge.m_c3i >= 0 ) { ON_Curve* curve = edge.DuplicateCurve(); // Make the curve direction go in the natural // boundary loop direction so that the curve // directions come out consistantly if( brep->m_T[edge.m_ti[0]].m_bRev3d ) curve->Reverse(); if( brep->m_T[edge.m_ti[0]].Face()->m_bRev) curve->Reverse(); curve_array.Append( curve ); } } double tol = 2.1 * RhinoApp().ActiveDoc()->AbsoluteTolerance(); ON_SimpleArray output_array; // Join the curves if( RhinoMergeCurves(curve_array, output_array, tol) ) { for( int i = 0; i < output_array.Count(); i++ ) { CRhinoCurveObject* curve_object = new CRhinoCurveObject; curve_object->SetCurve( output_array[i]); if( context.m_doc.AddObject(curve_object) ) curve_object->Select(); else delete curve_object; } } // Don't leak memory for( int i = 0; i < curve_array.Count(); i++ ) delete curve_array[i]; context.m_doc.Redraw(); return success; } ``` -------------------------------------------------------------------------------- # Duplicating Objects with Group Source: https://developer.rhino3d.com/en/guides/cpp/duplicating-objects-with-group/ This guide demonstrates how to duplicate objects that are members of one or more object groups using C/C++. ## Problem When you duplicate a Rhino object which happens to be a member of a group, the duplicate object is (also) a member of that same group. Is there a quick way to duplicate a Rhino object and have the duplicated object be a member of a new group? ## Solution You can use the `RhinoUpdateObjectGroups` function. See *rhinoSdkGrips.h* for details. Here is a sample of its usage: ```cpp CRhinoCommand::result CCommandTest::RunCommand( const CRhinoCommandContext& context ) { CRhinoGetObject go; go.SetCommandPrompt( L"Select objects to copy in-place" ); go.EnableGroupSelect( TRUE ); go.EnableSubObjectSelect( FALSE ); go.GetObjects( 1, 0 ); if( go.CommandResult() != CRhinoCommand::success ) return go.CommandResult(); ON_Xform xform; xform.Identity(); ON_2dexMap group_map; for( int i = 0; i < go.ObjectCount(); i++ ) { const CRhinoObject* object = go.Object(i).Object(); if( object ) { CRhinoObject* duplicate = context.m_doc.TransformObject( object, xform, true, false, true ); if( duplicate ) RhinoUpdateObjectGroups( duplicate, group_map ); } } context.m_doc.Redraw(); return CRhinoCommand::success; } ``` -------------------------------------------------------------------------------- # Dynamically Draw Geometry when Picking Points Source: https://developer.rhino3d.com/en/samples/rhinocommon/dynamically-draw-geometry-when-picking-points/ Demonstrates how to dynamically draw geometry during point picking. ```cs partial class Examples { public static Result GetPointDynamicDraw(RhinoDoc doc) { var gp = new GetPoint(); gp.SetCommandPrompt("Center point"); gp.Get(); if (gp.CommandResult() != Result.Success) return gp.CommandResult(); var center_point = gp.Point(); if (center_point == Point3d.Unset) return Result.Failure; var gcp = new GetCircleRadiusPoint(center_point); gcp.SetCommandPrompt("Radius"); gcp.ConstrainToConstructionPlane(false); gcp.SetBasePoint(center_point, true); gcp.DrawLineFromPoint(center_point, true); gcp.Get(); if (gcp.CommandResult() != Result.Success) return gcp.CommandResult(); var radius = center_point.DistanceTo(gcp.Point()); var cplane = doc.Views.ActiveView.ActiveViewport.ConstructionPlane(); doc.Objects.AddCircle(new Circle(cplane, center_point, radius)); doc.Views.Redraw(); return Result.Success; } } public class GetCircleRadiusPoint : GetPoint { private Point3d m_center_point; public GetCircleRadiusPoint(Point3d centerPoint) { m_center_point = centerPoint; } protected override void OnDynamicDraw(GetPointDrawEventArgs e) { base.OnDynamicDraw(e); var cplane = e.RhinoDoc.Views.ActiveView.ActiveViewport.ConstructionPlane(); var radius = m_center_point.DistanceTo(e.CurrentPoint); var circle = new Circle(cplane, m_center_point, radius); e.Display.DrawCircle(circle, System.Drawing.Color.Black); } } ``` ```python from Rhino import * from Rhino.Geometry import * from Rhino.Commands import * from Rhino.Input.Custom import * from scriptcontext import doc from System.Drawing import * def RunCommand(): gp = GetPoint() gp.SetCommandPrompt("Center point") gp.Get() if gp.CommandResult() != Result.Success: return gp.CommandResult() center_point = gp.Point() if center_point == Point3d.Unset: return Result.Failure gcp = GetCircleRadiusPoint(center_point) gcp.SetCommandPrompt("Radius") gcp.ConstrainToConstructionPlane(False) gcp.SetBasePoint(center_point, True) gcp.DrawLineFromPoint(center_point, True) gcp.Get() if gcp.CommandResult() != Result.Success: return gcp.CommandResult() radius = center_point.DistanceTo(gcp.Point()) cplane = doc.Views.ActiveView.ActiveViewport.ConstructionPlane() doc.Objects.AddCircle(Circle(cplane, center_point, radius)) doc.Views.Redraw() return Result.Success class GetCircleRadiusPoint (GetPoint): def __init__(self, centerPoint): self.m_center_point = centerPoint def OnDynamicDraw(self, e): cplane = e.RhinoDoc.Views.ActiveView.ActiveViewport.ConstructionPlane() radius = self.m_center_point.DistanceTo(e.CurrentPoint) circle = Circle(cplane, self.m_center_point, radius) e.Display.DrawCircle(circle, Color.Black) if __name__ == "__main__": RunCommand() ``` -------------------------------------------------------------------------------- # Dynamically Drawing Geometry when Picking Points Source: https://developer.rhino3d.com/en/guides/cpp/dynamically-drawing-geometry-when-picking-points/ This guide demonstrates how to derive a new class to dynamically draw geometry during a point picking operation using C/C++. ## Overview When using Rhino, you have probably noticed that many of the object creation commands, such as *Line* and *Circle*, and transformation commands, such as *Move* and *Copy*, dynamically draw objects as they are being created or transformed. This operation is performed by deriving a new class from Rhino's point pick class, `CRhinoGetPoint`, and overriding two member functions: `OnMouseMove()` and `DynamicDraw()`. `OnMouseMove()` is called every time the mouse moves. This is a great place to perform calculations, such as transformations. `DynamicDraw()` is called as the mouse moves, as well. Every time the mouse moves, `DynamicDraw()` will be called once per viewport. **NOTE**: Rhino calls `DynamicDraw()` happen after the call to `OnMouseMove()`. ## Sample The following sample demonstrates how to derive a new class from `CRhinoGetPoint` and override `OnMouseMove()` and `DynamicDraw()` to dynamically draw geometry. In this sample, we are going to dynamically draw a circle while the user is specifying its radius. ```cpp class CGetCircleRadiusPoint : public CRhinoGetPoint { public: CGetCircleRadiusPoint(); void SetCenterPoint( const ON_3dPoint center_point ); bool CalculateCircle( CRhinoViewport& vp, const ON_3dPoint& pt ); void OnMouseMove( CRhinoViewport& vp, UINT flags, const ON_3dPoint& pt, const CPoint* pt2d ); void DynamicDraw( HDC hDC, CRhinoViewport& vp, const ON_3dPoint& pt ); const ON_Circle& Circle() const; private: ON_Circle m_circle; ON_3dPoint m_center_point; bool m_draw_circle; }; CGetCircleRadiusPoint::CGetCircleRadiusPoint() { m_draw_circle = false; } void CGetCircleRadiusPoint::SetCenterPoint( const ON_3dPoint center_point ) { m_center_point = center_point; } bool CGetCircleRadiusPoint::CalculateCircle( CRhinoViewport& vp, const ON_3dPoint& pt ) { double radius = m_center_point.DistanceTo( pt ); if( radius < ON_SQRT_EPSILON ) return false; ON_Plane plane = vp.ConstructionPlane().m_plane; plane.SetOrigin( m_center_point ); m_circle.Create( plane, radius ); return m_circle.IsValid() ? true : false; } void CGetCircleRadiusPoint::OnMouseMove( CRhinoViewport& vp, UINT flags, const ON_3dPoint& pt, const CPoint* pt2d ) { m_draw_circle = CalculateCircle( vp, pt ); CRhinoGetPoint::OnMouseMove( vp, flags, pt, pt2d ); } void CGetCircleRadiusPoint::DynamicDraw( HDC hDC, CRhinoViewport& vp, const ON_3dPoint& pt ) { if( m_draw_circle ) { ON_Color color = RhinoApp().AppSettings().TrackingColor(); ON_Color saved_color = vp.SetDrawColor( color ); vp.DrawCircle( m_circle ); vp.SetDrawColor( saved_color ); } CRhinoGetPoint::DynamicDraw( hDC, vp, pt ); } const ON_Circle& CGetCircleRadiusPoint::Circle() const { return m_circle; } ``` Finally, here is our `CRhinoGetPoint` derived class in action: ```cpp CRhinoCommand::result CCommandTest::RunCommand(const CRhinoCommandContext& context) { CRhinoGetPoint gp; gp.SetCommandPrompt( L"Center point" ); gp.ConstrainToConstructionPlane( FALSE ); gp.GetPoint(); if( gp.CommandResult() != CRhinoCommand::success ) return gp.CommandResult(); ON_3dPoint center_point = gp.Point(); CGetCircleRadiusPoint gc; gc.SetCommandPrompt( L"Radius" ); gc.ConstrainToConstructionPlane( FALSE ); gc.SetBasePoint( center_point ); gc.DrawLineFromPoint( center_point, TRUE ); gc.SetCenterPoint( center_point ); gc.GetPoint(); if( gc.CommandResult() != CRhinoCommand::success ) return gc.CommandResult(); if( gc.CalculateCircle( gc.View()->Viewport(), gc.Point() )) { ON_Circle circle = gc.Circle(); context.m_doc.AddCurveObject( circle ); context.m_doc.Redraw(); } return CRhinoCommand::success; } ``` -------------------------------------------------------------------------------- # Dynamically Drawing Polylines Source: https://developer.rhino3d.com/en/guides/cpp/dynamically-drawing-polylines/ This guide demonstrates how to derive a new class from CRhinoGetPoint to dynamically draw a polyline. ## Problem What is the best way to draw dynamically a geometry based on a polyline? How is it possible to compute an object geometry based on a polyline picked by the user? ## Solution If you are not interested in writing your own drawing routine and you want to just use Rhino's built-in polyline drawing tool, then you can just use Rhino's `RhinoGetPolyline` function. See *rhinoSdkGetLine.h* for more information. If you need more control over how the polyline is define or how it is drawn, then you can derive your own class from `CRhinoGetPoint` and draw the polyline yourself. ## Sample The following sample code demonstrates a simple class, derived from `CRhinoGetPoint`, that dynamically draws a polyline based on the points picked by a user. ```cpp ///////////////////////////////////////////////////////////////////////////// // CRhGetPoints declaration class CRhGetPoints : public CRhinoGetPoint { public: CRhGetPoints(); void DynamicDraw( HDC hdc, CRhinoViewport& vp, const ON_3dPoint& pt ); CRhinoGet::result GetPoints( CRhinoHistory* history = NULL, bool bOnMouseUp = false ); int Points( ON_3dPointArray& points ); public: ON_3dPointArray m_P; }; ///////////////////////////////////////////////////////////////////////////// // CRhGetPoints definition CRhGetPoints::CRhGetPoints() { m_P.SetCapacity( 64 ); } CRhinoGet::result CRhGetPoints::GetPoints( CRhinoHistory* history, bool bOnMouseUp ) { m_P.Empty(); SetCommandPrompt( L"First point" ); CRhinoGet::result res = GetPoint( history, bOnMouseUp ); if( res == CRhinoGet::point ) { m_P.Append( Point() ); SetCommandPrompt( L"Next point" ); PermitOrthoSnap(); AcceptNothing(); for( ;; ) { SetBasePoint( Point() ); res = GetPoint( history, bOnMouseUp ); if( res == CRhinoGet::point ) { m_P.Append( Point() ); continue; } else if( res == CRhinoGet::nothing ) res = CRhinoGet::point; else if( res == CRhinoGet::cancel ) m_P.Empty(); break; } } return res; } void CRhGetPoints::DynamicDraw( HDC hdc, CRhinoViewport& v, const ON_3dPoint& pt ) { if( m_P.Count() > 0 ) { int i; for( i = 1; i < m_P.Count(); i++ ) { v.DrawPoint( m_P[i-1] ); v.DrawLine( m_P[i-1], m_P[i] ); } v.DrawPoint( m_P[i-1] ); v.DrawPoint( pt ); v.DrawLine( m_P[i-1], pt ); } CRhinoGetPoint::DynamicDraw( hdc, v, pt); } int CRhGetPoints::Points( ON_3dPointArray& points ) { int count = m_P.Count(); if( count > 0 ) points.Append( count, m_P.Array() ); return count; } ``` You would use the above class as follows: ```cpp CRhinoCommand::result CCommandTest::RunCommand( const CRhinoCommandContext& context ) { CRhGetPoints gp; CRhinoGet::result res = gp.GetPoints(); if( res == CRhinoGet::point ) { // TODO... } return CRhinoCommand::success; } ``` ## Notes Some additional features that would make this class better are: - The code should evaluate the distance between the current point and the previous point before adding them to the point array to ensure the points are not duplicate. - The code should allow for an undo feature. - After a few points have been picked, the drawing tends to flicker. This is because the entire polyline is being drawn on every mouse move. To prevent flickering, portions of the polyline that do not need to be drawn on every mouse move should drawn with a conduit. -------------------------------------------------------------------------------- # Dynamically Drawing Text Strings Source: https://developer.rhino3d.com/en/guides/cpp/dynamically-drawing-text-strings/ This guide demonstrates how to dynamically draw text strings using C/C++. ## Overview On occasion, it is useful to dynamically display some text while in the middle of a point picking operation. Rhino's `VariableFilletSrf` command is a good example of a command that does this. To add this capability to an plugin command, you need to: 1. Derive a new class from `CRhinoGetPoint`. 1. Override the `CRhinoGetPoint::DynamicDraw` virtual function. 1. From within the `DynamicDraw` override, call `CRhinoViewport::DrawString`. ## Sample The following example code demonstrates how to derive a new class from `CRhinoGetPoint`, override the `CRhinoGetPoint::DynamicDraw` member, and draw text dynamically. ```cpp class CDrawStringGetPoint : public CRhinoGetPoint { public: CDrawStringGetPoint() {} void DynamicDraw( HDC hdc, CRhinoViewport& vp, const ON_3dPoint& pt ); }; void CDrawStringGetPoint::DynamicDraw( HDC hdc, CRhinoViewport& vp, const ON_3dPoint& pt ) { // Format active point as a string ON_wString str; RhinoFormatPoint( pt, str ); // Build world-to-screen coordinate transformation ON_Xform w2s; vp.VP().GetXform( ON::world_cs, ON::screen_cs, w2s ); // Transform point from world to screen coordinates ON_3dPoint screenpoint = w2s * pt; // Offset point so text does not overlap cursor screenpoint.x += 5.0; screenpoint.y += -5.0; // Draw string using the system font vp.DrawString( str, str.Length(), screenpoint, false, 0, 12, L"System" ); // Allow base class to draw CRhinoGetPoint::DynamicDraw( hdc, vp, pt ); } ``` You can use the above class as you would a `CRhinoGetPoint` object. Just create a new `CDrawStringGetPoint` object, initialize the class by calling base class members, and call it's `GetPoint` member. For example: ```cpp CRhinoCommand::result CCommandTest::RunCommand( const CRhinoCommandContext& context ) { CDrawStringGetPoint gp; gp.SetCommandPrompt( L"Pick test point" ); gp.GetPoint(); if( gp.CommandResult() != success ) return gp.CommandResult(); // TODO... return success; } ``` -------------------------------------------------------------------------------- # Dynamically Drawing Text Strings Source: https://developer.rhino3d.com/en/samples/rhinocommon/dynamically-drawing-text-strings/ Demonstrates how to dynamically draw text strings relative to a given screen to world transform. ```cs partial class Examples { public static Result DrawString(RhinoDoc doc) { var gp = new GetDrawStringPoint(); gp.SetCommandPrompt("Point"); gp.Get(); return gp.CommandResult(); } } public class GetDrawStringPoint : GetPoint { protected override void OnDynamicDraw(GetPointDrawEventArgs e) { base.OnDynamicDraw(e); var xform = e.Viewport.GetTransform(CoordinateSystem.World, CoordinateSystem.Screen); var current_point = e.CurrentPoint; current_point.Transform(xform); var screen_point = new Point2d(current_point.X, current_point.Y); var msg = string.Format("screen {0:F}, {1:F}", current_point.X, current_point.Y); e.Display.Draw2dText(msg, System.Drawing.Color.Blue, screen_point, false); } } ``` ```python from Rhino import * from Rhino.DocObjects import * from Rhino.Geometry import * from Rhino.Commands import * from Rhino.Input.Custom import * from System.Drawing import Color def RunCommand(): gp = GetDrawStringPoint() gp.SetCommandPrompt("Point") gp.Get() return gp.CommandResult() class GetDrawStringPoint(GetPoint): def OnDynamicDraw(self, e): xform = e.Viewport.GetTransform(CoordinateSystem.World, CoordinateSystem.Screen) current_point = e.CurrentPoint current_point.Transform(xform) screen_point = Point2d(current_point.X, current_point.Y) msg = "screen {0}, {1}".format(screen_point.X, screen_point.Y) e.Display.Draw2dText(msg, Color.Blue, screen_point, False) if __name__ == "__main__": RunCommand() ``` -------------------------------------------------------------------------------- # Dynamically Inserting Blocks Source: https://developer.rhino3d.com/en/guides/cpp/dynamically-inserting-blocks/ This guide demonstrates how to insert a block instance at a user specified location using C/C++. ## Overview To dynamically insert and draw blocks, you can take advantage of the new display pipeline technology, which is capable of drawing block instance definitions. For example, the following code demonstrates how to draw a block instance definition from a `CRhinoGetPoint`-derived object's DynamicDraw member... ```cpp class CGetBlockInsertPoint : public CRhinoGetPoint { public: CGetBlockInsertPoint( const CRhinoInstanceDefinition* idef ); // CRhinoGetPoint overrides void OnMouseMove( CRhinoViewport&, UINT, const ON_3dPoint&, const CPoint* ); void DynamicDraw( HDC, CRhinoViewport&, const ON_3dPoint& ); bool CalculateTransform( CRhinoViewport&, const ON_3dPoint&, ON_Xform& ); private: const CRhinoInstanceDefinition* m_idef; ON_Xform m_xform; bool m_bDraw; }; CGetBlockInsertPoint::CGetBlockInsertPoint( const CRhinoInstanceDefinition* idef ) : m_idef(idef) { m_xform.Identity(); m_bDraw = false; } void CGetBlockInsertPoint::OnMouseMove( CRhinoViewport& vp, UINT flags, const ON_3dPoint& pt, const CPoint* p ) { m_bDraw = CalculateTransform( vp, pt, m_xform ); CRhinoGetPoint::OnMouseMove( vp, flags, pt, p ); } void CGetBlockInsertPoint::DynamicDraw( HDC hdc, CRhinoViewport& vp, const ON_3dPoint& pt ) { if( m_idef && m_bDraw ) { CRhinoDisplayPipeline* dp = vp.DisplayPipeline(); if( dp ) { dp->PushObjectColor( 0 ); dp->DrawObject( m_idef, &m_xform ); dp->PopObjectColor(); } } CRhinoGetPoint::DynamicDraw( hdc, vp, pt ); } bool CGetBlockInsertPoint::CalculateTransform( CRhinoViewport& vp, const ON_3dPoint& pt, ON_Xform& xform ) { bool rc = false; ON_3dVector v = pt - BasePoint(); if( v.IsTiny() ) xform.Identity(); else xform.Translation( v ); return xform.IsValid(); } ``` ## Sample The following sample code demonstrates how to insert a block definition into the Rhino document and allow the user to interactively pick the insertion point. ```cpp CRhinoCommand::result CCommandTest::RunCommand( const CRhinoCommandContext& context ) { // Prompt for instance definition to insert CRhinoGetString gs; gs.SetCommandPrompt( L"Name of block to insert" ); gs.GetString(); if( gs.CommandResult() != success ) return gs.CommandResult(); ON_wString idef_name = gs.String(); idef_name.TrimLeftAndRight(); if( idef_name.IsEmpty() ) return nothing; // Find specified instance definition CRhinoInstanceDefinitionTable& idef_table = context.m_doc.m_instance_definition_table; int idef_index = idef_table.FindInstanceDefinition( idef_name ); if( idef_index < 0 ) { RhinoApp().Print( L"Unable to insert \"%s\". Block not found.\n", idef_name ); return nothing; } // Get instance definition const CRhinoInstanceDefinition* idef = idef_table[idef_index]; if( !idef | idef->IsDeleted() ) return nothing; // Pick an insert point CGetBlockInsertPoint gp( idef ); gp.SetCommandPrompt( L"Insertion point" ); gp.SetBasePoint( ON_origin ); gp.GetPoint(); if( gp.CommandResult() != success ) return gp.CommandResult(); // Get active view CRhinoView* view = gp.View(); if( !view ) { view = RhinoApp().ActiveView(); if( !view ) return failure; } // Calculate final xform ON_Xform xform; if( gp.CalculateTransform(view->ActiveViewport(), gp.Point(), xform) ) { idef_table.AddInstanceObject( idef_index, xform ); context.m_doc.Redraw(); } return success; } ``` ## Related Topics - [Creating Blocks](/guides/cpp/creating-blocks) -------------------------------------------------------------------------------- # Edit Text Source: https://developer.rhino3d.com/en/samples/rhinocommon/edit-text/ Demonstrates how to edit selected text, replacing it with new text. ```cs partial class Examples { public static Rhino.Commands.Result EditText(Rhino.RhinoDoc doc) { Rhino.DocObjects.ObjRef objref; Rhino.Commands.Result rc = Rhino.Input.RhinoGet.GetOneObject("Select text", false, Rhino.DocObjects.ObjectType.Annotation, out objref); if (rc != Rhino.Commands.Result.Success || objref == null) return rc; Rhino.DocObjects.TextObject textobj = objref.Object() as Rhino.DocObjects.TextObject; if (textobj == null) return Rhino.Commands.Result.Failure; Rhino.Geometry.TextEntity textentity = textobj.Geometry as Rhino.Geometry.TextEntity; if (textentity == null) return Rhino.Commands.Result.Failure; string str = textentity.Text; rc = Rhino.Input.RhinoGet.GetString("New text", false, ref str); if (rc != Rhino.Commands.Result.Success) return rc; textentity.Text = str; textobj.CommitChanges(); doc.Views.Redraw(); return Rhino.Commands.Result.Success; } } ``` ```python import Rhino import scriptcontext def EditText(): rc, objref = Rhino.Input.RhinoGet.GetOneObject("Select text", False, Rhino.DocObjects.ObjectType.Annotation) if rc!=Rhino.Commands.Result.Success: return textobj = objref.Object() if not textobj: return str = textobj.Geometry.Text rc, str = Rhino.Input.RhinoGet.GetString("New text", False, str) if rc!=Rhino.Commands.Result.Success: return textobj.Geometry.Text = str; textobj.CommitChanges(); scriptcontext.doc.Views.Redraw(); if __name__=="__main__": EditText() ``` -------------------------------------------------------------------------------- # Efficient Script Loading Source: https://developer.rhino3d.com/en/guides/rhinoscript/efficient-script-loading/ This guide discusses different techniques of loading and running script and their efficiencies. ## LoadScript and RunScript When running a script from a toolbar button, is it better to use the *LoadScript* command or *RunScript* command? Which is better for Rhino, resource wise? The **LoadScript** command: 1. Opens the script file, reads the contents of the file into a buffer, and then closes the file. 1. Loads the buffer, read in from the script file, it into the scripting engine. The script engine, then, attempts to parse the script. 1. If the script was parsed successfully, it is run, or executed. The **RunScript** command: 1. Runs the script, skipping steps 1. and 2. listed above. ## Considerations Using *LoadScript* to load the same script file over and over and over again is somewhat inefficient and certainly unnecessary, as you are simply replacing the same script, over and over again, that is already resident in the script engine. The only time you need reload a script is if the script has changed, or if the script engine was reset. One technique you can use to be more efficient, when loading scripts, is to have them load at startup. You can specify the scripts to load at startup by selecting **Tool** > **Option** > **RhinoScript**. Then, you can just use the *RunScript* to execute your pre-loaded scripts. Another technique you can use it to load your scripts on demand. For example, say you have a *Hello.rvb* script file with a single function defined as such: ```vbnet Sub Hello Call MsgBox("Hello Rhino!") End Sub ``` From a toolbar button, you could use the following macro to load it on demand and then run it: ```vbnet _-NoEcho _-RunScript ( If Not Rhino.IsProcedure("Hello") Then Call Rhino.Command("_-LoadScript Hello.rvb", 0) End If Call Hello ``` The code above checks for the existence of a user-defined procedure (e.g. subroutine or function) named `"Hello"`. If the procedure is not found, then script file, were the procedure is stored, is loaded by running the *LoadScript* command. Finally, the specified procedure is called. To ensure this technique works, make sure to included the path to Hello.rvb is included in Rhino's file search path by selecting **Tools** > **Options** > **Files**. ## Related Topics - [Script Demand Loading](/guides/rhinoscript/script-demand-load) -------------------------------------------------------------------------------- # Enabling Orthogonal Mode Source: https://developer.rhino3d.com/en/guides/cpp/enabling-orthogonal-mode/ This brief guide demonstrates how to enable Rhino's orthogonal mode using C/C++. ## Problem You are trying to draw a line and you need ortho enabled. ## Solution The state of Rhino's orthogonal drawing mode is stored in Rhino's application settings, or it's `CRhinoAppSettings` object. To check the current state of ortho, call `CRhinoAppSettings::Ortho`. To enable or disable ortho, call `CRhinoAppSettings::EnableOrtho` and pass in the boolean value that is appropriate. For more information on this and Rhino's application settings class, see *rhinoSdkAppSettings.h*. ## Sample The following sample code illustrates how to use this feature: ```cpp CRhinoCommand::result CCommandTest::RunCommand(const CRhinoCommandContext& context) { CRhinoGetPoint gp; gp.SetCommandPrompt(L"Starting point"); gp.GetPoint(); if (gp.CommandResult() != success) return gp.CommandResult(); ON_3dPoint start_point = gp.Point(); CRhinoAppSettings& settings = RhinoApp().AppSettings(); bool bOldValue = settings.Ortho(); if (bOldValue == false) settings.EnableOrtho(true); gp.SetCommandPrompt(L"Ending point"); gp.SetBasePoint(start_point); gp.DrawLineFromPoint(start_point, true); gp.GetPoint(); if (bOldValue != settings.Ortho()) settings.EnableOrtho(bOldValue); if (gp.CommandResult() != success) return gp.CommandResult(); ON_3dPoint end_point = gp.Point(); ON_Line line(start_point, end_point); context.m_doc.AddCurveObject(line); context.m_doc.Redraw(); return success; } ``` -------------------------------------------------------------------------------- # Error Handling Source: https://developer.rhino3d.com/en/guides/rhinoscript/error-handling/ This guide describes the error handling semantics of VBScript. ## Overview There are two statements that affect error handling in VBScript: ```vbnet On Error Resume Next On Error Goto 0 ``` The meaning of the first statement is this: if you get an error, ignore it and resume execution on the next statement. As we'll see, there are some subtleties. The second statement simply turns off "Resume Next" mode if it is on. The odd syntax is because Visual Basic has an error handling mode which VBScript does not – VB can branch to a labeled or numbered statement. ## Discussion The subtlety in the "Resume Next" mode is best illustrated with an example... ```vbnet Const InvalidCall = 5 Rhino.Print "Global code start" Blah1 Rhino.Print "Global code end" Sub Blah1() On Error Resume Next Rhino.Print "Blah1 Start" Blah2 Rhino.Print "Blah1 End" End Sub Sub Blah2() Rhino.Print "Blah2 Start" Err.Raise InvalidCall Rhino.Print "Blah2 End" End Sub ``` This prints out: ```vbs Global code start Blah1 Start Blah2 Start Blah1 End Global code end ``` When the error ocurred, Blah1 had already turned "Resume Next" mode on. The next statement after the error raise is Print "Blah2 End" but that statement was never executed. This is because the error mode is on a per-procedure basis, not a global basis. Also, remember that the `Next` in “Resume Next” mode is the next statement. Consider these two scripts: ```vbnet On Error Resume Next Temp = CInt(Foo.Bar(123)) Blah Temp Rhino.Print "Done" On Error Resume Next Blah CInt(Foo.Bar(123)) Rhino.Print "Done" ``` They do not have the same semantics. If `Foo.Bar` raises an error, then the first script passes `Empty` to `Blah`. The second one never calls `Blah` at all if an error is raised, because it resumes to the next statement. You can get into similar trouble with other constructs. For example, these do have the same semantics: ```vbnet On Error Resume Next If Blah Then Rhino.Print "Hello" End If Rhino.Print "Goodbye" On Error Resume Next If Blah Then Rhino.Print "Hello" Rhino.Print "Goodbye" ``` If `Blah` raises an error then it resumes on the `Rhino.Print "Hello"` in either case. You can also get into trouble with loops: ```vbnet On Error Resume Next For index = 1 to Blah Rhino.Print TypeName(index) Next Rhino.Print "Goodbye" ``` If Blah raises an error, this resumes into the loop, not after the loop. This prints out: ```vbs Empty Goodbye ``` -------------------------------------------------------------------------------- # Evaluate Curve Torsion Source: https://developer.rhino3d.com/en/samples/rhinoscript/evaluate-curve-torsion/ Demonstrates how to evaluate the torsion of a curve in RhinoScript. ```vbnet ''' ''' Description ''' Evaluate the torsion of a curve. ''' Parameters ''' crv - a string that identifies the curve to evaluate ''' t - a parameter of the curve within its domain ''' Returns ''' The torsion if successful. ''' Null if the torsion is undefined at the parameter. ''' Function EvaluateTorsion(crv, t) ' Local variables Dim data, d1xd2, numer, denom ' Default return value EvaluateTorsion = Null ' Calculate the torsion data = Rhino.CurveEvaluate(crv, t, 3) If IsArray(data) And UBound(data) = 3 Then d1xd2 = Rhino.VectorCrossProduct(data(1), data(2)) numer = Rhino.VectorDotProduct(d1xd2, data(3)) denom = Rhino.VectorDotProduct(d1xd2, d1xd2) If denom > 0 Then EvaluateTorsion = numer / denom End If End If End Function ``` -------------------------------------------------------------------------------- # Explode Hatch Source: https://developer.rhino3d.com/en/samples/rhinocommon/explode-hatch/ Demonstrates how to explode a user-specified hatch object into its constituent parts (curves, points, etc.) ```cs partial class Examples { public static Rhino.Commands.Result ExplodeHatch(Rhino.RhinoDoc doc) { const ObjectType filter = Rhino.DocObjects.ObjectType.Hatch; Rhino.DocObjects.ObjRef objref; Rhino.Commands.Result rc = Rhino.Input.RhinoGet.GetOneObject("Select hatch to explode", false, filter, out objref); if (rc != Rhino.Commands.Result.Success || objref == null) return rc; Rhino.Geometry.Hatch hatch = objref.Geometry() as Rhino.Geometry.Hatch; if (null == hatch) return Rhino.Commands.Result.Failure; Rhino.Geometry.GeometryBase[] hatch_geom = hatch.Explode(); if (null != hatch_geom) { for (int i = 0; i < hatch_geom.Length; i++) { Rhino.Geometry.GeometryBase geom = hatch_geom[i]; if (null != geom) { switch (geom.ObjectType) { case Rhino.DocObjects.ObjectType.Point: { Rhino.Geometry.Point point = geom as Rhino.Geometry.Point; if (null != point) doc.Objects.AddPoint(point.Location); } break; case Rhino.DocObjects.ObjectType.Curve: { Rhino.Geometry.Curve curve = geom as Rhino.Geometry.Curve; if (null != curve) doc.Objects.AddCurve(curve); } break; case Rhino.DocObjects.ObjectType.Brep: { Rhino.Geometry.Brep brep = geom as Rhino.Geometry.Brep; if (null != brep) doc.Objects.AddBrep(brep); } break; } } } } return Rhino.Commands.Result.Success; } } ``` ```python import Rhino import scriptcontext def ExplodeHatch(): filter = Rhino.DocObjects.ObjectType.Hatch rc, objref = Rhino.Input.RhinoGet.GetOneObject("Select hatch to explode", False, filter) if rc != Rhino.Commands.Result.Success: return hatch = objref.Geometry() if not hatch: return hatch_geom = hatch.Explode() if hatch_geom: for geom in hatch_geom: if geom.ObjectType == Rhino.DocObjects.ObjectType.Point: scriptcontext.doc.Objects.AddPoint(geom) elif geom.ObjectType == Rhino.DocObjects.ObjectType.Curve: scriptcontext.doc.Objects.AddCurve(geom) elif geom.ObjectType == Rhino.DocObjects.ObjectType.Brep: scriptcontext.doc.Objects.AddBrep(geom) scriptcontext.doc.Views.Redraw() if __name__=="__main__": ExplodeHatch() ``` -------------------------------------------------------------------------------- # Exploding Block Instances Source: https://developer.rhino3d.com/en/samples/rhinoscript/exploding-block-instances/ Demonstrates how to explode an instance of a block using RhinoScript. ```vbnet Sub SuperExplodeBlock Const rhInstanceObject = 4096 Dim arrBlocks, strBlock arrBlocks = Rhino.ObjectsByType(rhInstanceObject) If IsArray(arrBlocks) Then For Each strBlock In arrBlocks If Rhino.IsObjectSelectable(strBlock) Then DoInstanceExplosion strBlock End If Next End If End Sub Sub DoBlockExplosion(strBlock) Dim arrObjects, strObject If Rhino.IsBlockInstance(strBlock) Then arrObjects = Rhino.ExplodeBlockInstance(strBlock) If IsArray(arrObjects) Then For Each strObject In arrObjects DoBlockExplosion strObject '*RECURSE* Next End If End If End Sub ``` -------------------------------------------------------------------------------- # Exploding Meshes Source: https://developer.rhino3d.com/en/samples/rhinoscript/exploding-meshes/ Demonstrates how to explode a mesh into individual faces using RhinoScript. ```vbnet Option Explicit Sub ExplodeMesh Dim mesh mesh = Rhino.GetObject("Select mesh to explode", 32) If IsNull(mesh) Then Exit Sub Dim faces faces = Rhino.MeshFaces(mesh, True) If Not IsArray(faces) Then Exit Sub Rhino.EnableRedraw False Dim i, a, b, c, d, bQuad i = 0 While i <= UBound(faces) a = faces(i) b = faces(i+1) c = faces(i+2) d = faces(i+3) If c(0)=d(0) And c(1)=d(1) And c(2)=d(2) Then Rhino.AddMesh Array(a,b,c,d), Array(Array(0,1,2,2)) Else Rhino.AddMesh Array(a,b,c,d), Array(Array(0,1,2,3)) End If i = i + 4 Wend Rhino.DeleteObject mesh Rhino.EnableRedraw True End Sub ``` -------------------------------------------------------------------------------- # Export Block Count to Excel Source: https://developer.rhino3d.com/en/samples/rhinoscript/export-block-count-to-excel/ Demonstrates how to count blocks and then export the results to Microsoft Excel. ```vbnet ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' ExportBlockCount.rvb -- May 2010 ' If this code works, it was written by Dale Fugier. ' If not, I don't know who wrote it. ' Works with Rhino 4.0. Option Explicit Sub ExportBlockCount() Dim arrBlocks, strBlock, strName Dim objCounts, objKey Dim objExcel, objBook, objSheet, nCell ' Get all of the block instances in the document arrBlocks = Rhino.ObjectsByType(4096) If IsNull(arrBlocks) Then Call Rhino.Print("No blocks to export.") Exit Sub End If ' Create a dictionary object for counting blocks Set objCounts = CreateObject("Scripting.Dictionary") ' Count the blocks For Each strBlock In arrBlocks strName = Rhino.BlockInstanceName(strBlock) If objCounts.Exists(strName) Then objCounts(strName) = objCounts(strName) + 1 Else Call objCounts.Add(strName, 1) End If Next ' Create a Excel object Set objExcel = CreateObject("Excel.Application") objExcel.Visible = True ' Initialize Excel Set objBook = objExcel.Workbooks.Add Set objSheet = objBook.Worksheets(1) ' Place titles on sheet nCell = 1 objExcel.Cells(nCell, 1).Value = "Block Name" objExcel.Cells(nCell, 2).Value = "Count" nCell = nCell + 1 ' Write the blocks and counts to the sheet Call Rhino.Print("Block counts:") For Each objKey In objCounts objExcel.Cells(nCell, 1).Value = CStr(objKey) objExcel.Cells(nCell, 2).Value = CStr(objCounts(objKey)) ' Print to command line too Call Rhino.Print(" " & CStr(objKey) & " = " & CStr(objCounts(objKey))) nCell = nCell + 1 Next End Sub ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' Drag & drop and alias creation stuff Rhino.AddStartUpScript Rhino.LastLoadedScriptFile Rhino.AddAlias "ExportBlockCount", "_-RunScript (ExportBlockCount)" ``` -------------------------------------------------------------------------------- # Export Control Points Source: https://developer.rhino3d.com/en/samples/rhinocommon/export-control-points/ Demonstrates how to export the control points of a user-selected curve and write them to a file. ```cs partial class Examples { public static Rhino.Commands.Result ExportControlPoints(Rhino.RhinoDoc doc) { Rhino.DocObjects.ObjRef objref; var get_rc = Rhino.Input.RhinoGet.GetOneObject("Select curve", false, Rhino.DocObjects.ObjectType.Curve, out objref); if (get_rc != Rhino.Commands.Result.Success) return get_rc; var curve = objref.Curve(); if (curve == null) return Rhino.Commands.Result.Failure; var nc = curve.ToNurbsCurve(); var fd = new SaveFileDialog(); //fd.Filters = "Text Files | *.txt"; //fd.Filter = "Text Files | *.txt"; //fd.DefaultExt = "txt"; //if( fd.ShowDialog(Rhino.RhinoApp.MainWindow())!= System.Windows.Forms.DialogResult.OK) if (fd.ShowDialog(null) != DialogResult.Ok) return Rhino.Commands.Result.Cancel; string path = fd.FileName; using( System.IO.StreamWriter sw = new System.IO.StreamWriter(path) ) { foreach( var pt in nc.Points ) { var loc = pt.Location; sw.WriteLine("{0} {1} {2}", loc.X, loc.Y, loc.Z); } sw.Close(); } return Rhino.Commands.Result.Success; } } ``` ```python # No Python sample available ``` -------------------------------------------------------------------------------- # Export Curve Control Points Source: https://developer.rhino3d.com/en/samples/rhinoscript/export-curve-control-points/ Demonstrates how to export the 3D coordinates of a curve's control points to a text file using RhinoScript. ```vbnet Sub ExportControlPoints() 'Pick a curve object Dim strObject strObject = Rhino.GetObject("Select curve", 4) If IsNull(strObject) Then Exit Sub ' Get the curve's control points Dim arrPoints arrPoints = Rhino.CurvePoints(strObject) If Not IsArray(arrPoints) Then Exit Sub ' Prompt the user to specify a file name Dim strFilter, strFileName strFilter = "Text File (*.txt)|*.txt|All Files (*.*)|*.*|" strFileName = Rhino.SaveFileName("Save Control Points As", strFilter) If IsNull(strFileName) Then Exit Sub ' Get the file system object Dim objFSO, objStream Set objFSO = CreateObject("Scripting.FileSystemObject") ' Open a text file to write to On Error Resume Next Set objStream = objFSO.CreateTextFile(strFileName, True) If Err Then MsgBox Err.Description Exit Sub End If ' Write each point as text to the file Dim strPoint, strText For Each strPoint In arrPoints strText = Rhino.Pt2Str(strPoint) objStream.WriteLine(strText) Next ' Close the file objStream.Close End Sub ``` -------------------------------------------------------------------------------- # Export Layer Objects Source: https://developer.rhino3d.com/en/samples/rhinoscript/export-layer-objects/ Demonstrates how to export all objects by layer, with each layer exported to a new file using RhinoScript. ```vbnet Option Explicit Sub ExportLayerObjects ' Declare local variables Dim strPath, strFile Dim arrLayers, strLayer Dim arrSelected ' Get the path to and name of the current document. ' Surround with double-quotes in case path includes spaces. strPath = Chr(34) & Rhino.DocumentPath & Rhino.DocumentName & Chr(34) ' Get names of all layers arrLayers = Rhino.LayerNames ' Disable redrawing Rhino.EnableRedraw False ' Process each layer For Each strLayer In arrLayers ' Unselect all Rhino.Command "_-SelNone", 0 ' Select all objects on layer. Surround layer name ' with double-quotes in case it includes spaces. Rhino.Command "_-SelLayer " & Chr(34) & strLayer & Chr(34), 0 ' Make sure some objects were selected arrSelected = Rhino.SelectedObjects If IsArray(arrSelected) Then ' Generate a modified path string ' that includes the layer name strFile = strPath strFile = Replace(strFile, ".3dm", "_" & strLayer & ".3dm") ' Export the selected objects Rhino.Command "_-Export " & strFile, 0 End If Next ' Unselect all Rhino.Command "_-SelNone", 0 ' Enable redrawing Rhino.EnableRedraw True End Sub ``` -------------------------------------------------------------------------------- # Export Points to Excel Source: https://developer.rhino3d.com/en/samples/rhinoscript/export-points-to-excel/ Illustrates RhinoScript code that exports Rhino point coordinates to Microsoft Excel. ```vbnet Sub ExportPointsToExcel() Const rhPoint = 1 Dim arrPoints arrPoints = Rhino.GetObjects("Select points to export", rhPoint, True, True) If Not IsArray(arrPoints) Then Exit Sub Dim objXL Set objXL = CreateObject("Excel.Application") objXL.Visible = True objXL.WorkBooks.Add objXL.Columns(1).ColumnWidth = 20 objXL.Columns(2).ColumnWidth = 20 objXL.Columns(3).ColumnWidth = 20 objXL.Cells(1, 1).Value = "X" objXL.Cells(1, 2).Value = "Y" objXL.Cells(1, 3).Value = "Z" objXL.Range("A1:C1").Select objXL.Selection.Font.Bold = True objXL.Selection.Interior.ColorIndex = 1 objXL.Selection.Interior.Pattern = 1 'xlSolid objXL.Selection.Font.ColorIndex = 2 objXL.Columns("B:B").Select objXL.Selection.HorizontalAlignment = &hFFFFEFDD ' xlLeft Dim intIndex intIndex = 2 Dim strPoint, arrPt For Each strPoint In arrPoints arrPt = Rhino.PointCoordinates(strPoint) objXL.Cells(intIndex, 1).Value = arrPt(0) objXL.Cells(intIndex, 2).Value = arrPt(1) objXL.Cells(intIndex, 3).Value = arrPt(2) intIndex = intIndex + 1 Next objXL.UserControl = True End Sub ``` -------------------------------------------------------------------------------- # Exporting Meshes to Geomview Source: https://developer.rhino3d.com/en/samples/rhinoscript/exporting-meshes-to-geomview/ Demonstrates how to export a mesh object to Geomview's OFF file format using RhinoScript. ```vbnet ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' ExportOff.rvb -- February 2009 ' If this code works, it was written by Dale Fugier. ' If not, I don't know who wrote it. ' Works with Rhino 4.0. ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Option Explicit ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' Exports a mesh object to a Geomview .OFF file ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Sub ExportOff ' Local variables Dim strMesh, strFilter, strFileName Dim objFSO, objStream, i Dim v_count, v_list, v Dim f_count, f_list, f ' Pick a mesh object strMesh = Rhino.GetObject("Select mesh", 32) If IsNull(strMesh) Then Exit Sub ' Prompt the user to specify a file name strFilter = "Geomview OFF (*.off)|*.off|" strFileName = Rhino.SaveFileName("Save As", strFilter) If IsNull(strFileName) Then Exit Sub ' Get the file system object Set objFSO = CreateObject("Scripting.FileSystemObject") ' Open a text file to write to On Error Resume Next Set objStream = objFSO.CreateTextFile(strFileName, True) If Err Then MsgBox Err.Description Exit Sub End If ' Write the header objStream.WriteLine("OFF") objStream.WriteLine("#") objStream.WriteLine("# " & strFileName) objStream.WriteLine("#") ' Write the vertex, face, and edge counts v_count = Rhino.MeshVertexCount(strMesh) f_count = Rhino.MeshFaceCount(strMesh) objStream.WriteLine(CStr(v_count) & " " & CStr(f_count) & " " & CStr(v_count*f_count)) ' Write the vertices v_list = Rhino.MeshVertices(strMesh) For Each v In v_list objStream.WriteLine(CStr(v(0)) & " " & CStr(v(1)) & " " & CStr(v(2))) Next ' Write the faces f_list = Rhino.MeshFaceVertices(strMesh) For Each f In f_list If f(2) = f(3) Then objStream.WriteLine(CStr(3) & " " & CStr(f(0)) & " " & CStr(f(1)) & " " & CStr(f(2))) Else objStream.WriteLine(CStr(4) & " " & CStr(f(0)) & " " & CStr(f(1)) & " " & CStr(f(2)) & " " & CStr(f(3))) End If Next ' Close the file objStream.Close End Sub ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Rhino.AddStartupScript Rhino.LastLoadedScriptFile Rhino.AddAlias "ExportOff", "_NoEcho _-RunScript (ExportOff)" ``` -------------------------------------------------------------------------------- # Extend Curve Source: https://developer.rhino3d.com/en/samples/cpp/extend-curve/ Demonstrates how to extend a curve by a line, arc or smooth extension until it intersects a collection of objects. ```cpp CRhinoCommand::result CCommandTest::RunCommand( const CRhinoCommandContext& context ) { CRhinoGetObject gc; gc.SetCommandPrompt( L"Select line to extend" ); gc.SetGeometryFilter( CRhinoGetObject::curve_object ); gc.GetObjects( 1, 1 ); if( gc.CommandResult() != CRhinoCommand::success ) return gc.CommandResult(); const CRhinoObjRef& objref = gc.Object(0); const ON_Curve* pC = ON_Curve::Cast( objref.Geometry() ); if( !pC ) return CRhinoCommand::failure; int side = 0; // start of curve ON_3dPoint pt; if( objref.SelectionPoint(pt) ) { ON_3dPoint p0 = pC->PointAtStart(); ON_3dPoint p1 = pC->PointAtEnd(); if( (p0-pt).Length() > (p1-pt).Length() ) side = 1; // end of curve } CRhinoGetObject go; go.SetCommandPrompt( L"Select boundary surfaces" ); go.SetGeometryFilter( CRhinoGetObject::surface_object | CRhinoGetObject::polysrf_object ); go.EnablePreSelect( false ); go.EnableDeselectAllBeforePostSelect( false ); go.GetObjects( 1, 0 ); if( go.CommandResult() != CRhinoCommand::success ) return go.CommandResult(); int object_count = go.ObjectCount(); ON_SimpleArray geom( object_count ); for( int i = 0; i < object_count; i++ ) { const CRhinoObject* obj = go.Object(i).Object(); if( obj ) geom.Append( obj->Geometry() ); } if( geom.Count() <= 0 ) return CRhinoCommand::cancel; ON_Curve* crv = pC->DuplicateCurve(); if( !crv ) return CRhinoCommand::failure; // Do the curve extension bool rc = RhinoExtendCurve(crv, CRhinoExtend::Line, side, geom); if( rc ) { // CRhinoDoc::ReplaceObject() will copy our curve // so, we will need to clean up when finshed. context.m_doc.ReplaceObject( objref, *crv ); context.m_doc.Redraw(); } // Clean up or leak... delete crv; crv = 0; return CRhinoCommand::success; } ``` -------------------------------------------------------------------------------- # Extend Curve Source: https://developer.rhino3d.com/en/samples/rhinocommon/extend-curve/ Demonstrates how to extend a curve object to user-selected boundary objects. ```cs partial class Examples { public static Result ExtendCurve(RhinoDoc doc) { ObjRef[] boundary_obj_refs; var rc = RhinoGet.GetMultipleObjects("Select boundary objects", false, ObjectType.AnyObject, out boundary_obj_refs); if (rc != Result.Success) return rc; if (boundary_obj_refs == null || boundary_obj_refs.Length == 0) return Result.Nothing; var gc = new GetObject(); gc.SetCommandPrompt("Select curve to extend"); gc.GeometryFilter = ObjectType.Curve; gc.GeometryAttributeFilter = GeometryAttributeFilter.OpenCurve; gc.DisablePreSelect (); gc.Get(); if (gc.CommandResult() != Result.Success) return gc.CommandResult(); var curve_obj_ref = gc.Object(0); var curve = curve_obj_ref.Curve(); if (curve == null) return Result.Failure; double t; if (!curve.ClosestPoint(curve_obj_ref.SelectionPoint(), out t)) return Result.Failure; var curve_end = t <= curve.Domain.Mid ? CurveEnd.Start : CurveEnd.End; var geometry = boundary_obj_refs.Select(obj=> obj.Geometry()); var extended_curve = curve.Extend(curve_end, CurveExtensionStyle.Line, geometry); if (extended_curve != null && extended_curve.IsValid) { if (!doc.Objects.Replace(curve_obj_ref.ObjectId, extended_curve)) return Result.Failure; doc.Views.Redraw(); } else { RhinoApp.WriteLine("No boundary object was intersected so curve not extended"); return Result.Nothing; } return Result.Success; } } ``` ```python from Rhino import * from Rhino.Geometry import * from Rhino.DocObjects import * from Rhino.Commands import * from Rhino.Input import * from Rhino.Input.Custom import * from scriptcontext import doc def RunCommand(): rc, boundary_obj_refs = RhinoGet.GetMultipleObjects("Select boundary objects", False, ObjectType.AnyObject) if rc != Result.Success: return rc if boundary_obj_refs == None or boundary_obj_refs.Length == 0: return Result.Nothing gc = GetObject() gc.SetCommandPrompt("Select curve to extend") gc.GeometryFilter = ObjectType.Curve gc.GeometryAttributeFilter = GeometryAttributeFilter.OpenCurve gc.Get() if gc.CommandResult() != Result.Success: return gc.CommandResult() curve_obj_ref = gc.Object(0) curve = curve_obj_ref.Curve() if curve == None: return Result.Failure b, t = curve.ClosestPoint(curve_obj_ref.SelectionPoint()) if not b: return Result.Failure curve_end = CurveEnd.Start if t <= curve.Domain.Mid else CurveEnd.End geometry = [obj.Geometry() for obj in boundary_obj_refs] extended_curve = curve.Extend(curve_end, CurveExtensionStyle.Line, geometry) if extended_curve != None and extended_curve.IsValid: if not doc.Objects.Replace(curve_obj_ref.ObjectId, extended_curve): return Result.Failure doc.Views.Redraw() return Result.Success else: RhinoApp.WriteLine("No boundary object was intersected so curve not extended") return Result.Nothing if __name__ == "__main__": RunCommand() ``` -------------------------------------------------------------------------------- # Extend Surface Source: https://developer.rhino3d.com/en/samples/cpp/extend-surface/ Demonstrates how to use RhinoExtendSurface() to extend a surface object. ```cpp CRhinoCommand::result CCommandTest::RunCommand( const CRhinoCommandContext& context ) { CRhinoGetObject go; go.SetCommandPrompt( L"Select edge of surface to extend" ); go.SetGeometryFilter(CRhinoGetObject::edge_object); go.SetGeometryAttributeFilter( CRhinoGetObject::edge_curve ); go.GetObjects( 1, 1 ); if( go.CommandResult() != CRhinoCommand::success ) return go.CommandResult(); const CRhinoObjRef& objref = go.Object(0); const ON_Surface* srf = objref.Surface(); if( !srf ) { RhinoApp().Print( L"Unable to extend polysurfaces.\n" ); return CRhinoCommand::nothing; } const ON_Brep* brep = objref.Brep(); const ON_BrepFace* face = objref.Face(); if( !brep | !face | face->m_face_index < 0 ) return CRhinoCommand::failure; if( !brep->IsSurface() ) { RhinoApp().Print( L"Unable to extend trimmed surfaces.\n" ); return CRhinoCommand::nothing; } const ON_BrepTrim* trim = objref.Trim(); if( !trim ) return CRhinoCommand::failure; ON_Surface::ISO edge_index( trim->m_iso ); int dir = edge_index % 2; if( srf->IsClosed(1-dir) ) { RhinoApp().Print(L"Unable to extend surface at seam.\n" ); return CRhinoCommand::nothing; } if( edge_index < ON_Surface::W_iso | edge_index > ON_Surface::N_iso ) { RhinoApp().Print( L"Selected edge must be an underlying surface edge.\n" ); return CRhinoCommand::nothing; } ON_Surface* myface = srf->DuplicateSurface(); if( !myface ) return CRhinoCommand::failure; bool rc = RhinoExtendSurface( myface, edge_index, 5.0, true); if( rc ) { ON_Brep* mybrep = new ON_Brep(); mybrep->Create( myface ); CRhinoBrepObject* obj = new CRhinoBrepObject(); obj->SetBrep( mybrep ); context.m_doc.ReplaceObject( CRhinoObjRef(objref.Object()), obj ); context.m_doc.Redraw(); } return CRhinoCommand::success; } ``` -------------------------------------------------------------------------------- # Extend Surface Source: https://developer.rhino3d.com/en/samples/rhinocommon/extend-surface/ Demonstrates how to extend a user-specified edge of a surface. ```cs partial class Examples { public static Result ExtendSurface(RhinoDoc doc) { var go = new Rhino.Input.Custom.GetObject(); go.SetCommandPrompt("Select edge of surface to extend"); go.GeometryFilter = ObjectType.EdgeFilter; go.GeometryAttributeFilter = GeometryAttributeFilter.EdgeCurve; go.Get(); if (go.CommandResult() != Result.Success) return go.CommandResult(); var obj_ref = go.Object(0); var surface = obj_ref.Surface(); if (surface == null) { RhinoApp.WriteLine("Unable to extend polysurfaces."); return Result.Failure; } var brep = obj_ref.Brep(); var face = obj_ref.Face(); if (brep == null || face == null) return Result.Failure; if (face.FaceIndex < 0) return Result.Failure; if( !brep.IsSurface) { RhinoApp.WriteLine("Unable to extend trimmed surfaces."); return Result.Nothing; } var curve = obj_ref.Curve(); var trim = obj_ref.Trim(); if (trim == null) return Result.Failure; if (trim.TrimType == BrepTrimType.Seam) { RhinoApp.WriteLine("Unable to extend surface at seam."); return Result.Nothing; } var extended_surface = surface.Extend(trim.IsoStatus, 5.0, true); if (extended_surface != null) { var mybrep = Brep.CreateFromSurface(extended_surface); doc.Objects.Replace(obj_ref.ObjectId, mybrep); doc.Views.Redraw(); } return Result.Success; } } ``` ```python import Rhino from Rhino.Input.Custom import GeometryAttributeFilter from Rhino.DocObjects import ObjectType from Rhino.Commands import Result from Rhino.Geometry import Brep, BrepTrimType from scriptcontext import doc def RunCommand(): go = Rhino.Input.Custom.GetObject() go.SetCommandPrompt("Select edge of surface to extend") go.GeometryFilter = ObjectType.EdgeFilter go.GeometryAttributeFilter = GeometryAttributeFilter.EdgeCurve go.Get() if go.CommandResult() != Result.Success: return go.CommandResult() obj_ref = go.Object(0) surface = obj_ref.Surface() if surface == None: print("Unable to extend polysurfaces.") return Result.Failure brep = obj_ref.Brep() face = obj_ref.Face() if brep == None or face == None: return Result.Failure if face.FaceIndex < 0: return Result.Failure if not brep.IsSurface: print("Unable to extend trimmed surfaces.") return Result.Nothing curve = obj_ref.Curve() trim = obj_ref.Trim() if trim == None: return Result.Failure if trim.TrimType == BrepTrimType.Seam: print("Unable to extend surface at seam.") return Result.Nothing extended_surface = surface.Extend(trim.IsoStatus, 5.0, True) if extended_surface != None: mybrep = Brep.CreateFromSurface(extended_surface) doc.Objects.Replace(obj_ref.ObjectId, mybrep) doc.Views.Redraw() return Result.Success if __name__ == "__main__": RunCommand() ``` -------------------------------------------------------------------------------- # Extract Interpolated Curve Construction Points Source: https://developer.rhino3d.com/en/samples/rhinoscript/extract-interpolated-curve-construction-points/ Demonstrates how to reverse engineer an interpolated curve using RhinoScript. ```vbnet Function ExtractInterpCrvPoints(curve) ' local variables Dim points(), knots, i ' default result ExtractInterpCrvPoints = Null ' verify the curve If Not IsNull(curve) And Rhino.IsCurve(curve) Then ' verify the degree of the curve If Rhino.CurveDegree(curve) = 3 Then ' get the curve's knots knots = Rhino.CurveKnots(curve) ' verify the curve's knots If IsArray(knots) Then ' evaluate the curve at each knot value ReDim points(UBound(knots)) For i = 0 To UBound(knots) points(i) = Rhino.EvaluateCurve(curve, knots(i)) Next ' cull any duplicate points ExtractInterpCrvPoints = Rhino.CullDuplicatePoints(points) End If End If End If End Function ``` -------------------------------------------------------------------------------- # Extract Isocurve Source: https://developer.rhino3d.com/en/samples/rhinocommon/extract-isocurve/ Demonstrates how to extract the isoparametric curves from selected surfaces. ```cs partial class Examples { public static Result ExtractIsoCurve(RhinoDoc doc) { ObjRef obj_ref; var rc = RhinoGet.GetOneObject("Select surface", false, ObjectType.Surface, out obj_ref); if (rc != Result.Success || obj_ref == null) return rc; var surface = obj_ref.Surface(); var gp = new GetPoint(); gp.SetCommandPrompt("Point on surface"); gp.Constrain(surface, false); var option_toggle = new OptionToggle(false, "U", "V"); gp.AddOptionToggle("Direction", ref option_toggle); Point3d point = Point3d.Unset; while (true) { var grc = gp.Get(); if (grc == GetResult.Option) continue; else if (grc == GetResult.Point) { point = gp.Point(); break; } else return Result.Nothing; } if (point == Point3d.Unset) return Result.Nothing; int direction = option_toggle.CurrentValue ? 1 : 0; // V : U double u_parameter, v_parameter; if (!surface.ClosestPoint(point, out u_parameter, out v_parameter)) return Result.Failure; var iso_curve = surface.IsoCurve(direction, direction == 1 ? u_parameter : v_parameter); if (iso_curve == null) return Result.Failure; doc.Objects.AddCurve(iso_curve); doc.Views.Redraw(); return Result.Success; } } ``` ```python from Rhino import * from Rhino.DocObjects import * from Rhino.Commands import * from Rhino.Input import * from Rhino.Input.Custom import * from Rhino.Geometry import * from scriptcontext import doc def RunCommand(): rc, obj_ref = RhinoGet.GetOneObject("Select surface", False, ObjectType.Surface) if rc != Result.Success or obj_ref == None: return rc surface = obj_ref.Surface() gp = GetPoint() gp.SetCommandPrompt("Point on surface") gp.Constrain(surface, False) option_toggle = OptionToggle(False, "U", "V") gp.AddOptionToggle("Direction", option_toggle) point = Point3d.Unset while True: grc = gp.Get() if grc == GetResult.Option: continue elif grc == GetResult.Point: point = gp.Point() break else: return Result.Nothing if point == Point3d.Unset: return Result.Nothing direction = 1 if option_toggle.CurrentValue else 0 b, u_parameter, v_parameter = surface.ClosestPoint(point) if not b: return Result.Failure iso_curve = surface.IsoCurve(direction, u_parameter if direction == 1 else v_parameter) if iso_curve == None: return Result.Failure doc.Objects.AddCurve(iso_curve) doc.Views.Redraw() return Result.Success if __name__ == "__main__": RunCommand() ``` -------------------------------------------------------------------------------- # Extract Isocurve Intersection Points Source: https://developer.rhino3d.com/en/samples/rhinoscript/extract-isocurve-intersection-points/ Demonstrates how to get the intersection points of a surface's isocurves using RhinoScript. ```vbnet ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' ExtractUVIntersectPts.rvb -- November 2008 ' If this code works, it was written by Dale Fugier. ' If not, I don't know who wrote it. ' Works with Rhino 4.0. ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Option Explicit ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' 'Extracts surface wireframe curves ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Function RhExtractWireframe(sSurface) Dim aResults RhExtractWireframe = Null Call Rhino.SelectObject(sSurface) Call Rhino.Command("_-ExtractWireframe", 0) aResults = Rhino.LastCreatedObjects If IsArray(aResults) Then RhExtractWireframe = aResults Rhino.UnselectObjects(aResults) End If Call Rhino.UnselectObject(sSurface) End Function ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' 'Intersects curves ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Function RhIntersect(aCurves) Dim aResults, aPoints(), i RhIntersect = Null Call Rhino.SelectObjects(aCurves) Call Rhino.Command("_-Intersect", 0) aResults = Rhino.LastCreatedObjects If IsArray(aResults) Then ReDim aPoints(UBound(aResults)) For i = 0 To UBound(aResults) aPoints(i) = Rhino.PointCoordinates(aResults(i)) Next Call Rhino.DeleteObjects(aResults) RhIntersect = aPoints End If Call Rhino.UnselectObjects(aCurves) End Function ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' The one and only ExtractUVIntersectPts subroutine ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Sub ExtractUVIntersectPts Dim sSurface, aCurves, aPoints, aObjects sSurface = Rhino.GetObject("Select surface", 24, True) If IsNull(sSurface) Then Exit Sub Call Rhino.EnableRedraw(False) aCurves = RhExtractWireframe(sSurface) If IsArray(aCurves) Then aPoints = RhIntersect(aCurves) Call Rhino.DeleteObjects(aCurves) If IsArray(aPoints) Then aObjects = Rhino.AddPoints(Rhino.CullDuplicatePoints(aPoints)) Call Rhino.SelectObjects(aObjects) End If End If Call Rhino.EnableRedraw(True) End Sub ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Rhino.AddStartupScript Rhino.LastLoadedScriptFile Rhino.AddAlias "ExtractUVIntersectPts", "_NoEcho _-RunScript (ExtractUVIntersectPts)" ``` -------------------------------------------------------------------------------- # Extract Render Mesh Source: https://developer.rhino3d.com/en/samples/rhinocommon/extract-render-mesh/ Demonstrates how to extract the render mesh from a surface or polysurface. ```cs partial class Examples { public static Rhino.Commands.Result ExtractRenderMesh(Rhino.RhinoDoc doc) { Rhino.DocObjects.ObjRef objRef = null; Rhino.Commands.Result rc = Rhino.Input.RhinoGet.GetOneObject("Select surface or polysurface", false, Rhino.DocObjects.ObjectType.Brep, out objRef); if (rc != Rhino.Commands.Result.Success) return rc; Rhino.DocObjects.RhinoObject obj = objRef.Object(); if (null == obj) return Rhino.Commands.Result.Failure; System.Collections.Generic.List objList = new System.Collections.Generic.List(1); objList.Add(obj); Rhino.DocObjects.ObjRef[] meshObjRefs = Rhino.DocObjects.RhinoObject.GetRenderMeshes(objList, true, false); if (null != meshObjRefs) { for (int i = 0; i < meshObjRefs.Length; i++) { Rhino.DocObjects.ObjRef meshObjRef = meshObjRefs[i]; if (null != meshObjRef) { Rhino.Geometry.Mesh mesh = meshObjRef.Mesh(); if (null != mesh) doc.Objects.AddMesh(mesh); } } doc.Views.Redraw(); } return Rhino.Commands.Result.Success; } } ``` ```python # No Python sample available ``` -------------------------------------------------------------------------------- # Extract Thumbnail Preview Images Source: https://developer.rhino3d.com/en/samples/rhinoscript/extract-thumbnail-preview-images/ Demonstrates how to extract the thumbnail preview image from .3DM files using RhinoScript. ```vbnet ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' BatchExtractThumbnails.rvb -- October 2008 ' If this code works, it was written by Dale Fugier. ' If not, I don't know who wrote it. ' Works with Rhino 4.0. ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Option Explicit ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' BatchExtractThumbnails ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Sub BatchExtractThumbnails() ' Allow the user to interactively pick a folder Dim sFolder : sFolder = Rhino.WorkingFolder sFolder = Rhino.BrowseForFolder(sFolder, "Select folder to process", "Batch Extract Thumbnails" ) If IsNull(sFolder) Then Exit Sub ' Create a file system object Dim oFSO : Set oFSO = CreateObject("Scripting.FileSystemObject") ' Get a folder object based on the selected folder Dim oFolder : Set oFolder = oFSO.GetFolder(sFolder) ' Process the entire folder Call DoThumbnailExtraction(oFSO, oFolder) ' Done Call Rhino.Print("Done!") End Sub ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' DoThumbnailExtraction ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Sub DoThumbnailExtraction(oFSO, oFolder) ' Process all 3dm files in the folder Dim oFile, strOpen, strSave For Each oFile In oFolder.Files If LCase(oFSO.GetExtensionName(oFile.Path)) = "3dm" Then strOpen = LCase(oFile.Path) strSave = LCase(Replace(strOpen, ".3dm", ".jpg", 1, -1, 1)) Call Rhino.Print("Processing " & strOpen & "...") Call Rhino.ExtractPreviewImage(strSave, strOpen) End If Next ' Un-comment the following if you want to recurse this folder 'Dim oSubFolder 'For Each oSubFolder In oFolder.SubFolders ' Call DoThumbnailExtraction(oFSO, oSubFolder) 'Next End Sub ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Rhino.AddStartupScript Rhino.LastLoadedScriptFile Rhino.AddAlias "BatchExtractThumbnails", "_NoEcho _-RunScript (BatchExtractThumbnails)" ``` -------------------------------------------------------------------------------- # Extracting Curve Edit Points Source: https://developer.rhino3d.com/en/guides/cpp/extracting-curve-edit-points/ This brief guide demonstrates how to extract a curve's edit points using C/C++. ## Problem You would like to extract a curve's edit points - the points you see when you run the *EditPtOn* command, but you do not see any methods on `ON_Curve` or `ON_NurbsCurve` to do this. ## Solution Unlike control points, edit points are not part of a NURBS curve's data structure. Rather, they are calculated when needed. The following code demonstrates to get obtain the edit points for a NURBS curve and then create point objects at those locations. ```cpp CRhinoCommand::result CCommandTest::RunCommand( const CRhinoCommandContext& context ) { CRhinoGetObject go; go.SetCommandPrompt( L"Select curve" ); go.SetGeometryFilter( CRhinoGetObject::curve_object ); go.GetObjects( 1, 1 ); if( go.CommandResult() != success ) return go.CommandResult(); const ON_Curve* crv = go.Object(0).Curve(); if( 0 == crv ) return failure; ON_NurbsCurve nc; if( crv->GetNurbForm(nc) ) { // For every control point, we can calculate // a cooresponding edit point. ON_SimpleArray t( nc.CVCount() ); t.SetCount( nc.CVCount() ); if( nc.GetGrevilleAbcissae(t.Array()) ) { int i; for( i = 0; i < t.Count(); i++ ) context.m_doc.AddPointObject( nc.PointAt(t[i]) ); context.m_doc.Redraw(); } } return success; } ``` -------------------------------------------------------------------------------- # Extracting Isoparametric Curves from Surfaces Source: https://developer.rhino3d.com/en/samples/cpp/extract-isoparametric-curves-from-surfaces/ Demonstrates how to extract isoparametric curves from surfaces. ```cpp CRhinoCommand::result CCommandTest::RunCommand( const CRhinoCommandContext& context ) { // Select the surface to extract isocurve CRhinoGetObject go; go.SetCommandPrompt( L"Select surface" ); go.SetGeometryFilter( CRhinoGetObject::surface_object ); go.GetObjects( 1, 1 ); if( go.CommandResult() != success ) return go.CommandResult(); // Validate selection const CRhinoObjRef& ref = go.Object(0); const ON_Surface* srf = ref.Surface(); if( !srf ) return failure; ON_3dPoint pt( ON_UNSET_POINT ); BOOL dir = FALSE; // Pick a point on the surface CRhinoGetPoint gp; gp.SetCommandPrompt( L"Point on surface" ); gp.AddCommandOptionToggle( RHCMDOPTNAME(L"Direction"), RHCMDOPTVALUE(L"U"), RHCMDOPTVALUE(L"V"), dir, &dir ); gp.Constrain( *srf ); for(;;) { CRhinoGet::result res = gp.GetPoint(); if( res == CRhinoGet::point ) { pt = gp.Point(); break; } else if( res == CRhinoGet::option ) continue; else return cancel; } // Get the parameters of the point on // the surface that is closest to pt. double u, v; if( srf->GetClosestPoint(pt, &u, &v) ) { // Get the isoparametric curve. ON_Surface::IsoCurve // allocates memory for the resulting curve that we // will be responsible for. ON_Curve* crv = srf->IsoCurve( dir, dir ? u : v ); if( crv ) { context.m_doc.AddCurveObject( *crv ); // CRhinoDoc::AddCurveObject make a copy of the input curve. // So, we need to delete crv otherwise we will leak memory. delete crv; crv = 0; context.m_doc.Redraw(); } } return success; } ``` -------------------------------------------------------------------------------- # Extracting Thumbnail Preview Images Source: https://developer.rhino3d.com/en/guides/cpp/extracting-thumbnail-preview-images/ This guide demonstrates how to extract the thumbnail preview image from a 3dm file using C/C++. ## Problem You would like to be able to display a 3dm file's thumbnail preview image in a dialog box. ## Solution When Rhino reads a 3dm file, it ignores the thumbnail preview image stored in the file (since it is never used). Thus, if you want to obtain the thumbnail preview image for the current document, or any 3dm file, you will have to read the 3dm file yourself. Fortunately, you only need to read a very small portion of the 3dm file to get the thumbnail preview image. Rhino stores a document's thumbnail preview image as an `ON_WindowsBitmap`, which is just an uncompressed Windows device independent bitmap, or DIB. At the heart of `ON_WindowsBitmap` is simply a Windows `BITMAPINFO` structure. ## Sample The following sample code demonstrates how to read the thumbnail preview image from a 3dm file. ```cpp bool Read3dmThumbnailPreviewImage( const wchar_t* filename, ON_WindowsBitmap& bitmap ) { if( 0 == filename | 0 == filename[0] ) return false; try { FILE* archive_fp = ON::OpenFile( filename, L"rb" ); if( 0 == archive_fp ) return false; ON_BinaryFile archive( ON::read3dm, archive_fp ); // STEP 1: REQUIRED - Read start section int file_version = 0; ON_String strComments; if( !archive.Read3dmStartSection(&file_version, strComments) ) { ON::CloseFile( archive_fp ); return false; } // STEP 2: REQUIRED - Read properties section ON_3dmProperties properties; if( !archive.Read3dmProperties(properties) ) { ON::CloseFile( archive_fp ); return false; } ON::CloseFile( archive_fp ); if( !properties.m_PreviewImage.IsValid() ) return false; bitmap = properties.m_PreviewImage; } catch(...) // Handle all exceptions { return false; } return true; } ``` -------------------------------------------------------------------------------- # Extrude Brep Face Source: https://developer.rhino3d.com/en/samples/rhinocommon/extrude-brep-face/ Demonstrates how to extrude the Brep face from a user-specified surface. ```cs partial class Examples { public static Rhino.Commands.Result ExtrudeBrepFace(Rhino.RhinoDoc doc) { Rhino.Input.Custom.GetObject go0 = new Rhino.Input.Custom.GetObject(); go0.SetCommandPrompt("Select surface to extrude"); go0.GeometryFilter = Rhino.DocObjects.ObjectType.Surface; go0.SubObjectSelect = true; go0.Get(); if (go0.CommandResult() != Rhino.Commands.Result.Success) return go0.CommandResult(); Rhino.Geometry.BrepFace face = go0.Object(0).Face(); if (null == face) return Rhino.Commands.Result.Failure; Rhino.Input.Custom.GetObject go1 = new Rhino.Input.Custom.GetObject(); go1.SetCommandPrompt("Select path curve"); go1.GeometryFilter = Rhino.DocObjects.ObjectType.Curve; go1.SubObjectSelect = true; go1.DeselectAllBeforePostSelect = false; go1.Get(); if (go1.CommandResult() != Rhino.Commands.Result.Success) return go1.CommandResult(); Rhino.Geometry.Curve curve = go1.Object(0).Curve(); if (null == curve) return Rhino.Commands.Result.Failure; Rhino.Geometry.Brep brep = face.CreateExtrusion(curve, true); if (null != brep) { doc.Objects.Add(brep); doc.Views.Redraw(); } return Rhino.Commands.Result.Success; } } ``` ```python # No Python sample available ``` -------------------------------------------------------------------------------- # Failed to Create VBScript Error Source: https://developer.rhino3d.com/en/guides/rhinoscript/failed-to-create-vbscript-error/ This guide discusses the Failed to create instance of VBScript engine error and how to fix it. ## Symptom When you run Rhino, you receive the following error message: **"Failed to create instance of VBScript engine"** ## Cause The RhinoScript plugin included with Rhino uses Microsoft's Visual Basic Script, or VBScript. The "Failed to create instance of VBScript engine" message is a result of RhinoScript's inability to connect with VBScript. This error can be caused by the following: - VBScript is not installed. - VBScript is not properly registered. - Script blocking software is preventing VBScript from running. - Security settings are preventing VBScript from being accessed. ## VBScript is not installed Rhino 3.0, 4.0, and 5 (32-bit) use the 32-bit version of the VBScript dynamic-link library, *vbscript.dll*. On English-language versions of 32-bit Windows, *vbscript.dll* is located here: ``` C:\Windows\System32 ``` On English-language versions of 64-bit Windows, 32-bit *vbscript.dll* is located here: ``` C:\Windows\SysWOW64 ``` Rhino 5 (64-bit) use the 64-bit version of *vbscript.dll*. On English-language versions of 64-bit Windows, *vbscript.dll* is located here: ``` C:\Windows\System32 ``` If you search these folders and you are unable to locate *vbscript.dll*, then you will need to reinstall VBScript. ### Installing VBScript Install the latest Visual Basic Script engine from Microsoft for your Windows version: - [Windows XP](http://www.microsoft.com/downloads/details.aspx?familyid=47809025-D896-482E-A0D6-524E7E844D81) - Windows Vista, 7, 8, 10: No download necessary. The latest VBScript is already included with Windows. **NOTE**: You can also install the latest VBScript by simply downloading and installing the latest version of Microsoft Internet Explorer. ## VBScript is not registered In order for applications to use VBScript, it must be properly registered in the Windows Registry. Rhino 3.0, 4.0, and 5 (32-bit) use the 32-bit version of VBScript. On English-language versions of 32-bit Windows, VBScript is registered here: ``` HKEY_CLASSES_ROOT\CLSID\{B54F3741-5B07-11cf-A4B0-00AA004A55E8} ``` On English-language versions of 64-bit Windows, 32-bit VBScript is registered here: ``` HKEY_CLASSES_ROOT\Wow6432Node\CLSID\{B54F3741-5B07-11cf-A4B0-00AA004A55E8} ``` Rhino 5 (64-bit) use the 64-bit version of VBScript. On English-language versions of 64-bit Windows, VBScript is registered here: ``` HKEY_CLASSES_ROOT\CLSID\{B54F3741-5B07-11cf-A4B0-00AA004A55E8} ``` If, using *REGEDIT.EXE*, you search for these keys and you are unable to locate them, then you will need to reregister VBScript by following the directions below...

WARNING

Modifying the registry incorrectly can have negative consequences on your system's stability and even damage the system. You must be logged in as administrator or logged in as a user with administrator privileges for the computer to execute these steps. 1. Click Start > All Programs > Accessories > Command Prompt. (Windows 7 users: right-click on Command Prompt and select "Run as administrator"). 1. In the command prompt window, change to the folder where *vbscript.dll* resides by typing either `CD C:\Windows\System32` or `CD C:\Windows\SysWOW64` and then press Enter. 1. In the command prompt window, type `REGSVR32 VBSCRIPT.DLL` and then press Enter. 1. A message should appear stating "DllRegisterServer in VBSCRIPT.DLL succeeded". If you see a message with error code 0x80004005, the command window was not opened as an administrator. You might also need to disable anti-virus, personal security, and firewall applications before performing this task. ## Script Blocking Software Many anti-virus, personal security, and firewall applications contain script blocking features that will prevent applications from accessing VBScript. For example, Norton Antivirus has a Script Blocking feature what will disable VBScript. McAfee also has one that is even more destructive. Kaspersky also was found to be blocking VBScripting. If you are running anti-virus, personal security, and firewall applications, you may have to disable their script blocking features. Check your product documentation for details. ### McAfee McAfee anti-virus can cause serious problems with VBScript, even if it is uninstalled. Even after uninstalling McAfee, it may have modified the two registry keys listed above, pointing to place-holder DLLs in the McAfee folders instead of where the two *vbscript.dll* files should be. If you encounter this issue, use PSEXEC as described below to restore full permissions to the Administrator account in order to manuallyy change the keys to point back to the 32 and 64-bit *vbscript.dll* files. There are two keys that tell Windows where VBScript.dll is on the local drive. If McAfee has modified your VBScript installation, these keys will be pointing to the wrong file and locations. It is likely that: ``` [HKEY_CLASSES_ROOT\CLSID{B54F3741-5B07-11cf-A4B0-00AA004A55E8}\InprocServer32] ``` points to: ``` C:\Program Files\McAfee\VirusScan Enterprise\scriptcl.dll ``` which is wrong. This causes VBScript to fail when any application tries to use it. A complete uninstall of McAfee will leave this key unmodified, causing VBScript to fail since this McAfee dll no longer exists. Manually changing the registry key to the windows default: ``` \[HKEY_CLASSES_ROOT\CLSID{B54F3741-5B07-11cf-A4B0-00AA004A55E8}\InprocServer32] ``` to: ``` \\C:\Windows\System32\vbscript.dll\\ ``` fixes the problem for Rhino 5 for Windows (64-bit). For 32-bit Rhino 5 on 64-bit Windows, this key: ``` \HKEY_CLASSES_ROOT\Wow6432Node\CLSID{B54F3741-5B07-11cf-A4B0-00AA004A55E8\InprocServer32}\\ ``` should point to: ``` \\C:\Windows\SysWOW64\vbscript.dll\\ ``` ## Security Settings If *vbscript.dll* is present on your system and the module is properly registered, then you might not have enough rights to read the Windows Registry.

WARNING

Modifying the registry incorrectly can have negative consequences on your system's stability and even damage the system. You must be logged in as administrator or logged in as a user with administrator privileges for the computer to execute these steps. Run *REGEDIT.EXE* and find the appropriate registry key in `HKEY_CLASSES_ROOT`, either: ``` HKEY_CLASSES_ROOT\CLSID\{B54F3741-5B07-11cf-A4B0-00AA004A55E8} ``` or ``` HKEY_CLASSES_ROOT\Wow6432Node\CLSID\{B54F3741-5B07-11cf-A4B0-00AA004A55E8} ``` Right-click on the key and pick Permissions... from the context menu. Users should have the Read permission checked. If this is not the case, then the users permissions have been modified, which is causing RhinoScript to not be able to read this key. Registry permissions issues are often caused by security policies have been pushed down onto the workstations that are members of an Active Directory domain. To set the permissions, navigate to the top of the hive, `HKEY_CLASSES_ROOT`, and try to set the proper permissions from there. In doing this, all keys below this will inherit the permissions (and hopefully the problem will be solved). If you are unable to do this, then you will need to contact your IT department and ask for assistance. -------------------------------------------------------------------------------- # Fibonacci Numbers Source: https://developer.rhino3d.com/en/guides/rhinoscript/fibonacci-numbers/ This guide is a survey of Fibonacci number algorithms in RhinoScript. ## Overview By definition, [Fibonacci numbers](http://en.wikipedia.org/wiki/Fibonacci_number) are a series of numbers where the first two Fibonacci numbers are 0 and 1, and each remaining number is the sum of the previous two. The formula for calculating Fibonacci numbers is: $$F(n) = F(n-1) + F(n-2)$$ with seed values: $$F(0) = 0$$ and $$F(1) = 1$$ There are a number of methods that one can use to calculate these numbers. Here are a few... ## Recursion You can calculate Fibonacci numbers using a recursive function... ```vbnet ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' Compute Fibonacci number using recursion ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Function Fib_1(n) If (n < 2) Then Fib_1 = n Else Fib_1 = Fib_1(n-1) + Fib_1(n-2) End If End Function ``` ...but, it is not always the fastest method. ## Dynamic Iteration One of the reasons the recursive algorithm can be slow is we keep recomputing the same subproblems over and over again. In this iterative approach, we solve each subproblem once and then look up the solution later when we need it instead of repeatedly recomputing it... ```vbnet ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' Compute Fibonacci number using dynamic iteration ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Function Fib_2(n) Dim f, i If (n < 2) Then Fib_2 = n Else ReDim f(n-1) f(0) = 1 f(1) = 1 For i = 2 To n - 1 f(i) = f(i-1) + f(i-2) Next Fib_2 = f(n-1) End If End Function ``` ## Space Complexity Iteration It turns out that the dynamic iteration can be modified to use a much smaller amount of space. Each step through the loop uses only the previous two values of F(n), so instead of storing these values in an array, we can simply use two variables. This requires some swapping around of values so that everything stays in the appropriate places. ```vbnet ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' Compute Fibonacci number using space complexity iteration ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Function Fib_3(n) Dim a, b, c, i If (n < 2) Then Fib_3 = n Else a = 1 b = 1 For i = 2 To n -1 c = a + b a = b b = c Next Fib_3 = b End If End Function ``` ## Binet's Formula Binet's Formula for calculating the nth Fibonacci number is fast because it uses neither recursion nor iteration... ```vbnet ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' Compute Fibonacci number using Binet's formula ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Function Fib_4(n) Fib_4 = Round(((Sqr(5) + 1) / 2) ^ n / Sqr(5)) End Function ``` ## Testing You can use the following test code to benchmark the above functions: ```vbnet ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' Tests the Fibonacci functions ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Sub TestFibonacci Dim s, a, n, i, st, et a = Array("Recursion", "Dynamic", "Space", "Binet") s = Rhino.GetString("Fibonacci algorithm to use",,a) If IsNull(s) Then Exit Sub n = Rhino.GetInteger("Number of iterations", 20, 1, 100) If IsNull(n) Then Exit Sub Select Case s ' Iterative Case "Recursion" st = Timer For i = 0 To n - 1 Rhino.Print Fib_1(i) Next et = Timer ' Recursive Case "Dynamic" st = Timer For i = 0 To n - 1 Rhino.Print Fib_2(i) Next et = Timer ' Space Case "Space" st = Timer For i = 0 To n - 1 Rhino.Print Fib_3(i) Next et = Timer 'Binet Case Else st = Timer For i = 0 To n - 1 Rhino.Print Fib_4(i) Next et = Timer End Select Call Rhino.Print(s & " calculation completed in " & FormatNumber(et-st,3) & " seconds.") End Sub ``` ## Related Topics - [Fibonacci Numbers on Wikipedia](http://en.wikipedia.org/wiki/Fibonacci_number) -------------------------------------------------------------------------------- # Fibonacci Spirals Source: https://developer.rhino3d.com/en/samples/rhinoscript/fibonacci-spirals/ Demonstrates how to create a Fibonacci Spiral with RhinoScript. ```vbnet ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' FibonacciSpiral.rvb -- June 2009 ' If this code works, it was written by Dale Fugier. ' If not, I don't know who wrote it. ' Works with Rhino 4.0. ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Option Explicit Call FibonacciSpiral() ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' Computes Fibonacci Spiral ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Sub FibonacciSpiral() ' Local variables Dim steps, scale, plane, xform Dim origin, pt0, pt1, pt2, pt3 Dim n, cmd ' Get number of Fibonacci numbers to calculate steps = Rhino.GetInteger("Number of steps", 10, 1, 50) If IsNull(steps) Then Exit Sub ' Original origin point origin = Array(0,0,0) ' Process every step... For n = 1 To steps ' Compute Fibonacci number using Binet's formula scale = Round(((Sqr(5) + 1) / 2) ^ n / Sqr(5)) ' Determine x and y axes based on where we are plane = Rhino.WorldXYPlane() xform = Rhino.XformRotation(90.0 * (n Mod 4), plane(3), plane(0)) plane = Rhino.PlaneTransform(plane, xform) ' Calculate arc points pt0 = origin ' Offset pt0 in the xaxis direction by scale pt1 = Rhino.PointAdd(pt0, Rhino.VectorScale(plane(1), scale)) ' Offset pt1 in the yaxis direction by scale pt2 = Rhino.PointAdd(pt1, Rhino.VectorScale(plane(2), scale)) ' Offset origin in the yaxis direction by scale pt3 = Rhino.PointAdd(pt0, Rhino.VectorScale(plane(2), scale)) ' Add a closed polyline Call Rhino.AddPolyline(Array(pt0, pt1, pt2, pt3, pt0)) ' Build a command script that will create an arc from ' start, end, and direction cmd = "_-Arc _StartPoint " & _ Rhino.Pt2Str(pt0) & _ " " & _ Rhino.Pt2Str(pt2) & _ " _Direction " & _ Rhino.Pt2Str(pt1) ' Run the command script to create the arc Call Rhino.Command(cmd, 0) ' Update the origin point origin = pt2 Next End Sub ``` -------------------------------------------------------------------------------- # Fillet Curve Source: https://developer.rhino3d.com/en/samples/cpp/fillet-curve/ Demonstrates how to create a fillet between two curves. ```cpp CRhinoCommand::result CCommandTest::RunCommand( const CRhinoCommandContext& context ) { ON_3dPoint point0( 10.0, 0.0, 0.0 ); ON_3dPoint point1( 10.0, 10.0, 0.0 ); ON_3dPoint point2( 0.0, 10.0, 0.0 ); // Create the line curves to fillet ON_LineCurve curve0( point0, point1 ); ON_LineCurve curve1( point2, point1 ); // Fillet at the end points of the line curves double curve0_t = curve0.Domain().Max(); double curve1_t = curve1.Domain().Max(); // Fillet radius double radius = 1.0; // Do the fillet calculation double t0 = 0.0, t1 = 0.0; ON_Plane plane; if( RhinoGetFilletPoints(curve0, curve1, radius, curve0_t, curve1_t, t0, t1, plane) ) { // Trim back the two line curves ON_Interval domain0( curve0.Domain().Min(), t0 ); curve0.Trim( domain0 ); ON_Interval domain1( curve1.Domain().Min(), t1 ); curve1.Trim( domain1 ); // Compute the fillet curve ON_3dVector radial0 = curve0.PointAt(t0) - plane.Origin(); radial0.Unitize(); ON_3dVector radial1 = curve1.PointAt(t1) - plane.Origin(); radial1.Unitize(); double angle = acos( radial0 * radial1 ); ON_Plane fillet_plane( plane.Origin(), radial0, radial1 ); ON_Arc fillet( fillet_plane, plane.Origin(), radius, angle ); // Add the geometry context.m_doc.AddCurveObject( curve0 ); context.m_doc.AddCurveObject( curve1 ); context.m_doc.AddCurveObject( fillet ); context.m_doc.Redraw(); } return CRhinoCommand::success; } ``` -------------------------------------------------------------------------------- # Fillet Curves Source: https://developer.rhino3d.com/en/samples/rhinocommon/fillet-curves/ Demonstrates how to fillet two curves by a specified radius. ```cs partial class Examples { public static Result FilletCurves(RhinoDoc doc) { var gc1 = new GetObject(); gc1.DisablePreSelect(); gc1.SetCommandPrompt("Select first curve to fillet (close to the end you want to fillet)"); gc1.GeometryFilter = ObjectType.Curve; gc1.GeometryAttributeFilter = GeometryAttributeFilter.OpenCurve; gc1.Get(); if (gc1.CommandResult() != Result.Success) return gc1.CommandResult(); var curve1_obj_ref = gc1.Object(0); var curve1 = curve1_obj_ref.Curve(); if (curve1 == null) return Result.Failure; var curve1_point_near_end = curve1_obj_ref.SelectionPoint(); if (curve1_point_near_end == Point3d.Unset) return Result.Failure; var gc2 = new GetObject(); gc2.DisablePreSelect(); gc2.SetCommandPrompt("Select second curve to fillet (close to the end you want to fillet)"); gc2.GeometryFilter = ObjectType.Curve; gc2.GeometryAttributeFilter = GeometryAttributeFilter.OpenCurve; gc2.Get(); if (gc2.CommandResult() != Result.Success) return gc2.CommandResult(); var curve2_obj_ref = gc2.Object(0); var curve2 = curve2_obj_ref.Curve(); if (curve2 == null) return Result.Failure; var curve2_point_near_end = curve2_obj_ref.SelectionPoint(); if (curve2_point_near_end == Point3d.Unset) return Result.Failure; double radius = 0; var rc = RhinoGet.GetNumber("fillet radius", false, ref radius); if (rc != Result.Success) return rc; var join = false; var trim = true; var arc_extension = true; var fillet-curves = Curve.CreateFilletCurves(curve1, curve1_point_near_end, curve2, curve2_point_near_end, radius, join, trim, arc_extension, doc.ModelAbsoluteTolerance, doc.ModelAngleToleranceDegrees); if (fillet-curves == null /*|| fillet-curves.Length != 3*/) return Result.Failure; foreach(var fillet-curve in fillet-curves) doc.Objects.AddCurve(fillet-curve); doc.Views.Redraw(); return rc; } } ``` ```python from Rhino.Commands import Result from Rhino.Geometry import Point3d, Curve from Rhino.Input import RhinoGet from Rhino.DocObjects import ObjectType from Rhino.Input.Custom import GetObject, GeometryAttributeFilter from scriptcontext import doc def RunCommand(): gc1 = GetObject() gc1.DisablePreSelect() gc1.SetCommandPrompt("Select first curve to fillet (close to the end you want to fillet)") gc1.GeometryFilter = ObjectType.Curve gc1.GeometryAttributeFilter = GeometryAttributeFilter.OpenCurve gc1.Get() if gc1.CommandResult() != Result.Success: return gc1.CommandResult() curve1_obj_ref = gc1.Object(0) curve1 = curve1_obj_ref.Curve() if curve1 == None: return Result.Failure curve1_point_near_end = curve1_obj_ref.SelectionPoint() if curve1_point_near_end == Point3d.Unset: return Result.Failure gc2 = GetObject() gc2.DisablePreSelect() gc2.SetCommandPrompt("Select second curve to fillet (close to the end you want to fillet)") gc2.GeometryFilter = ObjectType.Curve gc2.GeometryAttributeFilter = GeometryAttributeFilter.OpenCurve gc2.Get() if gc2.CommandResult() != Result.Success: return gc2.CommandResult() curve2_obj_ref = gc2.Object(0) curve2 = curve2_obj_ref.Curve() if curve2 == None: return Result.Failure curve2_point_near_end = curve2_obj_ref.SelectionPoint() if curve2_point_near_end == Point3d.Unset: return Result.Failure radius = 0.0 rc, radius = RhinoGet.GetNumber("fillet radius", False, radius) if rc != Result.Success: return rc fillet_curve = Curve.CreateFilletCurves(curve1, curve1_point_near_end, curve2, curve2_point_near_end, radius, True, True, True, doc.ModelAbsoluteTolerance, doc.ModelAngleToleranceDegrees) if fillet_curve == None or fillet_curve.Length != 1: return Result.Failure doc.Objects.AddCurve(fillet_curve[0]) doc.Views.Redraw() return rc if __name__ == "__main__": RunCommand() ``` -------------------------------------------------------------------------------- # Find Closest Curve to Point Source: https://developer.rhino3d.com/en/samples/rhinoscript/find-closest-curve-to-point/ Demonstrates how to find the closest curve to test point using RhinoScript. ```vbnet Sub FindClosestCurve Const rhPoint = 1 Const rhCurve = 4 'Dim arrCurves : arrCurves = Rhino.ObjectsByType(rhCurve) Dim arrCurves: arrCurves = Rhino.GetObjects("Select curves to test", rhCurve) If Not IsArray(arrCurves) Then Exit Sub Dim strPoint : strPoint = Rhino.GetObject("Select test point", rhPoint) If IsNull(strPoint) Then Exit Sub Dim arrPoint : arrPoint = Rhino.PointCoordinates(strPoint) Dim dblDistance : dblDistance = Null Dim strCurve : strCurve = Null Dim dblParameter : dblParameter = Null Dim arrPt : arrPt = Null Dim i, b, t, pt, d For i = 0 To UBound(arrCurves) b = vbFalse t = Rhino.CurveClosestPoint( arrCurves(i), arrPoint ) If Not IsNull(t) Then pt = Rhino.EvaluateCurve( arrCurves(i), t ) If IsArray(pt) Then d = Rhino.Distance(pt, arrPoint) If IsNull(dblDistance) Then b = vbTrue ElseIf (d < dblDistance) Then b = vbTrue End If If (b = vbTrue) Then dblDistance = d strCurve = arrCurves(i) dblParameter = t arrPt = pt End If End If End If Next If Not IsNull(dblDistance) Then Rhino.Print "Closest curve = " & CStr(strCurve) Rhino.Print "Curve parameter = " & CStr(dblParameter) Rhino.Print "Point = " & Rhino.Pt2Str(arrPt) Rhino.Print "Distance = " & CStr(dblDistance) Rhino.SelectObject strCurve Rhino.SelectObject Rhino.AddPoint(arrPt) End If End Sub ``` -------------------------------------------------------------------------------- # Find Curve Parameter At Point Source: https://developer.rhino3d.com/en/samples/rhinocommon/find-curve-parameter-at-point/ Demonstrates how to find the curve parameter given a specific point on the curve. ```cs partial class Examples { public static Result FindCurveParameterAtPoint(RhinoDoc doc) { Rhino.DocObjects.ObjRef objref; var rc = RhinoGet.GetOneObject("Select curve", true, ObjectType.Curve,out objref); if(rc!= Result.Success) return rc; var curve = objref.Curve(); if( curve==null ) return Result.Failure; var gp = new GetPoint(); gp.SetCommandPrompt("Pick a location on the curve"); gp.Constrain(curve, false); gp.Get(); if (gp.CommandResult() != Result.Success) return gp.CommandResult(); var point = gp.Point(); double closest_point_param; if (curve.ClosestPoint(point, out closest_point_param)) { RhinoApp.WriteLine("point: ({0}), parameter: {1}", point, closest_point_param); doc.Objects.AddPoint(point); doc.Views.Redraw(); } return Result.Success; } } ``` ```python import Rhino import scriptcontext import rhinoscriptsyntax as rs def RunCommand(): rc, objref = Rhino.Input.RhinoGet.GetOneObject("Select curve", True, Rhino.DocObjects.ObjectType.Curve) if(rc!= Rhino.Commands.Result.Success): return rc crv = objref.Curve() if( crv == None ): return Rhino.Commands.Result.Failure gp = Rhino.Input.Custom.GetPoint() gp.SetCommandPrompt("Pick a location on the curve") gp.Constrain(crv, False) gp.Get() if (gp.CommandResult() != Rhino.Commands.Result.Success): return gp.CommandResult(); p = gp.Point() b, cp = crv.ClosestPoint(p) if (b): print("point: ({0},{1},{2}), parameter: {3}".format(p.X, p.Y, p.Z, cp)) scriptcontext.doc.Objects.AddPoint(p) scriptcontext.doc.Views.Redraw() return Rhino.Commands.Result.Success if __name__=="__main__": RunCommand() ``` -------------------------------------------------------------------------------- # Find Objects by Name Source: https://developer.rhino3d.com/en/samples/rhinocommon/find-objects-by-name/ Demonstrates how to find objects by their name or label. ```cs partial class Examples { public static Rhino.Commands.Result FindObjectsByName(Rhino.RhinoDoc doc) { const string name = "abc"; Rhino.DocObjects.ObjectEnumeratorSettings settings = new Rhino.DocObjects.ObjectEnumeratorSettings(); settings.NameFilter = name; System.Collections.Generic.List ids = new System.Collections.Generic.List(); foreach (Rhino.DocObjects.RhinoObject rhObj in doc.Objects.GetObjectList(settings)) ids.Add(rhObj.Id); if (ids.Count == 0) { Rhino.RhinoApp.WriteLine("No objects with the name " + name); return Rhino.Commands.Result.Failure; } Rhino.RhinoApp.WriteLine("Found {0} objects", ids.Count); foreach (Guid id in ids) Rhino.RhinoApp.WriteLine(" {0}", id); return Rhino.Commands.Result.Success; } } ``` ```python import Rhino import scriptcontext import System.Guid def FindObjectsByName(): name = "abc" settings = Rhino.DocObjects.ObjectEnumeratorSettings() settings.NameFilter = name ids = [rhobj.Id for rhobj in scriptcontext.doc.Objects.GetObjectList(settings)] if not ids: print("No objects with the name", name) return Rhino.Commands.Result.Failure else: print("Found", len(ids), "objects") for id in ids: print(" ", id) return Rhino.Commands.Result.Success if __name__ == "__main__": FindObjectsByName() ``` -------------------------------------------------------------------------------- # Find point on curve at distance Source: https://developer.rhino3d.com/en/samples/rhinocommon/find-point-on-curve-at-distance/ Demonstrates how find a point on a curve given a specified length from the start of the curve. ```cs partial class Examples { public static Rhino.Commands.Result ArcLengthPoint(Rhino.RhinoDoc doc) { Rhino.DocObjects.ObjRef objref; Rhino.Commands.Result rc = Rhino.Input.RhinoGet.GetOneObject("Select curve", true, Rhino.DocObjects.ObjectType.Curve,out objref); if(rc!= Rhino.Commands.Result.Success) return rc; Rhino.Geometry.Curve crv = objref.Curve(); if( crv==null ) return Rhino.Commands.Result.Failure; double crv_length = crv.GetLength(); double length = 0; rc = Rhino.Input.RhinoGet.GetNumber("Length from start", true, ref length, 0, crv_length); if(rc!= Rhino.Commands.Result.Success) return rc; Rhino.Geometry.Point3d pt = crv.PointAtLength(length); if (pt.IsValid) { doc.Objects.AddPoint(pt); doc.Views.Redraw(); } return Rhino.Commands.Result.Success; } } ``` ```python import Rhino import scriptcontext def ArcLengthPoint(): rc, objref = Rhino.Input.RhinoGet.GetOneObject("Select curve", True, Rhino.DocObjects.ObjectType.Curve) if rc!=Rhino.Commands.Result.Success: return rc crv = objref.Curve() if not crv: return Rhino.Commands.Result.Failure crv_length = crv.GetLength() length = 0 rc, length = Rhino.Input.RhinoGet.GetNumber("Length from start", True, length, 0, crv_length) if rc!=Rhino.Commands.Result.Success: return rc pt = crv.PointAtLength(length) if pt.IsValid: scriptcontext.doc.Objects.AddPoint(pt) scriptcontext.doc.Views.Redraw() return Rhino.Commands.Result.Success if __name__=="__main__": ArcLengthPoint() ``` -------------------------------------------------------------------------------- # Finding an Annotation object's font Source: https://developer.rhino3d.com/en/guides/opennurbs/finding-annotation-font/ This guide demonstrates how to get an Annotation object's font using openNURBS. ## Question There are many changes to ```ONX_Model``` in openNURBS. In prior versions, I was getting the font and its id information for ```ON_Leader2``` with: ```cpp ONX_Model model = ... const ON_Leader2* leader = ... const ON_Font& font = model.m_font_table[leader->Index()]; ``` How can I do this with the current openNURBS? ## Answer In openNURBS, the the dimension style, or ```ON_DimStyle```, specifies all appearance properties like the text font, size, and alignment, arrow head shape, and so on. To obtain the font used by an Annotation object, such as an ```ON_Leader```, you just need to query the object's effective dimension style. ## Example The following example code can be used to get the ```ON_Font``` used by an ```ON_Leader``` using the openNURBS toolkit: ```cpp ONX_Model model = ... // Create a model geometry interator ONX_ModelComponentIterator it(model, ON_ModelComponent::Type::ModelGeometry); const ON_ModelComponent* model_component = nullptr; for (model_component = it.FirstComponent(); nullptr != model_component; model_component = it.NextComponent()) { // Get the model geometry const ON_ModelGeometryComponent* model_geometry = ON_ModelGeometryComponent::Cast(model_component); if (nullptr == model_geometry) continue; // Try getting an annotation leader const ON_Leader* leader = ON_Leader::Cast(model_geometry->Geometry(nullptr)); if (nullptr == leader) continue; // Get the parent dimension style const ON_ModelComponentReference& parent_dim_style_ref = model.DimensionStyleFromId(leader->DimensionStyleId()); const ON_DimStyle* parent_dim_style = ON_DimStyle::Cast(parent_dim_style_ref.ModelComponent()); if (nullptr == parent_dim_style) continue; // Get the effective dimension style const ON_DimStyle& dim_style = leader->DimensionStyle(*parent_dim_style); // Get the font const ON_Font& font = dim_style.Font(); // TODO... } ``` -------------------------------------------------------------------------------- # Finding Duplicate Strings Source: https://developer.rhino3d.com/en/guides/rhinoscript/finding-duplicate-strings/ This guide demonstrates finding duplicate string using RhinoScript. ## Problem Imagine you have an array of strings which contains duplicates. RhinoScript has a method to cull the duplicate strings. But rather than cull them, one would like to find them with a routine that will return the indices of the duplicate items. Better yet, a routine that would return sets of indices, with each set containing the indices of a particular string would solve the problem. For example, if an array contained "Curve", "Surface", "curve", "surface", we would like to have an array containing [0,2] and [1,3] returned. ## Solution VBScript's `Dictionary` is a useful tool for storing associative data, or data in the form of (key, item) pairs. In the problem outlined above, you could use a Dictionary to track each string and the indices where is appears in the array. In other words, use a Dictionary to store (string, indices) pairs. To store the indices, you are going to need an array. But creating and resizing VBScript arrays is always a challenge. So, you might consider using a .NET's `ArrayList` object. A .NET `ArrayList` is a COM-enabled object, which means it can be used in VBScript. The following sample function demonstrates how you can use a `Dictionary` of strings and .NET `ArrayList` objects to find the indices of duplicate string items in an array. ```vbnet Function FindDuplicateStrings(arrStrings, blnCase) ' Local variables Dim objDict, strKey, objItem, arrItems Dim i, j, nCount Dim arrResults() ' Default return value FindDuplicateStrings = Null ' Create a dictionary object and set it's compare mode Set objDict = CreateObject("Scripting.Dictionary") If (blnCase = True) Then objDict.CompareMode = vbBinaryCompare Else objDict.CompareMode = vbTextCompare End If ' Process input strings. If the string is not in the dictionary, ' then add it and add it's index to the ArrayList. Otherwise, ' just add it's index to the dictionary item's existing ArrayList. For i = 0 To UBound(arrStrings) strKey = arrStrings(i) If Not objDict.Exists(strKey) Then objDict.Add strKey, CreateObject("System.Collections.ArrayList") End If objDict(strKey).Add(i) Next ' Find all of the dictionary items that have more than one index. ' Add those arrays to our result array nCount = 0 arrItems = objDict.Items For Each objItem In arrItems If (objItem.Count > 1) Then ReDim Preserve arrResults(nCount) arrResults(nCount) = objItem.ToArray() nCount = nCount + 1 End If Next ' Done! FindDuplicateStrings = arrResults End Function ``` Here is an example of how to use the above function: ```vbnet Sub TestFindDuplicateStrings Dim arrStrings, arrResults, arrItem, nItem, i arrStrings = Array("Curve" , _ "Surface", _ "Mesh", _ "Point", _ "Surface", _ "Curve", _ "Curve") arrResults = FindDuplicateStrings(arrStrings, False) If IsArray(arrResults) Then Call Rhino.Print("Duplicate Sets = " & CStr(UBound(arrResults) + 1)) For i = 0 To UBound(arrResults) Call Rhino.Print("Set = " & CStr(i + 1)) arrItem = arrResults(i) For Each nItem In arrItem Call Rhino.Print("Item " & CStr(nItem) & " = " & arrStrings(nItem)) Next Next End If End Sub ``` ## Related Topics - [Array Utilities](/guides/rhinoscript/array-utilities) - [Sorting VBScript Arrays with .NET](/guides/rhinoscript/sorting-vbs-arrays-with-net) - [VBScript Dictionaries](/guides/rhinoscript/vbscript-dictionaries) -------------------------------------------------------------------------------- # Finding Perfect Squares Source: https://developer.rhino3d.com/en/guides/rhinoscript/finding-perfect-squares/ This guide demonstrates how to determine if an integer is a perfect square using RhinoScript. ## Problem In mathematics, a perfect square is an integer that is the square of an integer; in other words, it is the product of some integer with itself. For example, 9 is a perfect square, since it can be written as 3 × 3. How can one determine whether or not an integer is a perfect square in RhinoScript? ## Solution Here is an example of a function that determines whether or not a number is a perfect square: ```vbnet Function IsPerfectSquare(n) Dim h, t IsPerfectSquare = False ' default return value h = n And &HF ' last hexadecimal "digit" If (h > 9) Then Exit Function ' return immediately in 6 cases out of 16 If (h <> 2 And h <> 3 And h <> 5 And h <> 6 And h <> 7 And h <> 8) Then t = Int(Rhino.Floor(Sqr(n)+0.5)) If (t*t = n) Then IsPerfectSquare = True End If End Function ``` You can test the above function as follows: ```vbnet For i = 0 To 60^2 If IsPerfectSquare(i) Then Call Rhino.Print(CStr(i) & "^2 = " & CStr(i^2)) End If Next ``` -------------------------------------------------------------------------------- # Finding Points on Curves at Arc Length Distances Source: https://developer.rhino3d.com/en/guides/cpp/finding-points-on-curves-at-arc-length-distances/ This guide demonstrates how to find points that are a specified distance from the start of curves using C/C++. ## Problem For a given length from the beginning of a curve, you would like to get the curve's parameter at this point. ## Solution The two functions on `ON_Curve` that are useful for determining the parameter of the point on a curve that is a prescribed arc length distance from the start of a curve are: - `ON_Curve::GetNormalizedArcLengthPoint` - `ON_Curve::GetNormalizedArcLengthPoints` To use these functions, calculate a normalized arc length parameter. That is, a parameter on the curve where 0.0 = the start of the curve, 0.5 = the midpoint of the curve, and 1.0 = the end of the curve. **NOTE**: To determine the parameter of the point on a curve that is a prescribed arc length distance from the end of a curve, just reverse the curve before calling one of the above curve members. ## Sample The following code sample demonstrates how to use these functions: ```cpp CRhinoCommand::result CCommandTest::RunCommand( const CRhinoCommandContext& context ) { CRhinoGetObject go; go.SetCommandPrompt( L"Select curve" ); go.SetGeometryFilter( CRhinoGetObject::curve_object ); go.GetObjects( 1, 1 ); if( go.CommandResult() != success ) return go.CommandResult(); const CRhinoObjRef& obj_ref = go.Object(0); const ON_Curve* crv = obj_ref.Curve(); if( 0 == crv ) return failure; double crv_length = 0.0; if( !crv->GetLength(&crv_length) ) return failure; CRhinoGetNumber gn; gn.SetCommandPrompt( L"Length from start" ); gn.SetLowerLimit( 0.0, TRUE ); gn.SetUpperLimit( crv_length, TRUE ); gn.GetNumber(); if( gn.CommandResult() != success ) return gn.CommandResult(); // Cook up a normalized arc length parameter, // where 0.0 <= s <= 1.0. double length = fabs( gn.Number() ); double s = 0.0; if( length == 0.0 ) s = 0.0; else if( length == crv_length ) s = 1.0; else s = length / crv_length; // Get the parameter of the point on the curve that is a // prescribed arc length from the start of the curve. double t = 0.0; if( crv->GetNormalizedArcLengthPoint(s, &t) ) { ON_3dPoint pt = crv->PointAt( t ); context.m_doc.AddPointObject( pt ); context.m_doc.Redraw(); } return success; } ``` -------------------------------------------------------------------------------- # Finding Rhino's Installation Folder Source: https://developer.rhino3d.com/en/guides/cpp/finding-rhino-installation-folder/ This guide discusses how to find Rhino's installation folder. ## Problem You are putting together an installer for your Rhino plugin. You would like to know how you can, programatically, get Rhino's installation folder. ## Solution ### Rhino 8, 7, and 6 If you are looking for Rhino 8, 7, or 6, then you can find the location of Rhino's installation folder by looking in the Windows Registry in this location: ```text Hive: HKEY_LOCAL_MACHINE Key: SOFTWARE\McNeel\Rhinoceros\\Install Name: InstallPath Type: REG_SZ ``` If you are looking for Rhino 8, for example, replace `` with `8.0`. ### Rhino 5 If you are looking for Rhino 5 64-bit, then you can find the location of Rhino's installation folder by looking in the Windows Registry in this location: ```text Hive: HKEY_LOCAL_MACHINE Key: SOFTWARE\McNeel\Rhinoceros\5.0x64\Install Name: InstallPath Type: REG_SZ ``` If you are looking for Rhino 5 32-bit, then you can find the location of Rhino's installation folder by looking in the Windows Registry in this location: ```text Hive: HKEY_LOCAL_MACHINE Key: SOFTWARE\WOW6432Node\McNeel\Rhinoceros\5.0\Install Name: InstallPath Type: REG_SZ ``` -------------------------------------------------------------------------------- # Finding the Parameter of a Curve at a Point Source: https://developer.rhino3d.com/en/guides/cpp/finding-parameter-of-curve-at-point/ This brief guide demonstrates how to find the parameter of a curve at a given 3D point using C/C++. ## Overview In general, to find the parameter of a point on a curve that is closest to a test point, use `ON_Curve::GetClosestPoint()`. See *opennurbs_curve.h* for more information. ## Sample The following sample code demonstrates how to find the parameter of a curve at a point. The code demonstrates how to select a curve object, and how to pick a point on a curve. For more information on the `CRhinoObjRef` class, see *rhinoSdkObject.h*. ```cpp CRhinoCommand::result CCommandTest::RunCommand( const CRhinoCommandContext& context ) { CRhinoGetObject go; go.SetCommandPrompt( L"Select curve" ); go.SetGeometryFilter( CRhinoGetObject::curve_object ); go.GetObjects( 1, 1 ); if( go.CommandResult() != CRhinoCommand::success ) return go.CommandResult(); const CRhinoObjRef& objref = go.Object( 0 ); const ON_Curve* crv = objref.Curve(); if( !crv ) return CRhinoCommand::failure; CRhinoGetPoint gp; gp.SetCommandPrompt( L"Pick a location on the curve" ); gp.Constrain( *crv ); // constrain to curve gp.GetPoint(); if( gp.CommandResult() != CRhinoCommand::success ) return gp.CommandResult(); ON_3dPoint pt = gp.Point(); double t = 0.0; if( crv->GetClosestPoint(pt, &t) ) RhinoApp().Print( L"Curve parameter at (%f,%f,%f) is %g.\n", pt.x, pt.y, pt.z, t ); return CRhinoCommand::success; } ``` It is possible to save a step by examining the `CRhinoObjRef` class. The class return information on the picking operation that just occurred, including the object that was picked, the point that the user picked, and in this case, the parameter of the curve that was closest to the picked point. ```cpp CRhinoCommand::result CCommandTest::RunCommand( const CRhinoCommandContext& context ) { CRhinoGetObject go; go.SetCommandPrompt( L"Select curve" ); go.SetGeometryFilter( CRhinoGetObject::curve_object ); go.GetObjects( 1, 1 ); if( go.CommandResult() != CRhinoCommand::success ) return go.CommandResult(); const CRhinoObjRef& objref = go.Object( 0 ); ON_3dPoint pt; objref.SelectionPoint( pt ) double t = 0.0; const ON_Curve* crv = objref.CurveParameter( &t ); if( crv ) RhinoApp().Print( L"Curve parameter at (%f,%f,%f) is %g.\n", pt.x, pt.y, pt.z, t ); return CRhinoCommand::success; } ``` -------------------------------------------------------------------------------- # Flamingo Legacy Plant Browser Source: https://developer.rhino3d.com/en/samples/rhinoscript/flamingo-legacy-plant-browser/ Demonstrates how to display the Flamingo nXt legacy plant browser using RhinoScript. ```vbnet Option Explicit Call Main() Sub Main() Dim objPlugIn, plant On Error Resume Next Set objPlugIn = Rhino.GetPluginObject("8008880f-8d13-4b2d-92b0-727e12878a4c") If Err Then MsgBox Err.Description Exit Sub End If plant = objPlugIn.ModalFlamingo2PlantBrowser("", "", "") If Not IsNull(plant) Then Rhino.Print("Library(" & plant(0) & ") Folder(" & plant(1) & ") Plant(" & plant(2) & ")") End If Set objPlugIn = Nothing End Sub ``` -------------------------------------------------------------------------------- # Flamingo Object Mapping Properties Source: https://developer.rhino3d.com/en/samples/rhinoscript/flamingo-object-mapping-properties/ Demonstrates how to set Flamingo nXt mapping properties for an object using RhinoScript. ```vbnet Option Explicit Call Main() Sub Main() Dim objPlugIn, strObject, scale, xscale, yscale, zscale On Error Resume Next Set objPlugIn = Rhino.GetPluginObject("8008880f-8d13-4b2d-92b0-727e12878a4c") If Err Then MsgBox Err.Description Exit Sub End If strObject = Rhino.GetObject("Select object") If Not IsNull(strObject) And Not strObject = "" Then scale = objPlugIn.GetMappingScale(strObject) xscale = 1.0 yscale = 1.0 zscale = 1.0 If Not IsNull(scale) Then xscale = CDbl(scale(0)) yscale = CDbl(scale(1)) zScale = CDbl(scale(2)) End If xscale = Rhino.GetReal("X-Scale", xscale) If Not IsNull(xscale) Then yscale = Rhino.GetReal("Y-Scale", yscale) If Not IsNull(xscale) Then zscale = Rhino.GetReal("Z-Scale", zscale) If Not IsNull(xscale) Then If objPlugIn.SetMappingScale(strObject, xscale, yscale, zscale) Then Rhino.Print("Object mapping scale set to: x-scale:" & xscale & " y-scale: " & yscale & " z-scale: " & zscale) Else Rhino.Print("Error setting objects mapping scale") End If End If End If End If End If Set objPlugIn = Nothing End Sub ``` -------------------------------------------------------------------------------- # FPU Issues Source: https://developer.rhino3d.com/en/guides/cpp/fpu-issues/ This guide discusses math errors and floating point unit issues. ## Problem When your plugin tries to perform a certain calculation, you get the following text in Visual Studio's output window: ``` [[developer:opennurbs:home|opennurbs]] ERROR # 2 .\rhino3MathErrorHandling.cpp:154 Serious math library or floating point errors occurred. [[developer:opennurbs:home|opennurbs]] ERROR # 3 .\opennurbs_plus_fpu.cpp:289 ON_FPU_BeforeSloppyCall - fpu STAT is already dirty. See source comment for next steps. ``` ## Workaround These two opennurbs errors are most likely related. Sometimes it is impossible to avoid calling code that performs invalid floating point operations or rudely changes the FPU control settings. We have found dozens of cases in Windows core DLLs, 3rd party DLLs, OpenGL drivers, VBScript, and the .NET JIT compiler where the FPU CTRL setting is changed or floating point exceptions are generated. When these cases are discovered, we bracket the code that is abusing the FPU with: ```cpp ON_FPU_BeforeSloppyCall(); // call that abuses the FPU ON_FPU_AfterSloppyCall(); ``` In doing this, we don't lose any information about exceptions in our own code and we don't get pestered about exceptions we can't do anything about. (Note, if you are calling something that may run the .NET JIT, then use `ON_FPU_AfterDotNetJITUse` instead of `ON_FPU_AfterSloppyCall`). Also, the following error occurs when a serious divide by zero, overflow, or invalid operation happened sometime before the call to `ON_FPU_BeforeSloppyCall`: ``` [[developer:opennurbs:home|opennurbs]] ERROR # 3 .\opennurbs_plus_fpu.cpp:289 ON_FPU_BeforeSloppyCall - fpu STAT is already dirty. See source comment for next steps. ``` These are easy to find. Start Rhino, run `TestErrorCheck` and set `CrashOnFPUException=Yes`. Then do whatever it is that made the FPU dirty. You will crash on the line that is dividing by zero, overflowing, or performing the invalid operation. -------------------------------------------------------------------------------- # Frequently Asked Questions Source: https://developer.rhino3d.com/en/guides/compute/compute-faq/ This guide is a list of Frequently Asked Questions (FAQ) for Rhino.Compute. ## General ### What is Rhino.Compute? In its simplest definition, Rhino.Compute is a web server that can perform geometry calculations using Rhino's geometry library (i.e. [Rhino.Inside](https://www.rhino3d.com/features/rhino-inside/)). It works by receiving requests over the web (using [HTTP](https://en.wikipedia.org/wiki/HTTP) or [HTTPS](https://en.wikipedia.org/wiki/HTTPS)), processing them with Rhino’s geometry engine, and then sending back the results. Any application that can send web requests can interact with Rhino.Compute, making it easy to integrate into different workflows. ### Why would I want to use it? Rhino.Compute lets you use Rhino’s powerful tools and features outside of the regular Rhino or Grasshopper interface. It’s great for teams, because you can run Grasshopper definitions or Rhino functions from one central place, making collaboration easier. It can also handle multiple tasks at the same time (in parallel), helping speed up large projects. And for longer calculations, it runs in the background (asynchronously) — so it won’t freeze or slow down your main interface. ### Will it work on macOS? No. Rhino.Compute is dependent on [Rhino.Inside](https://www.rhino3d.com/features/rhino-inside/) which allows Rhino and Grasshopper to run *inside* other 64-bit applications. Currently, Rhino.Inside is only compatible with Windows and Linux (tested on Ubuntu 24.04 and AmazonLinux 2023) operating systems. ### Does it cost money? The short answer is: it depends on *where* you run Rhino.Compute. If you're using it on a regular Windows computer, like your personal PC, there’s no extra cost. Rhino.Compute will check for your standard Rhino licence on startup (note: [Rhino evaluation versions](https://www.rhino3d.com/download/) will work just fine). But if you're running it on a Windows or Linux Server (such as a virtual machine in the cloud), you’ll be charged based on our [Core-Hour Billing](../core-hour-billing/) model. ### How is it different from Hops? [Hops](../what-is-hops/) is a Grasshopper plugin (available from the [package manager](../../yak/what-is-yak/)) that makes it easy to solve Grasshopper definitions using Rhino.Compute. When you install Hops, it automatically starts an instance of Rhino.Compute in the background whenever you open Grasshopper. You can then use the Hops component to send your Grasshopper definition to Rhino.Compute, which solves it and sends the results back. In this context, Hops is the “client” and Rhino.Compute is the “server.” ### Can I make my own interface to work with Rhino.Compute? Yes. As mentioned earlier, any app that can send web requests can work with Rhino.Compute. To make things easier, we offer three libraries you can use depending on the language you prefer: 1. [Python Rhino.Compute Library](https://pypi.org/project/compute-rhino3d/) 1. [Javascript Rhino.Compute Library](https://www.npmjs.com/package/compute-rhino3d) 1. [.NET (C#) Rhino.Compute Library](https://github.com/mcneel/compute.rhino3d/blob/8.x/src/compute.geometry/RhinoCompute.cs) We also have step-by-step guides to help you get started with the libraries above: 1. [Calling Compute with Python](../compute-python-getting-started/) 1. [Calling Compute with Javascript](../compute-javascript-getting-started/) 1. [Calling Compute with .NET](../compute-net-getting-started/) Finally, we have a [GitHub repository](https://github.com/mcneel/rhino-developer-samples/tree/8/compute) with lots of sample projects and examples that show how to use Rhino.Compute for different geometry tasks. ### Can I use Rhino.Compute in a production environment? Yes. We offer a [step-by-step guide](../deploy-to-iis/) to help you set up Rhino.Compute in a production environment, such as on a virtual machine (VM). The setup is straightforward—just run a simple PowerShell script, and Rhino.Compute will be up and running, ready to handle requests. ## Troubleshooting We’ve worked hard to make Rhino.Compute as easy to use as possible. But sometimes things don’t go as planned. This section is here to help you troubleshoot and solve any issues you might run into. ### Where can I find the log files? If you are running Rhino.Compute with Hops, you will want to make sure that the Rhino.Compute console application is visible whenever it starts up. To do this, follow the following steps: 1. Launch Grasshopper 1. Click on **File -> Preferences** to open the Grasshopper Settings dialog 1. Click on the **Solver** tab in the left-hand menu 1. **Uncheck** the Hide Rhino.Compute Console Window menu item 1. Restart Rhino and Grasshopper At this point, you should see a new application appear in your task bar (if you are using Windows) when you start Grasshopper. This is the Rhino.Compute console application. Useful troubleshooting information will be displayed here as you are working with Rhino.Compute. If you are running Rhino.Compute on a VM, then the log files are saved as text files. New log files will be created daily. By default, the log files are saved under the following locations: - Windows ```cs C:\inetpub\wwwroot\aspnet_client\system_web\4_0_30319\rhino.compute\logs\ ``` - Linux ```cs /var/log/rhino-compute ``` ### Can I enable verbose logging information? By default, Rhino.Compute outputs a minimal set of information to the logs. To enable more verbose logging you will need to create an environment variable on your machine. 1. Right-click on the **This PC** icon in the File Explorer, then select **Properties** or **System** from the Control Panel 1. In the System Properties window, click on **Advanced System Settings** 1. In the **Advanced** tab, click on **Environment Variables** 1. Click on the **New** button to create a new system variable 1. In the **Variable name** input, type "RHINO_COMPUTE_DEBUG" 1. In the **Variable value** input, type "true" 1. Click OK to save the system environment variable, and then click OK again to close the Environment Variables dialog box and the System Properties window. ### What do I do if I get an HRESULT E_FAIL Error? If Rhino.Compute returns the following error: ```cs Application startup exception System.Runtime.InteropServices.COMException (0x80004005): Error HRESULT E_FAIL has been returned from a call to a COM component. at Rhino.Runtime.InProcess.RhinoCore.InternalStartup(Int32 argc, String[] argv, StartupInfo& info, IntPtr hostWnd) ``` This is most likely caused because Rhino.Compute can not find a valid license on startup. If you are running a Windows Server based machine (i.e. on a virtual machine) then follow these steps: 1. Go to the [Licenses Portal](https://www.rhino3d.com/licenses?_forceEmpty=true) and **select the team** that you set up with Core-hour billing. 1. Click **Manage Team -> Manage Core-Hour Billing**. 1. Click **Action -> Get Auth Token** to get a token. 1. Create a new environment variable with the name `RHINO_TOKEN` and use the token as the value. Since the token is too long for Windows' Environment Variables dialog, it's easiest to do this via a PowerShell command. ```ps [System.Environment]::SetEnvironmentVariable('RHINO_TOKEN', 'your token here', 'Machine') ``` From now on, when you start Rhino on this machine it will use your core-hour billing team. Warning! Your core-hour billing token allows anyone with it to charge your team at will. Do NOT share this token with anyone. ### How do I find detailed usage statistics about my core-hour billing license? 1. Go to the [Licenses Portal](https://www.rhino3d.com/licenses?_forceEmpty=true) and **select the team** that you set up with Core-hour billing. 1. You should then see a table with a list of available McNeel products. **Click on the name of the product (i.e. Rhino 8)** that you wish to inspect. 1. A new page will be opened which contains some live usage statistics. **Click on the blue button labeled "View Historical Usage"** at the bottom of this page. 1. Another new page will be generated which allows you to set custom time ranges and inspect the historical usage of your core-hour billing license. You may even click on the blue button at the bottom of this page to download a comma-separated value list (CSV) of your usage statistics. ### I'm getting a 401 Error message. What does that mean? If Rhino.Compute gives you a 401 error, it means the request wasn’t authorized. This usually happens when the request is missing an API key. Rhino.Compute checks for this key—which is saved as an environment variable on the machine it's running on—to make sure only approved clients can connect. If you are using Hops, you can set the API key in the preferences section. 1. Launch Grasshopper 1. Click on **File -> Preferences** to open the Grasshopper Settings dialog 1. Click on the **Solver** tab in the left-hand menu 1. **Enter the API key** in the input dialog 1. Restart Rhino and Grasshopper If you're sending a web request to Rhino.Compute using a different method, make sure to include a key/value pair in the header of the request. The key should be named "RhinoComputeKey", and its value must match the API key set on the machine running Rhino.Compute. ### What does a 500 error code mean? If you receive a response from Rhino.Compute containing a 500 error code, this means that the server is malfunctioning and is unable to process the request correctly. If this happens, you may need to try to run Rhino.Compute in debug mode to get further information as to why the server is failing. [Follow this guide](../development/) to learn how to run Rhino.Compute in debug mode. ### What do I do if I get a timeout exception? If Rhino.Compute returns the following error: ```cs fail: Microsoft.AspNetCore.Server.Kestrel[13] Connection id "...", Request id "...": An unhandled exception was thrown by the application. System.Threading.Tasks.TaskCanceledException: The request was canceled due to the configured HttpClient.Timeout of 100 seconds elapsing. ``` An HTTP request timeout occurs when a client (like Hops) doesn't receive a response from a server (like Rhino.Compute) within a specified amount of time. Thus there are two timeout values that we should be aware of: 1) the client timeout and 2) the server timeout. The timeout setting you see in the Hops preferences controls the client-side timeout. The default value is 100 seconds, but this can be extended to alleviate this error. The server-side timeout is managed separately — it's set by an environment variable on the machine running the server. To set the environment variable follow these steps: 1. Right-click on the **This PC** icon in the File Explorer, then select **Properties** or **System** from the Control Panel 1. In the System Properties window, click on **Advanced System Settings** 1. In the **Advanced** tab, click on **Environment Variables** 1. Click on the **New** button to create a new system variable 1. In the **Variable name** input, type "RHINO_COMPUTE_TIMEOUT" 1. In the **Variable value** input, type the **number of seconds** that you want to use as your server-side timeout value 1. Click OK to save the system environment variable, and then click OK again to close the Environment Variables dialog box and the System Properties window. ### I'm getting an error that says the request body is too large. What do I do? If Rhino.Compute returns the following error: ```cs fail: Microsoft.AspNetCore.Server.Kestrel[13] Connection id "...", Request id "...": An unhandled exception was thrown by the application. Microsoft.AspNetCore.Server.Kestrel.Core.BadHttpRequestException: Request body too large. ``` This error may occur if you are trying to send a large amount of data to Rhino.Compute in the body of a request. The default limit for the size of a request is approximately 50mb. To increase this limit, follow these steps: 1. Right-click on the **This PC** icon in the File Explorer, then select **Properties** or **System** from the Control Panel 1. In the System Properties window, click on **Advanced System Settings** 1. In the **Advanced** tab, click on **Environment Variables** 1. Click on the **New** button to create a new system variable 1. In the **Variable name** input, type "RHINO_COMPUTE_MAX_REQUEST_SIZE" 1. In the **Variable value** input, type in the **maximum number of bytes** allowed in a HTTP request body. The default value is 52,428,800 bytes (approximately 50mb). Increase this value to allow larger requests. 1. Click OK to save the system environment variable, and then click OK again to close the Environment Variables dialog box and the System Properties window. -------------------------------------------------------------------------------- # Furthest Z on Surface given X Y Source: https://developer.rhino3d.com/en/samples/rhinocommon/furthest-z-on-surface-given-x-y/ Demonstrates how to determine the furthest Z on surface given the X Y coordinates. ```cs partial class Examples { public static Result FurthestZOnSurfaceGivenXY(RhinoDoc doc) { #region user input // select a surface var gs = new GetObject(); gs.SetCommandPrompt("select surface"); gs.GeometryFilter = ObjectType.Surface; gs.DisablePreSelect(); gs.SubObjectSelect = false; gs.Get(); if (gs.CommandResult() != Result.Success) return gs.CommandResult(); // get the brep var brep = gs.Object(0).Brep(); if (brep == null) return Result.Failure; // get X and Y double x = 0.0, y = 0.0; var rc = RhinoGet.GetNumber("value of X coordinate", true, ref x); if (rc != Result.Success) return rc; rc = RhinoGet.GetNumber("value of Y coordinate", true, ref y); if (rc != Result.Success) return rc; #endregion // an earlier version of this sample used a curve-brep intersection to find Z //var maxZ = maxZIntersectionMethod(brep, x, y, doc.ModelAbsoluteTolerance); // projecting points is another way to find Z var max_z = MaxZProjectionMethod(brep, x, y, doc.ModelAbsoluteTolerance); if (max_z != null) { RhinoApp.WriteLine("Maximum surface Z coordinate at X={0}, Y={1} is {2}", x, y, max_z); doc.Objects.AddPoint(new Point3d(x, y, max_z.Value)); doc.Views.Redraw(); } else RhinoApp.WriteLine("no maximum surface Z coordinate at X={0}, Y={1} found.", x, y); return Result.Success; } private static double? MaxZProjectionMethod(Brep brep, double x, double y, double tolerance) { double? max_z = null; var breps = new List {brep}; var points = new List {new Point3d(x, y, 0)}; // grab all the points projected in Z dir. Aggregate finds furthest Z from XY plane try { max_z = (from pt in Intersection.ProjectPointsToBreps(breps, points, new Vector3d(0, 0, 1), tolerance) select pt.Z) // Here you might be tempted to use .Max() to get the largest Z value but that doesn't work because // Z might be negative. This custom aggregate returns the max Z independant of the sign. If it had a name // it could be MaxAbs() .Aggregate((z1, z2) => Math.Abs(z1) > Math.Abs(z2) ? z1 : z2); } catch (InvalidOperationException) {/*Sequence contains no elements*/} return max_z; } private static double? MaxZIntersectionMethod(Brep brep, double x, double y, double tolerance) { double? max_z = null; var bbox = brep.GetBoundingBox(true); var max_dist_from_xy = (from corner in bbox.GetCorners() select corner.Z) // furthest Z from XY plane. // Here you might be tempted to use .Max() to get the largest Z value but that doesn't work because // Z might be negative. This custom aggregate returns the max Z independant of the sign. If it had a name // it could be MaxAbs() .Aggregate((z1, z2) => Math.Abs(z1) > Math.Abs(z2) ? z1 : z2); // multiply distance by 2 to make sure line intersects completely var line_curve = new LineCurve(new Point3d(x, y, 0), new Point3d(x, y, max_dist_from_xy*2)); Curve[] overlap_curves; Point3d[] inter_points; if (Intersection.CurveBrep(line_curve, brep, tolerance, out overlap_curves, out inter_points)) { if (overlap_curves.Length > 0 || inter_points.Length > 0) { // grab all the points resulting frem the intersection. // 1st set: points from overlapping curves, // 2nd set: points when there was no overlap // .Aggregate: furthest Z from XY plane. max_z = (from c in overlap_curves select Math.Abs(c.PointAtEnd.Z) > Math.Abs(c.PointAtStart.Z) ? c.PointAtEnd.Z : c.PointAtStart.Z) .Union (from p in inter_points select p.Z) // Here you might be tempted to use .Max() to get the largest Z value but that doesn't work because // Z might be negative. This custom aggregate returns the max Z independant of the sign. If it had a name // it could be MaxAbs() .Aggregate((z1, z2) => Math.Abs(z1) > Math.Abs(z2) ? z1 : z2); } } return max_z; } } ``` ```python import rhinoscriptsyntax as rs from Rhino.Geometry import Intersect, Point3d, Vector3d from scriptcontext import doc def RunCommand(): # select a surface srfid = rs.GetObject("select serface", rs.filter.surface | rs.filter.polysurface) if not srfid: return # get the brep brep = rs.coercebrep(srfid) if not brep: return x = rs.GetReal("value of x", 0) y = rs.GetReal("value of y", 0) pts = [(abs(point.Z), point.Z) for point in Intersect.Intersection.ProjectPointsToBreps([brep], [Point3d(x, y, 0)], Vector3d(0, 0, 1), doc.ModelAbsoluteTolerance)] pts.sort(reverse=True) if pts == []: print("no Z for given X, Y") else: rs.AddPoint(Point3d(x, y, pts[0][1])) if __name__ == "__main__": RunCommand() ``` -------------------------------------------------------------------------------- # Generating Random Numbers Source: https://developer.rhino3d.com/en/guides/rhinoscript/generating-random-numbers/ This guide demonstrates how to generate random numbers that fall within a specified range using RhinoScript. ## Standard VBScript The following code sample demonstrates how to generate random integers that fall within a specified range. First, the standard VBScript method: ```vbnet Sub Test1() nLow = 1 nHigh = 100 Randomize Rhino.Print Int((nHigh - nLow + 1) * Rnd + nLow) End Sub ``` **NOTE**: since the `Int` function always rounds down, we add one to the difference between the limits. ## Using .NET Now, a little help from the .NET Framework: ```vbnet Sub Test2() nLow = 1 nHigh = 100 Set objRandom = CreateObject("System.Random") Rhino.Print objRandom.Next_2(nLow, nHigh) End Sub ``` ## Floating Point If you want a random floating point number that falls within a specified range, you can use the technique below... ```vbnet Sub Test3() dblLow = 10.0 dblHigh = 50.0 Randomize Rhino.Print Rnd * (dblHigh - dblLow) + dblLow End Sub ``` -------------------------------------------------------------------------------- # Get and Set Active Viewport Source: https://developer.rhino3d.com/en/samples/cpp/get-set-active-viewport/ Demonstrates how to get and set the active view. ```cpp CRhinoCommand::result CCommandTest::RunCommand( const CRhinoCommandContext& context ) { // Get the active view CRhinoView* active_view = ::RhinoApp().ActiveView(); if( !active_view ) return CRhinoCommand::failure; // Get the name of the active view ON_wString active_view_name = active_view->Viewport().Name(); // Get a list of all views ON_SimpleArray view_list; context.m_doc.GetViewList( view_list ); // Build a list of view names ON_ClassArray view_names; int i; for( i = 0; i < view_list.Count(); i++ ) { CRhinoView* view = view_list[i]; if( view && view != active_view ) view_names.Append( view->Viewport().Name() ); } // Prompt the user for the view to set active CRhinoGetString gs; gs.SetCommandPrompt( L"Name of view to set active" ); gs.AcceptNothing(); gs.SetDefaultString( active_view_name ); // Add view names as pickable command options for( i = 0; i < view_names.Count(); i++ ) gs.AddCommandOption( RHCMDOPTNAME(view_names[i]) ); // Do the getstring CRhinoGet::result res = gs.GetString(); // User pressed ESC if( res == CRhinoGet::cancel ) return CRhinoCommand::cancel; // User pressed ENTER if( res == CRhinoGet::nothing ) return CRhinoCommand::nothing; ON_wString new_view_name; // New typed a string if( res == CRhinoGet::string ) new_view_name = gs.String(); // User clicked or typed an option if( res == CRhinoGet::option ) { const CRhinoCommandOption* option = gs.Option(); if( !option ) return CRhinoCommand::failure; int option_index = option->m_option_index; if( option_index < 1 && option_index > view_names.Count() ) return CRhinoCommand::failure; new_view_name = view_names[option_index-1]; } // Compare view names if( new_view_name.CompareNoCase(active_view_name) == 0 ) return CRhinoCommand::nothing; // Find the specified view by name active_view = 0; for( i = 0; i < view_list.Count(); i++ ) { CRhinoView* view = view_list[i]; if( view && view != active_view ) { if( new_view_name.CompareNoCase(view->Viewport().Name()) == 0 ) { active_view = view; break; } } } // Set the new active view if( active_view ) ::RhinoApp().SetActiveView( active_view ); else ::RhinoApp().Print( L"The \"%s\" view was not found.\n", new_view_name ); return CRhinoCommand::success; } ``` -------------------------------------------------------------------------------- # Get and Set the Active View Source: https://developer.rhino3d.com/en/samples/rhinocommon/get-and-set-the-active-view/ Demonstrates how to determine and set the active view in Rhino. ```cs partial class Examples { public static Result SetActiveView(RhinoDoc doc) { // view and view names var active_view_name = doc.Views.ActiveView.ActiveViewport.Name; var non_active_views = doc.Views .Where(v => v.ActiveViewport.Name != active_view_name) .ToDictionary(v => v.ActiveViewport.Name, v => v); // get name of view to set active var gs = new GetString(); gs.SetCommandPrompt("Name of view to set active"); gs.AcceptNothing(true); gs.SetDefaultString(active_view_name); foreach (var view_name in non_active_views.Keys) gs.AddOption(view_name); var result = gs.Get(); if (gs.CommandResult() != Result.Success) return gs.CommandResult(); var selected_view_name = result == GetResult.Option ? gs.Option().EnglishName : gs.StringResult(); if (selected_view_name != active_view_name) if (non_active_views.ContainsKey(selected_view_name)) doc.Views.ActiveView = non_active_views[selected_view_name]; else RhinoApp.WriteLine("\"{0}\" is not a view name", selected_view_name); return Rhino.Commands.Result.Success; } } ``` ```python from Rhino.Commands import Result from Rhino.Input.Custom import GetString from scriptcontext import doc def RunCommand(): # view and view names active_view_name = doc.Views.ActiveView.ActiveViewport.Name non_active_views = [(view.ActiveViewport.Name, view) for view in doc.Views if view.ActiveViewport.Name != active_view_name] # get name of view to set active gs = GetString() gs.SetCommandPrompt("Name of view to set active") gs.AcceptNothing(True) gs.SetDefaultString(active_view_name) for view_name, _ in non_active_views: gs.AddOption(view_name) result = gs.Get() if gs.CommandResult() != Result.Success: return gs.CommandResult() selected_view_name = "{0}".format(gs.StringResult()) if gs.Option() != None: selected_view_name = gs.Option().EnglishName if selected_view_name != active_view_name: if selected_view_name in [seq[0] for seq in non_active_views]: doc.Views.ActiveView = [seq[1] for seq in non_active_views if seq[0] == selected_view_name][0] else: print("\"{0}\" is not a view name".format(selected_view_name)) return Result.Success if __name__ == "__main__": RunCommand() ``` -------------------------------------------------------------------------------- # Get Angle Source: https://developer.rhino3d.com/en/samples/rhinocommon/get-angle/ Demonstrates how to interactively pick an angle given a base point and two reference points. ```cs partial class Examples { public static Result GetAngle(RhinoDoc doc) { var gp = new GetPoint(); gp.SetCommandPrompt("Base point"); gp.Get(); if (gp.CommandResult() != Result.Success) return gp.CommandResult(); var base_point = gp.Point(); gp.SetCommandPrompt("First reference point"); gp.DrawLineFromPoint(base_point, true); gp.Get(); if (gp.CommandResult() != Result.Success) return gp.CommandResult(); var first_point = gp.Point(); double angle_radians; var rc = RhinoGet.GetAngle("Second reference point", base_point, first_point, 0, out angle_radians); if (rc == Result.Success) RhinoApp.WriteLine("Angle = {0} degrees", RhinoMath.ToDegrees(angle_radians)); return rc; } } ``` ```python from Rhino import RhinoMath from Rhino.Commands import Result from Rhino.Input import RhinoGet from Rhino.Input.Custom import GetPoint def RunCommand(): gp = GetPoint() gp.SetCommandPrompt("Base point") gp.Get() if gp.CommandResult() != Result.Success: return gp.CommandResult() base_point = gp.Point() gp.SetCommandPrompt("First reference point") gp.DrawLineFromPoint(base_point, True) gp.Get() if gp.CommandResult() != Result.Success: return gp.CommandResult() first_point = gp.Point() rc, angle_radians = RhinoGet.GetAngle("Second reference point", base_point, first_point, 0) if rc == Result.Success: print("Angle = {0} degrees".format(RhinoMath.ToDegrees(angle_radians))) return rc if __name__ == "__main__": RunCommand() ``` -------------------------------------------------------------------------------- # Get Brep Face Vertices Source: https://developer.rhino3d.com/en/samples/cpp/get-brep-face-vertices/ Demonstrates how to get the vertices of a Brep face. ```cpp CRhinoCommand::result CCommandTest::RunCommand( const CRhinoCommandContext& context ) { CRhinoGetObject go; go.SetCommandPrompt( L"Select surface" ); go.SetGeometryFilter( CRhinoGetObject::surface_object ); go.EnableSubObjectSelect(); go.GetObjects( 1, 1 ); if( go.CommandResult() != CRhinoCommand::success ) return go.CommandResult(); const ON_BrepFace* face = go.Object(0).Face(); if( 0 == face ) return CRhinoCommand::failure; const ON_Brep* brep = face->Brep(); if( 0 == brep ) return CRhinoCommand::failure; ON_SimpleArray vertices; int i = 0; const ON_BrepLoop* loop = face->OuterLoop(); if( loop ) { for( i = 0; i < loop->TrimCount(); i++ ) { const ON_BrepTrim* trim = loop->Trim( i ); if( trim ) { const ON_BrepVertex* vertex = trim->Vertex( 0 ); if( vertex ) vertices.Append( vertex->m_vertex_index ); vertex = trim->Vertex( 1 ); if( vertex ) vertices.Append( vertex->m_vertex_index ); } } } if( 0 == vertices.Count() ) return CRhinoCommand::failure; // sort vertices.QuickSort( &ON_CompareIncreasing ); // cull int vi = -1; for( i = vertices.Count() - 1; i >= 0; i-- ) { if( vertices[i] == vi ) vertices.Remove(i); else vi = vertices[i]; } // Add for( i = 0; i < vertices.Count(); i++ ) context.m_doc.AddPointObject( brep->m_V[vertices[i]].Point() ); context.m_doc.Redraw(); return CRhinoCommand::success; } ``` -------------------------------------------------------------------------------- # Get Flamingo Material Source: https://developer.rhino3d.com/en/samples/rhinoscript/get-flamingo-material/ Demonstrates how get an objects Flamingo nXt material assignment using RhinoScript. ```vbnet Option Explicit Call Main() Sub Main() Dim objPlugIn, strObject, strMaterial On Error Resume Next Set objPlugIn = Rhino.GetPluginObject("8008880f-8d13-4b2d-92b0-727e12878a4c") If Err Then MsgBox Err.Description Exit Sub End If strObject = Rhino.GetObject("Select object") If Not IsNull(strObject) And Not strObject = "" Then strMaterial = objPlugIn.GetMaterialId(strObject) If Not IsNull(strMaterial) And Not strMaterial = "" Then Rhino.Print("Object assigned to Flamingo nXt material ID: " + strMaterial + " Name: " + objPlugIn.GetMaterialName(strMaterial)) Else Rhino.Print("Object does not have a Flamingo nXt material assigned") End If End If Set objPlugIn = Nothing End Sub ``` -------------------------------------------------------------------------------- # Get Mapping Information From Object Source: https://developer.rhino3d.com/en/samples/rhinoscript/get-mapping-information-from-object/ Demonstrates how to get Flamingo nXt mapping information from an object using RhinoScript. ```vbnet Option Explicit Call Main() Sub Main() Dim objPlugIn, strObject, origin On Error Resume Next Set objPlugIn = Rhino.GetPluginObject("8008880f-8d13-4b2d-92b0-727e12878a4c") If Err Then MsgBox Err.Description Exit Sub End If strObject = Rhino.GetObject("Select object") If Not IsNull(strObject) And Not strObject = "" Then origin = objPlugIn.GetMappingOrigin(strObject) If IsNull(origin) Then Rhino.Print("Object has no mapping data") Else Rhino.Print("Object mapping origin: " & Rhino.Pt2Str(origin)) End If End If Set objPlugIn = Nothing End Sub ``` -------------------------------------------------------------------------------- # Get Material List Source: https://developer.rhino3d.com/en/samples/rhinoscript/get-flamingo-material-list/ Demonstrates how to get a the Flamingo nXt material list from the current document using RhinoScript. ```vbnet Option Explicit Call Main() Sub Main() Dim objPlugIn, materials, i, count, material On Error Resume Next Set objPlugIn = Rhino.GetPluginObject("8008880f-8d13-4b2d-92b0-727e12878a4c") If Err Then MsgBox Err.Description Exit Sub End If materials = objPlugIn.GetMaterials() If IsNull(materials) Then Rhino.Print("Error getting Flamingo nXt material list") Else count = UBound(materials) Rhino.Print("==============================================================================") Rhino.Print("Flamingo nXt Material List") Rhino.Print("------------------------------------------------------------------------------") If count < 0 Then Rhino.Print("No Flamingo materials in the current document") End If For i = 0 to count Set material = materials(i) If IsObject(material) Then Rhino.Print("Material " & (i + 1) & ": ID: " & material.Id & " Name: " & material.Name) End If Next End If Set material = Nothing Set objPlugIn = Nothing End Sub ``` -------------------------------------------------------------------------------- # Get Multiple With Options Source: https://developer.rhino3d.com/en/samples/rhinocommon/get-multiple-with-options/ Demonstrates how to get multiple objects in Rhino command-line options. ```cs partial class Examples { public static Rhino.Commands.Result GetMultipleWithOptions(Rhino.RhinoDoc doc) { const Rhino.DocObjects.ObjectType geometryFilter = Rhino.DocObjects.ObjectType.Surface | Rhino.DocObjects.ObjectType.PolysrfFilter | Rhino.DocObjects.ObjectType.Mesh; int integer1 = 300; int integer2 = 300; Rhino.Input.Custom.OptionInteger optionInteger1 = new Rhino.Input.Custom.OptionInteger(integer1, 200, 900); Rhino.Input.Custom.OptionInteger optionInteger2 = new Rhino.Input.Custom.OptionInteger(integer2, 200, 900); Rhino.Input.Custom.GetObject go = new Rhino.Input.Custom.GetObject(); go.SetCommandPrompt("Select surfaces, polysurfaces, or meshes"); go.GeometryFilter = geometryFilter; go.AddOptionInteger("Option1", ref optionInteger1); go.AddOptionInteger("Option2", ref optionInteger2); go.GroupSelect = true; go.SubObjectSelect = false; go.EnableClearObjectsOnEntry(false); go.EnableUnselectObjectsOnExit(false); go.DeselectAllBeforePostSelect = false; bool bHavePreselectedObjects = false; for (; ; ) { Rhino.Input.GetResult res = go.GetMultiple(1, 0); if (res == Rhino.Input.GetResult.Option) { go.EnablePreSelect(false, true); continue; } else if (res != Rhino.Input.GetResult.Object) return Rhino.Commands.Result.Cancel; if (go.ObjectsWerePreselected) { bHavePreselectedObjects = true; go.EnablePreSelect(false, true); continue; } break; } if (bHavePreselectedObjects) { // Normally when command finishes, pre-selected objects will remain // selected, when and post-selected objects will be unselected. // With this sample, it is possible to have a combination of // pre-selected and post-selected objects. To make sure everything // "looks the same", unselect everything before finishing the command. for (int i = 0; i < go.ObjectCount; i++) { Rhino.DocObjects.RhinoObject rhinoObject = go.Object(i).Object(); if (null != rhinoObject) rhinoObject.Select(false); } doc.Views.Redraw(); } int objectCount = go.ObjectCount; integer1 = optionInteger1.CurrentValue; integer2 = optionInteger2.CurrentValue; Rhino.RhinoApp.WriteLine("Select object count = {0}", objectCount); Rhino.RhinoApp.WriteLine("Value of integer1 = {0}", integer1); Rhino.RhinoApp.WriteLine("Value of integer2 = {0}", integer2); return Rhino.Commands.Result.Success; } } ``` ```python import Rhino import scriptcontext def GetMultipleWithOptions(): geometryFilter = Rhino.DocObjects.ObjectType.Surface | Rhino.DocObjects.ObjectType.PolysrfFilter | Rhino.DocObjects.ObjectType.Mesh integer1 = 300 integer2 = 300 optionInteger1 = Rhino.Input.Custom.OptionInteger(integer1, 200, 900) optionInteger2 = Rhino.Input.Custom.OptionInteger(integer2, 200, 900) go = Rhino.Input.Custom.GetObject() go.SetCommandPrompt("Select surfaces, polysurfaces, or meshes") go.GeometryFilter = geometryFilter go.AddOptionInteger("Option1", optionInteger1) go.AddOptionInteger("Option2", optionInteger2) go.GroupSelect = True go.SubObjectSelect = False go.EnableClearObjectsOnEntry(False) go.EnableUnselectObjectsOnExit(False) go.DeselectAllBeforePostSelect = False bHavePreselectedObjects = False while True: res = go.GetMultiple(1, 0) if res == Rhino.Input.GetResult.Option: go.EnablePreSelect(False, True) continue elif res != Rhino.Input.GetResult.Object: return Rhino.Commands.Result.Cancel if go.ObjectsWerePreselected: bHavePreselectedObjects = True go.EnablePreSelect(False, True) continue break if bHavePreselectedObjects: # Normally, pre-selected objects will remain selected, when a # command finishes, and post-selected objects will be unselected. # This this way of picking, it is possible to have a combination # of pre-selected and post-selected. So, to make sure everything # "looks the same", lets unselect everything before finishing # the command. for i in range(0, go.ObjectCount): rhinoObject = go.Object(i).Object() if not rhinoObject is None: rhinoObject.Select(False) scriptcontext.doc.Views.Redraw() objectCount = go.ObjectCount integer1 = optionInteger1.CurrentValue integer2 = optionInteger2.CurrentValue Rhino.RhinoApp.WriteLine("Select object count = {0}", objectCount) Rhino.RhinoApp.WriteLine("Value of integer1 = {0}", integer1) Rhino.RhinoApp.WriteLine("Value of integer2 = {0}", integer2) return Rhino.Commands.Result.Success if __name__ == "__main__": GetMultipleWithOptions() ``` -------------------------------------------------------------------------------- # Get Object UUID Source: https://developer.rhino3d.com/en/samples/rhinocommon/get-object-uuid/ Demonstrates how to get the UUID (sometimes called GUID) of a Rhino object. ```cs partial class Examples { public static Result GetUUID(RhinoDoc doc) { ObjRef obj_ref; var rc = RhinoGet.GetOneObject("Select object", false, ObjectType.AnyObject, out obj_ref); if (rc != Result.Success) return rc; if (obj_ref == null) return Result.Nothing; var uuid = obj_ref.ObjectId; RhinoApp.WriteLine("The object's unique id is {0}", uuid.ToString()); return Result.Success; } } ``` ```python import rhinoscriptsyntax as rs obj_id = rs.GetObject("Select object") if obj_id != None: print("The object's unique id is {0}".format(obj_id)) ``` -------------------------------------------------------------------------------- # Get Plant Points Source: https://developer.rhino3d.com/en/samples/rhinoscript/get-flamingo-plant-points/ Demonstrates how get a list of points that represent a Flamingo nXt plant in RhinoScript. ```vbnet Option Explicit Call Main() Sub Main() Dim objPlugIn, plant, points, count On Error Resume Next Set objPlugIn = Rhino.GetPluginObject("8008880f-8d13-4b2d-92b0-727e12878a4c") If Err Then MsgBox Err.Description Exit Sub End If plant = objPlugIn.GetPlantFileName("") If Not IsNull(plant) Then points = objPlugIn.GetPlantPointCloud(plant) If Not IsNull(points) Then count = UBound(points) If count > 0 Then Rhino.AddPointCloud points Rhino.Redraw() End If End If End If Set objPlugIn = Nothing End Sub ``` -------------------------------------------------------------------------------- # Get Point at Mouse Location Source: https://developer.rhino3d.com/en/samples/cpp/get-point-at-mouse-location/ Discusses how to convert a 2D screen point into a 3D world point. ```cpp static bool GetPointAtCursorPos( ON_3dPoint& point ) { bool rc = false; CRhinoView* view = RhinoApp().ActiveView(); if (view) { POINT pt; if (::GetCursorPos(&pt) ) { view->ScreenToClient( &pt ); ON_Xform xform; if( view->ActiveViewport().VP().GetXform(ON::screen_cs, ON::world_cs, xform) ) { point = ON_3dPoint( pt.x, pt.y, 0.0 ); point.Transform( xform ); rc = true; } } } return rc; } ``` -------------------------------------------------------------------------------- # Get RGB Color Intensities Source: https://developer.rhino3d.com/en/samples/rhinoscript/get-rgb-color-intensities/ Demonstrates retrieve red, green, and blue color intensities using VBScript. ```vbnet ''' --------------------------------------------------------------------------- ''' Function: GetRValue ''' Purpose: Returns the Red value from an RGB color value ''' Arguments: vbLong - An RGB color value ''' Returns: vbLong - A Red color value, -1 on error ''' --------------------------------------------------------------------------- Function GetRValue(val) If val > -1 And val < 16777216 Then GetRValue = val \ 256 ^ 0 And 255 Else GetRValue = -1 End If End Function ''' --------------------------------------------------------------------------- ''' Function: GetGValue ''' Purpose: Returns the Green value from an RGB color value ''' Arguments: vbLong - an RGB color value ''' Returns: vbLong - a Green color value, -1 on error ''' --------------------------------------------------------------------------- Function GetGValue(val) If val > -1 And val < 16777216 Then GetGValue = val \ 256 ^ 1 And 255 Else GetGValue = -1 End If End Function ''' --------------------------------------------------------------------------- ''' Function: GetBValue ''' Purpose: Returns the Blue value from an RGB color value ''' Arguments: vbLong - an RGB color value ''' Returns: vbLong - a Blue color value, -1 on error ''' --------------------------------------------------------------------------- Function GetBValue(val) If val > -1 And val < 16777216 Then GetBValue = val \ 256 ^ 2 And 255 Else GetBValue = -1 End If End Function ``` -------------------------------------------------------------------------------- # Getting & Setting Locale Source: https://developer.rhino3d.com/en/guides/rhinoscript/getting-and-setting-locale/ This guide demonstrates how to format locale-sensitive numbers using RhinoScript. ## Overview Formatted numbers are locale-sensitive. That is, the way numbers are formatted differs depending on which region of the world you are in. Here are a few examples: | Culture | Format | | :------------ | -----------: | | United States | 1,234,567.89 | | France | 1 234 567,89 | | German | 1.234.567,89 | | Switzerland | 1’234’567.89 | ## Details VBScript supports the functionality of retrieving and changing the current locale settings of your computer. The two functions that support this feature are `GetLocale()` and `SetLocale()`. `GetLocale()` returns the current locale on the client machine. `SetLocale()` sets the locale to the new specified locale. These functions can be used accordingly to localize numeric values. The `SetLocale()` function requires one argument, the LCID, which is a short string, decimal value, or hex value that uniquely identifies a geographic locale. The various geographic locales are based upon the user's language, country, and culture. For example, the locale "English - United States" can be designated as either "en-us", or "1033", or "0x0409". This locale information is used to establish user preferences and formats for such things as alphabets, currency, dates, keyboard layout, and numbers. A list of these locales, and their return values, can be found on the [MSDN Locale ID (LCID) Chart](https://msdn.microsoft.com/en-us/library/0h88fahh(v=vs.85).aspx). **NOTE**: If the LCID argument is set to zero, the locale will be set by the system. The following examples demonstrates the use of the `SetLocale()` function: ```vbnet Sub TestLocale() Dim dblValue : dblValue = 1234567.89 Call SetLocale(1033) 'English - United States Rhino.Print "English - United States = " & FormatNumber(dblValue) Call SetLocale(1036) 'French - France Rhino.Print "French - France = " & FormatNumber(dblValue) Call SetLocale(1031) 'German - Germany Rhino.Print "German - Germany = " & FormatNumber(dblValue) Call SetLocale(2055) 'German - Switzerland Rhino.Print "German - Switzerland = " & FormatNumber(dblValue) Call SetLocale(0) End Sub ``` which outputs: ```vbs English - United States = 1,234,567.89 French - France = 1 234 567,89 German - Germany = 1.234.567,89 German - Switzerland = 1'234'567.89 ``` ## Related Topics - [Locale ID (LCID) Chart on MSDN](https://msdn.microsoft.com/en-us/library/0h88fahh(v=vs.85).aspx) -------------------------------------------------------------------------------- # Getting Layer Objects Source: https://developer.rhino3d.com/en/guides/cpp/getting-layer-objects/ This brief guide demonstrates how to get all of the objects on a layer using C/C++. ## Problem You would like to get all the objects on a specific layer. ## Solution You can get all of the objects on a specified layer in two ways: 1. Use `CRhinoDoc::LookupObject`. 1. Use a `CRhinoObjectIterator`. The `CRhinoDoc::LookupObject` is somewhat easier. So, we will demonstrate this in the following sample... ## Sample ```cpp CRhinoCommand::result CCommandTest::RunCommand(const CRhinoCommandContext& context) { const wchar_t* psz_layer_name = L"Default"; ; int layer_index = context.m_doc.m_layer_table.FindLayerFromFullPathName(psz_layer_name, ON_UNSET_INT_INDEX); if (layer_index >= 0 && layer_index < context.m_doc.m_layer_table.LayerCount()) { const CRhinoLayer& layer = context.m_doc.m_layer_table[layer_index]; ON_SimpleArray objects; int object_count = context.m_doc.LookupObject(layer, objects); if (object_count > 0) { RhinoApp().Print(L"%s layer object(s)s:\n", psz_layer_name); for (int i = 0; i < object_count; i++) { const CRhinoObject* object = objects[i]; if (object) RhinoApp().Print(L" %s\n", object->ShortDescription(false)); } } } return CRhinoCommand::success; } ``` -------------------------------------------------------------------------------- # Getting Object Attributes Source: https://developer.rhino3d.com/en/guides/opennurbs/getting-object-attributes/ This guide demonstrates how to obtain attributes from model geometry objects. ## Question With prior version of openNURBS, it was easy to find the properties associated to geometry objects, such layer, material and groups, once the reference of the objects were available using ```ONX_Model_object```. What is the correct way of finding the layer, material and groups, when we have a ```ON_ModelGeometryComponent``` object? ## Answer In order to lookup referenced components, such as Layers, Materials, Groups, etc., you first must obtain the model geometry attributes, or ```ON_3dmObjectAttributes``` object. If you are referencing the ```Example_read``` sample included with the openNURBS toolkit, then after the 3DM file has been read, you can query an object’s render material as follows: ```cpp ONX_Model model = ... for (model_component = it.FirstComponent(); nullptr != model_component; model_component = it.NextComponent()) { const ON_ModelGeometryComponent* model_geometry = ON_ModelGeometryComponent::Cast(model_component); if (nullptr != model_geometry) { const ON_3dmObjectAttributes* attributes = model_geometry->Attributes(nullptr); if (nullptr != attributes) { // TODO } } } ``` Now that you have the attributes, you can use the helper functions on ```ONX_Model``` to access these components. Here are a couple of helper functions you might find useful: ```cpp /* Description: Returns a pointer to an ON_Layer object, given an object's attributes. */ static const ON_Layer* ON_LayerFromAttributes( const ONX_Model& model, const ON_3dmObjectAttributes& attributes ) { const ON_Layer* layer = nullptr; const ON_ModelComponentReference& model_component_ref = model.LayerFromAttributes(attributes); if (!model_component_ref.IsEmpty()) layer = ON_Layer::Cast(model_component_ref.ModelComponent()); return layer; } /* Description: Returns a pointer to an ON_Material object, given an object's attributes. */ static const ON_Material* ON_MaterialFromAttributes( const ONX_Model& model, const ON_3dmObjectAttributes& attributes ) { const ON_Material* material = nullptr; const ON_ModelComponentReference& model_component_ref = model.RenderMaterialFromAttributes(attributes); if (!model_component_ref.IsEmpty()) material = ON_Material::Cast(model_component_ref.ModelComponent()); return material; } /* Description: Returns pointers to ON_Group objects, given an object's attributes. */ static int ON_GroupsFromAttributes(const ONX_Model& model, const ON_3dmObjectAttributes& attributes, ON_SimpleArray& groups) { const int group_count = groups.Count(); ON_SimpleArray group_indices; if (attributes.GetGroupList(group_indices) > 0) { for (int i = 0; i < group_indices.Count(); i++) { ON_ModelComponentReference model_component_ref = model.ComponentFromIndex(ON_ModelComponent::Type::Group, i); if (!model_component_ref.IsEmpty()) { const ON_Group* group = ON_Group::Cast(model_component_ref.ModelComponent()); if (nullptr != group) groups.Append(group); } } } return groups.Count() - group_count; } ``` Here is an example of their usage: ```cpp const ON_3dmObjectAttributes* attributes = model_geometry->Attributes(nullptr); if (nullptr != attributes) { // Try getting layer const ON_Layer* layer = ON_LayerFromAttributes(model, *attributes); if (nullptr != layer) { // TODO... } // Try getting material const ON_Material* material = ON_MaterialFromAttributes(model, *attributes); if (nullptr != material) { // TODO... } // Try getting groups ON_SimpleArray groups; const int group_count = ON_GroupsFromAttributes(model, *attributes, groups); for (int i = 0; i < group_count; i++) { const ON_Group* group = groups[i]; if (nullptr != group) { // TODO... } } } } ``` -------------------------------------------------------------------------------- # Getting Object UUIDs Source: https://developer.rhino3d.com/en/guides/cpp/getting-object-uuid/ This brief guide demonstrates how to get an object's UUID using C/C++. ## Overview Rhino can create and manipulate many geometric objects, including points, point clouds, curves, surfaces, Breps, extrusions, subds, meshes, lights, annotations and more. A universally unique identifier, or [UUID](https://en.wikipedia.org/wiki/Universally_unique_identifier), is assigned to each object in the Rhino document when the objects are created. The object's UUID uniquely identifies the object. Because UUIDs are saved in the 3DM file, an object's unique identifier will persist between editing sessions. ## Sample The following code sample demonstrates how to obtain an object's unique identifier, or UUID, using C/C++: ```cpp CRhinoCommand::result CCommandTest::RunCommand(const CRhinoCommandContext& context) { CRhinoGetObject go; go.SetCommandPrompt(L"Select object"); go.GetObjects(1, 1); if (go.CommandResult() != CRhinoCommand::success) return go.CommandResult(); const CRhinoObjRef& ref = go.Object(0); const CRhinoObject* obj = ref.Object(); if (nullptr == obj) return CRhinoCommand::failure; ON_UUID uuid = obj->ModelObjectId(); ON_wString str; ON_UuidToString(uuid, str); RhinoApp().Print(L"The object's unique identifier is \"%s\".\n", str); return CRhinoCommand::success; } ``` -------------------------------------------------------------------------------- # Getting Script-Added Objects Source: https://developer.rhino3d.com/en/guides/cpp/getting-script-added-objects/ This guide demonstrates how to locate objects that were added to Rhino by a script using C/C++. ## Problem You have a command, derived from `CRhinoScriptCommand`, that scripts several Rhino command that add objects. After running the scripts, with `CRhinoApp::RunScript`, you would like to get the addresses, or pointers, of the added objects. But the commands that create the new objects do not select then. Is there a way to get the added objects' addresses in this case? ## Solution See of the following sample gives you any ideas... ```cpp static int CompareObjectPtr( const CRhinoObject* const * a, const CRhinoObject* const * b ) { INT_PTR d = (*a) - (*b); return ( (d < 0) ? -1 : ( (d > 0) ? 1 : 0 ) ); } CRhinoCommand::result CCommandTest::RunCommand( const CRhinoCommandContext& context ) { // Disable redrawing CRhinoView::EnableDrawing( FALSE ); // Get the next runtime object serial number before scripting unsigned int first_sn = CRhinoObject::NextRuntimeObjectSerialNumber(); // Do some scripting... RhinoApp().RunScript( L"_-Line 0,0,0 10,10,0", 0 ); RhinoApp().RunScript( L"_SelLast", 0 ); RhinoApp().RunScript( L"_-Properties _Object _Color _Object 255,0,0 _Enter _Enter", 0 ); RhinoApp().RunScript( L"_-Circle 0,0,0 10", 0 ); RhinoApp().RunScript( L"_SelLast", 0 ); RhinoApp().RunScript( L"_-Properties _Object _Color _Object 0,0,255 _Enter _Enter", 0 ); // Get the next runtime object serial number after scripting unsigned int next_sn = CRhinoObject::NextRuntimeObjectSerialNumber(); // Enable redrawing CRhinoView::EnableDrawing( TRUE ); // if the two are the same, then nothing happened if( first_sn == next_sn ) return CRhinoCommand::nothing; // The the pointers of all of the objects that were added during scripting ON_SimpleArray objects; for( unsigned int sn = first_sn; sn < next_sn; sn++ ) { const CRhinoObject* obj = context.m_doc.LookupObjectByRuntimeSerialNumber( sn ); if( obj && !obj->IsDeleted() ) objects.Append( obj ); } // Sort and cull the list, as there may be duplicates if( objects.Count() > 1 ) { objects.HeapSort( CompareObjectPtr ); const CRhinoObject* last_obj = objects[objects.Count()-1]; for( int i = objects.Count()-2; i >= 0; i-- ) { const CRhinoObject* prev_obj = objects[i]; if( last_obj == prev_obj ) objects.Remove(i); else last_obj = prev_obj; } } // Do something with the list... for( int i = 0; i < objects.Count(); i++ ) { const CRhinoObject* obj = objects[i]; if( obj->IsSelectable(true) ) obj->Select( true ); } context.m_doc.Redraw(); return CRhinoCommand::success; } ``` -------------------------------------------------------------------------------- # Getting Started Source: https://developer.rhino3d.com/en/guides/opennurbs/getting-started/ This guide explains how to use the openNURBS C++ toolkit in your project. ## Prerequisites openNURBS is intended for *skilled* C++ developers. Please read [What is openNURBS?](/guides/opennurbs/what-is-opennurbs) if you have not already. It is also presumed that you have an application that wishes to access *3DM* files outside of Rhinoceros. ## openNURBS public SDK To download the public C++ SDK and example 3DM files, visit https://www.rhino3d.com/opennurbs. ## Supported C++ Compilers The openNURBS C++ toolkit has been successfully used with the following compilers: - A version of *Microsoft Visual Studio* that includes the Visual Studio 2019 (v142) platform toolset. Thus, you can use either Visual Studio 2022 or Visual Studio 2019. To build openNURBS and the examples with *Microsoft Visual Studio*, use the solution *opennurbs_public.sln*. To use C++ opennurbs in your Visual Studio project: 1. Open opennurbs_public.sln in Visual Studio, select the platform and configuration, and rebuild all. 1. In your project's stdafx.h, put the following lines. This will include all the header files you need to call C++ opennurbs and automatically link in the correct libraries: ```cpp // defining OPENNURBS_PUBLIC_INSTALL_DIR enables automatic linking using pragmas #define OPENNURBS_PUBLIC_INSTALL_DIR "" // uncomment the next line if you want to use opennurbs as a DLL //#define OPENNURBS_IMPORTS #include "/opennurbs_public.h" ``` - *Apple Xcode 9.2* (9C40b): To build openNURBS and the examples with *Xcode 9.2* (or later), use the workspace *opennurbs_public.xcworkspace*. - *Other C++ compilers* The compiler must support the C++14 standard. A *makefile* is provided as a starting point. The openNURBS C++ source code is clean and simple. If you are using a C++ compiler that supports the C++14 standard and run into some toolkit code that causes problems, please [let us know](http://discourse.mcneel.com/category/opennurbs). We'll attempt to change the code to accommodate the compiler. ## Read and Write Use `ONX_Model::Read()` and `ONX_Model::Write()` to add support for reading and writing 3DM files to your application. See *example_read.cpp* and *example_write.cpp* for more details. 1. Compile the openNURBS Toolkit library. To compile the example programs you must link with the openNURBS Toolkit library. 1. Study *example_read\example_read.cpp*. All the openNURBS geometry classes are derived from the `ON_Geometry` class. If you need generic attribute information, there is probably an `ON_Geometry` member function that will answer your query. See the `Dump()` function in *example_read.cpp* for code that demonstrates how to use the `Cast()` function to get at the actual class definitions. 1. Study *example_write\example_write.cpp*. If you want to write points, meshes, NURBS curves, NURBS surfaces, or trimmed NURBS surfaces, you should be able to cut and paste most of what you need from the functions in *example_write.cpp*. If you want to write trimmed surfaces or b-reps, then please study *example_brep.cpp*. 1. Study *example_brep\example_brep.cpp*. If you want to write solid models or more general b-reps, then first work through *example_write.cpp* and then work through *example_brep.cpp*. 1. The comments in the openNURBS Toolkit header files are the primary source of documentation. I suggest that you use a development environment that has high quality tags capabilities and a good class browser. 1. In the code you write include only *opennurbs.h*. The *opennurbs.h* header file includes the necessary openNURBS toolkit header files in the appropriate order. 1. Other items to note: 1. *Strings* The `ON_wString` class has `wchar_t` elements. When using Microsoft Visual Studio on Windows, `sizeof(wchar_t)=2` and `ON_wString` content is UTF-16 encoded. When using Apple XCode on Mac OS, `sizeof(wchar_t)=4` and `ON_wString` content is UTF-32 encoded. 1. All memory allocations and frees are done through `onmalloc()`, `onfree()`, and `onrealloc()`. The source that ships with openNURBS has `onmalloc()` call `malloc()` and `onfree()` call `free()`.     1. If you want to use Open GL to render openNURBS geometry, you may want to include *opennurbs_gl.h* after *opennurbs.h* and add *opennurbs_gl.cpp* to your openNURBS library. See *example_gl.cpp* for details.     1. The openNURBS Toolkit works correctly on both big and little endian CPUs. ## 3DM File Versions 1. *Version 1 3DM files*. The openNURBS toolkit will read version 1 files. Rhino 1.0 and other applications using the old Rhino I/O toolkit create version 1 files. 1. *Version 2 3DM files*. The openNURBS toolkit will read and write version 2 files. Rhino 2.0 and applications using an openNURBS toolkit released on or after December 2000 create version 2 files. (Rhino 1.0 and the old Rhino I/O toolkit will not read version 2 files.) 1. *Version 3 3DM files*. The openNURBS toolkit will read and write version 3 files. Rhino 3.0 and applications using an openNURBS toolkit released on or after October 2002 create version 3 files. (Rhino's 1.0 and 2.0 will not read version 3 files.) 1. *Version 4 3DM files*. The openNURBS toolkit will read and write version 4 files. Rhino 4.0 and applications using an openNURBS toolkit released on or after September 2006 create version 4 files. (Rhino's 1.0, 2.0 and 3.0 will not read version 4 files.) 1. *Version 5 3DM files*. The openNURBS toolkit will read and write version 5 files. Rhino 5 and applications using an openNURBS toolkit released on or after September 2009 create version 5 files. (Rhino's 1.0, 2.0, 3.0 and 4.0 will not read version 5 files.) 1. *Version 6 3DM files*. The openNURBS toolkit will read and write version 6 files. Rhino 6 and applications using an openNURBS toolkit released on or after January 2018 create version 6 files. (Rhino's 1.0, 2.0, 3.0, 4.0 and 5 will not read version 6 files.) 1. *Version 7 3DM files*. The openNURBS toolkit will read and write version 7 files. Rhino 7 and applications using an openNURBS toolkit released on or after November 2020 create version 7 files. (Rhino's 1.0, 2.0, 3.0, 4.0, 5 and 6 will not read version 7 files.) 1. *Version 8 3DM files*. The openNURBS toolkit will read and write version 8 files. Rhino 8 and applications using an openNURBS toolkit released on or after October 2023 create version 8 files. (Rhino's 1.0, 2.0, 3.0, 4.0, 5, 6, and 7 will not read version 8 files.) Sample 3DM files are availble from https://www.rhino3d.com/opennurbs ## Examples This is an overview of the examples included with the openNURBS toolkit: - *example_read\example_read.cpp*: Create a program by compiling *example_read.cpp* and statically linking with the openNURBS library. The code in *example_read.cpp* illustrates how to read an openNURBS *.3dm* file. - *example_write\example_write.cpp*: Create a program by compiling *example_write.cpp* and linking with the openNURBS library. The code in *example_write.cpp* illustrates how to write layers, units system and tolerances, viewports, spotlights, points, meshes, NURBs curves, NURBs surfaces, trimmed NURBs surfaces, texture and bump map information, render material name, and material definitions to an openNURBS *.3dm* file. The bitmap *example_write\example_texture.bmp* is referenced by a rendering material texture in one of the 3DM files created by *example_write*. - *example_test\example_test.cpp*: Create a program by compiling *example_test.cpp* and linking with the openNURBS library. The example_test program can be used to validate your opennurbs installation. - *example_brep\example_brep.cpp*: Create a program by compiling *example_brep.cpp* and linking with the openNURBS library. The code in *example_write.cpp* illustrates how to write a solid model. - *example_userdata\example_userdata.cpp*: Create a program by compiling *example_userdata.cpp* and linking with the openNURBS library. The code in *example_userdata* demonstrates how to create and manage arbitrary user defined information in *.3dm* files. - *The Open GL example*: This code is a crude sketch of a starting point for using OpenGL to display opennurbs geometry. Start with simple mesh objects and work up. We do not provide support for using OpenGL. ## Answers to frequently asked questions: ### ON_Mesh #### Vertex ordering in ON_Mesh faces All faces in a `ON_Mesh` are stored with vertices listed in counter-clockwise order. In particular, for quads the vertices are ordered as: ![Vertex Ordering](https://developer.rhino3d.com/images/opennurbs-getting-started-01.png) The quads may be non-planar. The definition of `void ON_GL( const ON_Mesh& )` in *opennurbs_gl.cpp* demonstrates how to go through an `ON_Mesh` and render all the quads as two triangles. ### ON_Brep #### Orientation of ON_Brep faces The UV orientation of surfaces in a Brep is arbitrary. If the `BOOL ON_BrepFace::m_bRev` member is `FALSE`, then the face's orientation agrees with the surface's natural $$Du \times Dv$$ orientation. When the member is `TRUE`, the face's orientation is opposite the surface's natural $$Du \times Dv$$ orientation. If your application cannot handle `ON_BrepFaces` that have a `TRUE` `m_bRev` flag, then call `ON_Brep::FlipReversedSurfaces()`. See the comments in `ON_Brep::FlipReversedSurfaces()` and `ON_Brep::SwapFaceParameters()` for details. #### Trimming loop order and nesting The `ON_BrepLoop::m_type` member records the type of boundary (inner, outer, etc.). A `ON_BrepFace` has exactly one outer loop and it is the first loop referenced in the `ON_BrepFace::m_li[]` array. The inner loops all define "holes" in the `ON_BrepFace`. All the inner holes lie inside of the outer loop. A `ON_BrepFace` is always path connected. In particular, inner loops are not "nested." #### Surfaces in Rhino are read as ON_Breps Internally, Rhino stores all surfaces as some type of b-rep and the openNURBS toolkit reads these objects as b-reps. To see if an entire `ON_Brep` is really just a surface, use: `BOOL ON_Brep::IsSurface()` If `ON_Brep::IsSurface()` returns `TRUE`, then the b-rep geometry is the same as the surface `ON_Brep::m_S[0]`. To see of a particular face in a b-rep is really just a surface, use: `BOOL ON_Brep::FaceIsSurface( face_index )` If `ON_Brep::FaceIsSurface( face_index )` returns `TRUE`, then the face's geometry is the same as the surface `ON_Brep::m_S[face.m_si]`. ## Related Topics - [What is openNURBS?](/guides/opennurbs/what-is-opennurbs) - [What is Rhino3dmIO?](/guides/opennurbs/what-is-rhino3dmio) -------------------------------------------------------------------------------- # Getting the Units of the Active Document Source: https://developer.rhino3d.com/en/guides/cpp/getting-units-of-active-document/ This brief guide demonstrates how to determine the unit system of the active document. ## Overview There are two unit systems associated with a document, model and page units. The `ON_UnitSystem` class makes it easy to work with custom units. The Rhino document class contains two functions to return these unit systems: 1. `const ON_UnitSystem& CRhinoDoc::ModelUnits() const;` 1. `const ON_UnitSystem& CRhinoDoc::PageUnits() const;` ## More information The unit system of the active document is stored on an `ON_3dmUnitsAndTolerances` class that is located on the `CRhinoDoc` object. If you inside of a `CRhinoCommand`-derived object's `RunCommand()` member, you can get the current units system as follows: ```cpp const CRhinoDocProperties& doc_props = context.m_doc.Properties(); const ON_3dmUnitsAndTolerances& units_tolerances = doc_props.ModelUnitsAndTolerances(); ON::LengthUnitSystem units_system = units_tolerances.m_unit_system; ``` As a shortcut, you can do the following: ```cpp ON::LengthUnitSystem units_system = context.m_doc.UnitSystem(); ``` If you outside of a `CRhinoCommand`-derived object's `RunCommand()` member, you can get the current units system as follows: ```cpp CRhinoDoc* doc = RhinoApp().ActiveDoc(); if (nullptr != doc) { ON::LengthUnitSystem units_system = doc->UnitSystem(); } ``` -------------------------------------------------------------------------------- # Ghost Viewport Source: https://developer.rhino3d.com/en/samples/cpp/ghost-viewport/ Demonstrates how to set a viewport to ghosted display. ```cpp CRhinoCommand::result CCommandTest::RunCommand( const CRhinoCommandContext& context ) { CRhinoView* view = RhinoApp().ActiveView(); if( 0 == view ) return CRhinoCommand::failure; CRhinoViewport& vp = view->ActiveViewport(); const CDisplayPipelineAttributes* pStdAttrs = CRhinoDisplayAttrsMgr::StdGhostedAttrs(); if( pStdAttrs ) { vp.SetDisplayMode( pStdAttrs->Id() ); view->Redraw(); } return CRhinoCommand::success; } ``` -------------------------------------------------------------------------------- # Grasshopper Data Types Source: https://developer.rhino3d.com/en/guides/grasshopper/grasshopper-data-types/ This guide covers the basic data types that Grasshopper deals with. ## Overview Grasshopper is an application that deals with a lot of different types of data. These data types can come from 6 different sources and some of them will not be known when Grasshopper is written and compiled. The six potential sources are: 1. Primitive types such as `Booleans`, `Integers`, `Colors`, `Strings`, etc. Grasshopper uses these types itself a lot. 1. Other .NET Framework types such as `System.Drawing.PointF` or `System.Collections.Generic.HashSet`. Grasshopper does not use these types to during component-to-component communication, but someone else might. 1. RhinoCommon types such as `Point3d`, `Circle`, `Plane`, `Brep`, `Curve` etc. Many of these are used natively, but certainly not all of them. 1. Types defined in Grasshopper itself. 1. Types defined in components for Grasshopper. 1. Types defined in VB/C#/Python scripts that run inside Grasshopper. ## What to do with unknowns? Although clearly some of these are known to the developer during the time of writing, not all of them can be. Yet Grasshopper still needs to be able to interpret and use types it may know nothing about. Some of the things Grasshopper needs to be able to do with data of any type are: 1. Convert it to text so tooltips and panels can be populated with *useful* descriptions of data. 1. Convert it to and from other types. 1. Duplicate data so we can change it without affecting the original. 1. Test data for validity. 1. Save types to *\*.gh* files and load them back in. (note: this works especially poorly at the moment.) 1. Preview geometric types in the viewport. 1. Bake geometric types to the Rhino document. 1. Transform geometric types. 1. Calculate bounding boxes of geometric types. 1. Be able to store null states of each type. To overcome the problem of (A) needing to do so many things while (B) knowing nothing about the types in advance an interface is defined in the Grasshopper SDK and all data which is stored inside parameters must implement this interface. This allows Grasshopper to do the things from the second list to all the types from the first list. ## Goo The `IGH_Goo` interface is is usually nothing more than a wrapper around the actual data which provides a bunch of functionality for whatever it wraps. For example take the primitive `Boolean` (or `bool` in C#). It's a structure so it can never be null, and it can only exist in either a *true* or a *false* state. Grasshopper uses booleans a lot so it provides an `IGH_Goo` implementation for `Boolean`. This wrapper class tells Grasshopper that a boolean value can be converted into an integer (false -> 0, true -> 1), into a colour (false -> black, true -> white), into a string (false -> "false", true -> "true") and so on. The wrapper class also knows how to write and read boolean values to and from *\*.gh* files, and because we're now dealing with a wrapper class we can have null instances in a collection of boolean values. The wrapper *doesn't* tell Grasshopper how to preview or bake booleans, because booleans are not a geometric type of data. Because so many data types are not in any way geometric, there is a second interface called `IGH_GeometricGoo` which extends `IGH_Goo` with stuff like transforming, bounding-boxing etc. *Almost always* component developers and scripters can ignore any of the `IGH_Goo` types, because they are mostly used for internal bookkeeping. However sometimes a developer will either want to access the functionality that goo provides or they wish to inject a new, previously unknown type of data into a Grasshopper file. In these cases some knowledge of `IGH_Goo` and its derived interface and classes is required. ## Conversion Methods IGH_Goo requires that the implementor provides three different conversion methods: 1. `CastFrom` 1. `CastTo` 1. `ScriptVariable` As an elucidation, imagine the following conversation going on between Grasshopper (G), which is trying to push data of type A in one parameter into another parameter which only accepts data of type B... G: > "Hmm, two parameters that both store different types have been connected with a wire. That means the user wants to transfer all data from left to right, but I can't simply copy it because the parameter on the right only accepts instances of type B. > First let me check if A is a more derived type of B, in which case I can just 'trick' the parameter into accepting the original data." >"Nope, A is not a special flavour of B, which means some conversion needs to happen first... Hey! You there! Mr. A! I need to move you over to this other place but they don't take kindly to the likes of you. Is there any way I can persuade you to turn yourself into something more B-like?" A: > "Oh sorry, I have never heard of this B-thing you mentioned before, afraid I can't help you out here." G: > "Never mind, I'll ask over there. Oy, B-man! Any chance of you letting me in on the secret about how to take this A thing over there and turn it into a B thing?" B: > "Yeah I know how to do that, but you'll lose some information in the process." G: > "That's all right, something's better than nothing." B: > "All right, here you go, a B-ified version of the A data which is the best I can do under these imperfect circumstances." G: > "Thanks! I'll put it into this data tree over here right away." More succinctly, Grasshopper calls the `CastTo` method on the instance of A, asking it if it knows how to convert itself into a B type. If that fails, it'll construct a new empty B instance and ask it using the `CastFrom` method whether it knows how to interpret the A data and mimic it. If both these methods fail then Grasshopper will put up an error and leave that slot blank. The `ScriptVariable` method allows data to protect itself from potentially ill-informed programmers. Using this method, Grasshopper tells some data that it's about to be handed over to a script (ie. a VB/C#/Python component) and whether it would like to maybe provide a different kind of data instead. For example a `GH_Boolean` might assume that the scripter does't really care for all that `IGH_Goo` malarkey and instead of pumping a `GH_Boolean` instance into a script just the value on the inside is provided. ## Related Topics - [Simple Data Types](/guides/grasshopper/simple-data-types) -------------------------------------------------------------------------------- # Hacking and Testing latest Atom rhino-python package Source: https://developer.rhino3d.com/en/guides/rhinopython/hacking-atom/ This guide describes how to run and test the latest Atom rhino-python package. ### Specifically, running from the "settings" branch of the source to test the search paths configuration. I updated the rhino-python Atom package to support configuring the Python search path which I plan to deploy when we release MR5.1. The document describes how to install and run it from source so some of you can test and give feedback. - open a terminal window pointing to the directory where you want to clone the rhino-python package and type: ``` git clone https://github.com/mcneel/rhino-python.git ``` ``` cd rhino-python ``` - switch to the branch that implements the search paths configuration. ``` git checkout settings ``` - create a link so that the package will be loaded from the git repo when Atom is run in dev mode: ``` apm link -d ``` - install the package dependencies ``` apm install ``` - to launch Atom in dev mode cd to where your python files are and type: ``` atom -d . ``` - You are now running the rhino-python package from source. You are specifically running from the "settings" git branch where the python search paths configuration is implemented. - Rename Rhinoceros.app to something else to back it up then rename RhinoWIP.app to Rhinoceros.app (this is a temporary step that won't be required in he future). - Launch the RhinoWIP (renamed) and type the command: `StartAtomEditorListener`. Getting and setting the Python search path values is done by Rhino so it has to be running and listening for Atom requests. - Switch back to Atom and press the ctrl + alt + s keys to open the "Rhino Python Search Paths" window. # Notes on the Rhino Python Search Paths toolbar: - when the window is 1st opened only the add ( + ) button is enabled. - click on a path to select it and the other nav buttons become enabled. - None of the changes made in the UI are saved until the save button is clicked which sends the save request to Rhino. - Clicking the revert button discards all changes made since the last save by sending a request to Rhino for the last saved search paths. Please send all feedback to [Alain](mailto:alain@mcneel.com). -------------------------------------------------------------------------------- # Handling Enter and Escape from Modal Dialogs Source: https://developer.rhino3d.com/en/guides/cpp/handling-enter-esc-from-modal-dialogs/ This guide discusses preventing modal dialogs from closing when the Enter or Escape key is pressed. ## Problem The problem is simple: how to stop my MFC modal dialog box from closing when the user presses the Enter or Escape keys? ## Solution The first step is to, on your CDialog-drived class, override the `CWnd::PreTranslateMessage` virtual function. This function is used translate window messages before they are dispatched to the `TranslateMessage` and `DispatchMessage` Windows functions. Then, add the following block of code: ```cpp BOOL CMyModalDialog::PreTranslateMessage( MSG* pMsg ) { if( pMsg ) { if( pMsg->message == WM_KEYDOWN ) { if( pMsg->wParam == VK_RETURN | pMsg->wParam == VK_ESCAPE ) pMsg->wParam = NULL; } } // Call the base class PreTranslateMessage. In this example, // CRhinoDialog is the base class to CMyModalDialog. return CRhinoDialog::PreTranslateMessage( pMsg ); } ``` -------------------------------------------------------------------------------- # Hatch Boundary Source: https://developer.rhino3d.com/en/samples/cpp/hatch-boundary/ Demonstrates how to hatch a closed planar boundary. ```cpp CRhinoCommand::result CCommandBrenton::RunCommand( const CRhinoCommandContext& context ) { CRhinoGetObject go; go.SetCommandPrompt( L"Select closed planar curve" ); go.SetGeometryFilter( CRhinoGetObject::curve_object ); go.SetGeometryAttributeFilter( CRhinoGetObject::closed_curve ); go.EnableSubObjectSelect( false ); go.GetObjects( 1, 1 ); if( go.CommandResult() != success ) return go.CommandResult(); const ON_Curve* crv = go.Object(0).Curve(); if( 0 == crv | !crv->IsClosed() | !crv->IsPlanar() ) return failure; CRhinoHatchPatternTable& table = context.m_doc.m_hatchpattern_table; CRhinoGetString gs; gs.SetCommandPrompt( L"Hatch pattern" ); gs.SetDefaultString( table.CurrentHatchPattern().Name() ); gs.GetString(); if( gs.CommandResult() != success ) return gs.CommandResult(); ON_wString name = gs.String(); name.TrimLeftAndRight(); if( name.IsEmpty() ) return nothing; int index = table.FindHatchPattern( name ); if( index < 0 ) { RhinoApp().Print( L"Hatch pattern does not exist.\n" ); return nothing; } CArgsRhinoHatch args; args.m_loops.Append( crv ); args.SetPatternIndex( index ); args.SetPatternScale( 1.0 ); // default args.SetPatternRotation( 0.0 ); // default ON_SimpleArray hatches; bool rc = RhinoCreateHatches( args, hatches ); if( rc && hatches.Count() ) { int i, num_added = 0; for( i = 0; i < hatches.Count(); i++ ) { ON_Hatch* hatch = hatches[0]; if( hatch ) { CRhinoHatch* hatch_obj = new CRhinoHatch(); if( hatch_obj ) { hatch_obj->SetHatch( hatch ); if( context.m_doc.AddObject(hatch_obj) ) num_added++; else delete hatch_obj; } else delete hatch; } } if( num_added ) context.m_doc.Redraw(); } return rc ? success : failure; } ``` -------------------------------------------------------------------------------- # Hatch Curve Source: https://developer.rhino3d.com/en/samples/rhinocommon/hatch-curve/ Demonstrates how to create a hatch from a curve. ```cs partial class Examples { public static Rhino.Commands.Result HatchCurve(Rhino.RhinoDoc doc) { var go = new Rhino.Input.Custom.GetObject(); go.SetCommandPrompt("Select closed planar curve"); go.GeometryFilter = Rhino.DocObjects.ObjectType.Curve; go.GeometryAttributeFilter = Rhino.Input.Custom.GeometryAttributeFilter.ClosedCurve; go.SubObjectSelect = false; go.Get(); if( go.CommandResult() != Rhino.Commands.Result.Success ) return go.CommandResult(); var curve = go.Object(0).Curve(); if( curve==null || !curve.IsClosed || !curve.IsPlanar() ) return Rhino.Commands.Result.Failure; string hatch_name = doc.HatchPatterns[doc.HatchPatterns.CurrentHatchPatternIndex].Name; var rc = Rhino.Input.RhinoGet.GetString("Hatch pattern", true, ref hatch_name); if( rc!= Rhino.Commands.Result.Success ) return rc; hatch_name = hatch_name.Trim(); if( string.IsNullOrWhiteSpace(hatch_name) ) return Rhino.Commands.Result.Nothing; int index = doc.HatchPatterns.Find(hatch_name, true); if( index < 0 ) { Rhino.RhinoApp.WriteLine("Hatch pattern does not exist."); return Rhino.Commands.Result.Nothing; } var hatches = Rhino.Geometry.Hatch.Create( curve, index, 0, 1); for( int i=0; i0 ) doc.Views.Redraw(); return Rhino.Commands.Result.Success; } } ``` ```python import Rhino import scriptcontext def HatchCurve(): go = Rhino.Input.Custom.GetObject() go.SetCommandPrompt("Select closed planar curve") go.GeometryFilter = Rhino.DocObjects.ObjectType.Curve go.GeometryAttributeFilter = Rhino.Input.Custom.GeometryAttributeFilter.ClosedCurve go.SubObjectSelect = False go.Get() if go.CommandResult()!=Rhino.Commands.Result.Success: return curve = go.Object(0).Curve() if (not curve or not curve.IsClosed or not curve.IsPlanar()): return hatch_name = scriptcontext.doc.HatchPatterns[scriptcontext.doc.HatchPatterns.CurrentHatchPatternIndex].Name rc, hatch_name = Rhino.Input.RhinoGet.GetString("Hatch pattern", True, hatch_name) if rc!=Rhino.Commands.Result.Success or not hatch_name: return pattern = scriptcontext.doc.HatchPatterns.FindName(hatch_name) if pattern is None: print("Hatch pattern does not exist.") return index = pattern.Index hatches = Rhino.Geometry.Hatch.Create(curve, index, 0, 1) for hatch in hatches: scriptcontext.doc.Objects.AddHatch(hatch) if hatches: scriptcontext.doc.Views.Redraw() if __name__=="__main__": HatchCurve() ``` -------------------------------------------------------------------------------- # Hello Python Source: https://developer.rhino3d.com/en/samples/rhinopython/hello-python/ Demonstrates Basic syntax for writing python scripts. ```python # Basic syntax for writing python scripts print("Hello Python!") # <- any line that begins with the pound symbol is a comment # assign a variable x=123 # print out the type of the variable print(type(x)) # print out the value of the variable print(x) # combine statements with commas to print a single line print("x is a {} and has a value of {}".format(type(x), x)) #loops # notice that there is a colon at the end of the first line and # and what is executed has some spaces in front of it for i in range(1,10): print("i={}".format(i)) #conditionals x = 8 for i in range(1,x+1): if i%2==0: print("{} is even".format(i)) else: print("{} is odd".format(i)) #functions def MyFunc(a): x = a+2 print("{}+2={}".format(a,x)) MyFunc(10) ``` -------------------------------------------------------------------------------- # Highlighting Objects in Conduits Source: https://developer.rhino3d.com/en/guides/cpp/highlighting-objects-in-conduits/ This guide demonstrates how to highlight objects in a conduit using C/C++. ## Problem You need to highlight a curve object on display conduit. ## Solution Consider the following sample `CRhinoDisplayConduit` derived class: ```cpp class CTestHighlightCurveConduit : public CRhinoDisplayConduit { public: CTestHighlightCurveConduit(); bool ExecConduit( CRhinoDisplayPipeline& dp, UINT nChannel, bool& bTerminate ); public: unsigned int m_runtime_object_serial_number; }; CTestHighlightCurveConduit::CTestHighlightCurveConduit() : CRhinoDisplayConduit( CSupportChannels::SC_DRAWOBJECT ) { // TODO: initialize members here } bool CTestHighlightCurveConduit::ExecConduit( CRhinoDisplayPipeline& dp, UINT nChannel, bool& bTerminate ) { switch( nChannel ) { case CSupportChannels::SC_DRAWOBJECT: { if( m_pChannelAttrs->m_pObject->m_runtime_object_serial_number == m_runtime_object_serial_number ) m_pDisplayAttrs->m_ObjectColor = RGB(255, 105, 180); // hot pink } break; } return true; } ``` During the `SC_DRAWOBJECT` channel, the code looks for the target object. If found, it overrides the object's drawing color. To use this conduit, you could write a command such as this: ```cpp CRhinoCommand::result CCommandTestHighlightCurve::RunCommand( const CRhinoCommandContext& context ) { CRhinoGetObject go; go.SetCommandPrompt( L"Select curve to highlight" ); go.SetGeometryFilter( CRhinoGetObject::curve_object ); go.GetObjects( 1, 1 ); if( go.CommandResult() != CRhinoCommand::success ) return go.CommandResult(); const CRhinoObject* obj = go.Object(0).Object(); if( 0 == obj ) return CRhinoCommand::failure; CTestHighlightCurveConduit conduit; conduit.m_runtime_object_serial_number = obj->m_runtime_object_serial_number; conduit.Enable(); context.m_doc.Redraw(); CRhinoGetString gs; gs.SetCommandPrompt( L"Press to continue" ); gs.GetString(); conduit.Disable(); context.m_doc.Redraw(); return CRhinoCommand::success; } ``` -------------------------------------------------------------------------------- # Hot & Cold Colors Source: https://developer.rhino3d.com/en/guides/rhinoscript/hot-cold-color-values/ This guide demonstrates how calculate colors for analysis using RhinoScript. ## Problem It is often useful to show curvature with a color index. For example, if you divide a curve into 500 points and measure the curvature at each point, you can assign a “curvature radius” color to each of the points using RhinoScript. Let's take a look at how this is done. ## Solution One solution you might consider is to determine the minimum and maximum curvature values for your samples. Then, you can use this function to calculate a color value for each point. ```vbnet ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' Return a RGB color given a scalar v in the range [vmin, vmax]. ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Function GetHotColdColor(v, vmin, vmax) Dim r, g, b, dv r = 1.0 : g = 1.0 : b = 1.0 'white If (v < vmin) Then v = vmin If (v > vmax) Then v = vmax dv = vmax - vmin If (v < (vmin + 0.25 * dv)) Then r = 0 g = 4 * (v - vmin) / dv ElseIf (v < (vmin + 0.5 * dv)) Then r = 0 b = 1 + 4 * (vmin + 0.25 * dv - v) / dv ElseIf (v < (vmin + 0.75 * dv)) Then r = 4 * (v - vmin - 0.5 * dv) / dv b = 0 Else g = 1 + 4 * (vmin + 0.75 * dv - v) / dv b = 0 End If GetHotColdColor = RGB(Int(r*255), Int(g*255), Int(b*255)) End Function ``` Here is a sample script that you can use to test the above function... ```vbnet ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' Test procedure creates a "hot-to-cold" color ramp mesh. ' To see the results, set a viewport to "rendered" display. ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Sub TestGetHotColdColor() ' Mesh with 200 vertices and 100 faces Dim v(199), f(99), c(199), ub, i ' Fill in arrays ub = UBound(v) For i = 0 To UBound(v) Step 2 v(i) = Array(i/2,0,0) v(i+1) = Array(i/2,10,0) c(i) = GetHotColdColor(i,0,ub) c(i+1) = c(i) f(i/2) = Array(i,i+2,i+3,i+1) Next ' Create the mesh object Call Rhino.AddMesh(v,f,,,c) End Sub ``` -------------------------------------------------------------------------------- # Import Flamingo Material Source: https://developer.rhino3d.com/en/samples/rhinoscript/import-flamingo-material/ Demonstrates how import a ArMaterial file using RhinoScript. ```vbnet Option Explicit Call Main() Sub Main() Dim objPlugIn, strMaterial, strMaterialId, material On Error Resume Next Set objPlugIn = Rhino.GetPluginObject("8008880f-8d13-4b2d-92b0-727e12878a4c") If Err Then MsgBox Err.Description Exit Sub End If strMaterial = "C:\ProgramData\McNeel\Flamingo nXt\Language Packs\en-US\Materials\Basics\Basic Car Paint Red.ArMaterial" strMaterialId = objPlugIn.ImportMaterial(strMaterial, true) if Not IsNull(strMaterialId) And Not strMaterialId = "" Then Set material = objPlugIn.GetMaterial(strMaterialId) Rhino.Print("Object assigned to Flamingo nXt material ID: " & material.Id & " Name: " & material.Name) End If Set material = Nothing Set objPlugIn = Nothing End Sub ``` -------------------------------------------------------------------------------- # Import Interpolated Curve Source: https://developer.rhino3d.com/en/samples/rhinoscript/import-interpolated-curve/ Demonstrates how to read a point file and create an interpolated curve using RhinoScript. ```vbnet Option Explicit Sub ImportInterpCrv() Dim strFilter, strFileName strFilter = "Text File (*.txt)|*.txt|All Files (*.*)|*.*|" strFileName = Rhino.OpenFileName("Open Point File", strFilter) If IsNull(strFileName) Then Exit Sub Dim objFSO, objFile Set objFSO = CreateObject("Scripting.FileSystemObject") On Error Resume Next Set objFile = objFSO.OpenTextFile(strFileName, 1) If Err Then MsgBox Err.Description Exit Sub End If Dim strLine, arrPt, arrPoints(), nCount nCount = 0 Do While objFile.AtEndOfStream <> True strLine = objFile.ReadLine If Not IsNull(strLine) Then strLine = Replace(strLine, Chr(34), , 1) arrPt = Rhino.Str2Pt(strLine) If IsArray(arrPoint) Then ReDim Preserve arrPoints(nCount) arrPoints(nCount) = arrPt nCount = nCount + 1 End If End If Loop If IsArray(arrPoints) Then Rhino.AddInterpCurveEx arrPoints End If objFile.Close Set objFile = Nothing Set objFSO = Nothing End Sub ``` -------------------------------------------------------------------------------- # Import Text from File Source: https://developer.rhino3d.com/en/samples/rhinoscript/import-text-from-file/ Demonstrates how to import text from a file using RhinoScript. ```vbnet Sub ImportText Const ForReading = 1 Dim strFile, strText Dim objFSO, objFile Dim arrOrigin strFile = Rhino.OpenFileName("Open", "Text Files (*.txt)|*.txt|") If IsNull(strFile) Then Exit Sub arrOrigin = Rhino.GetPoint("Start point") If Not IsArray(arrOrigin) Then Exit Sub Set objFSO = CreateObject("Scripting.FileSystemObject") On Error Resume Next Set objFile = objFSO.OpenTextFile(strFile, ForReading) If Err Then MsgBox Err.Description Exit Sub End If While Not objFile.AtEndOfStream strText = strText & objFile.ReadLine If Not objFile.AtEndOfStream Then strText = strText & VbCrLf End If Wend objFile.Close Set objFile = Nothing Set objFSO = Nothing If Len(strText) > 0 Then Rhino.AddText strText, arrOrigin End If End Sub ``` -------------------------------------------------------------------------------- # Importing Lightweight Extrusions Source: https://developer.rhino3d.com/en/guides/opennurbs/importing-lightweight-extrusions/ This guide demonstrates how to convert openNURBS Lightweight Extrusion objects into Breps for importing. ## Question I was try to add some code for handling the `ON::extrusion_object` type object. But, I don't now know which API I could use to get data from extrusion object. I know there was a new class `ON_Extrusion` in *opennurbs_beam.cpp*. But I did not know how to use it. Can you give me some suggestion or some sample code? I would like know how should I import extrusion object? ## Answer In most cases, you will want to convert the extrusion object to a Brep and then just pass the Brep to the Brep handling code that you've already written, for example: ```cpp ONX_Model model = ... ONX_ModelComponentIterator it(model, ON_ModelComponent::Type::ModelGeometry); const ON_ModelComponent* model_component = nullptr; for (model_component = it.FirstComponent(); nullptr != model_component; model_component = it.NextComponent()) { const ON_ModelGeometryComponent* model_geometry = ON_ModelGeometryComponent::Cast(model_component); if (nullptr != model_geometry) { // Test for extrusion object const ON_Extrusion* extrusion = ON_Extrusion::Cast(model_geometry->Geometry(nullptr)); if (nullptr != extrusion) { ON_Brep* brep = ON_Brep::New(); if (brep != extrusion->BrepForm(brep, true)) { delete brep; // don't leak... continue; } // TODO: do something with Brep here... delete brep; // don't leak... } } } ``` -------------------------------------------------------------------------------- # Importing Points from Text Files Source: https://developer.rhino3d.com/en/guides/rhinoscript/importing-points-from-txt-files/ This guide demonstration of how to open a text file and import data from it into Rhino using RhinoScript. ## Overview One common task performed by Rhino users is the importing of point coordinates from some kind of delimited file. This task is easy if you use the Points File file type when opening or importing files. But what if the file you are importing does not conform to the traditional delimited file notation. Or, what if the file contains other information? The key to understanding how to import data from a text is understanding the File System Object included as part of the Microsoft Scripting Runtime. The File System Object simplifies the task of dealing with any type of file input or output and for dealing with the system file structure itself. To access the File System Object model you must first create an instance of the `FileSystemObject` object, the only externally creatable object in the model. For more information on the `FileSystemObject`, see [Microsoft's VBScript documentation on MSDN](https://msdn.microsoft.com/en-us/library/2z9ffy99(v=vs.84).aspx). ## Example The following code example demonstrates how to import point coordinates from a text file. This can be easily modified to accommodate other information... ```vbnet Option Explicit '--------------------------------------------------------------- ' Subroutine: ImportPoints ' Purpose: Import points from a text file. '--------------------------------------------------------------- Sub ImportPoints ' Prompt the user for a file to import Dim strFilter, strFileName strFilter = "Text File (*.txt)|*.txt|All Files (*.*)|*.*|" strFileName = Rhino.OpenFileName("Open Point File", strFilter) If IsNull(strFileName) Then Exit Sub ' The the file system object Dim objFSO, objFile Set objFSO = CreateObject("Scripting.FileSystemObject") ' Try opening the text file On Error Resume Next Set objFile = objFSO.OpenTextFile(strFileName, 1) If Err Then MsgBox Err.Description Exit Sub End If Rhino.EnableRedraw False ' Read each line from the file Dim strLine, arrPoint Do While objFile.AtEndOfStream <> True strLine = objFile.ReadLine If Not IsNull(strLine) Then ' Remove any double-quote characters strLine = Replace(strLine, Chr(34), , 1) ' Convert the string to a 3D point arrPoint = Rhino.Str2Pt(strLine) ' Add the point to Rhino If IsArray(arrPoint) Then ' AddPoint will add a point object to Rhino Rhino.AddPoint arrPoint End If End If Loop Rhino.EnableRedraw True objFile.Close Set objFile = Nothing Set objFSO = Nothing End Sub ``` ## Related Topics - [Microsoft's VBScript documentation on MSDN](https://msdn.microsoft.com/en-us/library/2z9ffy99(v=vs.84).aspx) -------------------------------------------------------------------------------- # Including Scripts Source: https://developer.rhino3d.com/en/guides/rhinoscript/including-scripts/ This guide discusses how to include or use functions from another source file in RhinoScript. ## Problem In programming languages like C/C++ and C#, there are statements like `#include` and `using` where you can reference functions from other source files. This is often used when you want to build a library of common functions that can be used from within other scripts, VBScript does not have an equivalent to the C/C++ `#include` statement. But, its possible to write your own include-type function. ## Solution The following subroutine can be used to include scripts... ```vbnet ' --------------------------------------------------------------------------- ' Subroutine: Include ' Purpose: Includes, or loads, other RhinoScript files ' Argument: A script file name to include ' Example: Call Include("C:\Scripts\MyScriptFile.rvb") ' --------------------------------------------------------------------------- Sub Include(strScriptName) Dim objFSO, objFile Set objFSO = CreateObject("Scripting.FileSystemObject") Set objFile = objFSO.OpenTextFile(strScriptName) ExecuteGlobal objFile.ReadAll() objFile.Close End Sub ``` Also, if you have a number of script files that define utility functions and procedures that you would like to call from any source file, then consider adding these script files to the list of startup script file (*Tools* > *Options* > *RhinoScript*). -------------------------------------------------------------------------------- # Increase NURBS Curve Degree Source: https://developer.rhino3d.com/en/samples/rhinocommon/increase-nurbs-curve-degree/ Demonstrates how to increase the degree of a NURBS curve. ```cs partial class Examples { public static Result NurbsCurveIncreaseDegree(RhinoDoc doc) { ObjRef obj_ref; var rc = RhinoGet.GetOneObject( "Select curve", false, ObjectType.Curve, out obj_ref); if (rc != Result.Success) return rc; if (obj_ref == null) return Result.Failure; var curve = obj_ref.Curve(); if (curve == null) return Result.Failure; var nurbs_curve = curve.ToNurbsCurve(); int new_degree = -1; rc = RhinoGet.GetInteger(string.Format("New degree <{0}...11>", nurbs_curve.Degree), true, ref new_degree, nurbs_curve.Degree, 11); if (rc != Result.Success) return rc; rc = Result.Failure; if (nurbs_curve.IncreaseDegree(new_degree)) if (doc.Objects.Replace(obj_ref.ObjectId, nurbs_curve)) rc = Result.Success; RhinoApp.WriteLine("Result: {0}", rc.ToString()); doc.Views.Redraw(); return rc; } } ``` ```python from Rhino.Commands import Result from Rhino.Input import RhinoGet from Rhino.DocObjects import ObjectType from scriptcontext import doc def RunCommand(): rc, obj_ref = RhinoGet.GetOneObject("Select curve", False, ObjectType.Curve) if rc != Result.Success: return rc if obj_ref == None: return Result.Failure curve = obj_ref.Curve() if curve == None: return Result.Failure nurbs_curve = curve.ToNurbsCurve() new_degree = -1 rc, new_degree = RhinoGet.GetInteger("New degree <{0}...11>".format(nurbs_curve.Degree), True, new_degree, nurbs_curve.Degree, 11) if rc != Result.Success: return rc rc = Result.Failure if nurbs_curve.IncreaseDegree(new_degree): if doc.Objects.Replace(obj_ref.ObjectId, nurbs_curve): rc = Result.Success print("Result: {0}".format(rc)) doc.Views.Redraw() return rc if __name__ == "__main__": RunCommand() ``` -------------------------------------------------------------------------------- # Increase NURBS Surface Degree Source: https://developer.rhino3d.com/en/samples/rhinocommon/increase-nurbs-surface-degree/ Demonstrates how to increase the degree of a NURBS surface. ```cs partial class Examples { public static Result NurbsSurfaceIncreaseDegree(RhinoDoc doc) { ObjRef obj_ref; var rc = RhinoGet.GetOneObject( "Select surface", false, ObjectType.Surface, out obj_ref); if (rc != Result.Success) return rc; if (obj_ref == null) return Result.Failure; var surface = obj_ref.Surface(); if (surface == null) return Result.Failure; var nurbs_surface = surface.ToNurbsSurface(); int new_u_degree = -1; rc = RhinoGet.GetInteger(string.Format("New U degree <{0}...11>", nurbs_surface.Degree(0)), true, ref new_u_degree, nurbs_surface.Degree(0), 11); if (rc != Result.Success) return rc; int new_v_degree = -1; rc = RhinoGet.GetInteger(string.Format("New V degree <{0}...11>", nurbs_surface.Degree(1)), true, ref new_v_degree, nurbs_surface.Degree(1), 11); if (rc != Result.Success) return rc; rc = Result.Failure; if (nurbs_surface.IncreaseDegreeU(new_u_degree)) if (nurbs_surface.IncreaseDegreeV(new_v_degree)) if (doc.Objects.Replace(obj_ref.ObjectId, nurbs_surface)) rc = Result.Success; RhinoApp.WriteLine("Result: {0}", rc.ToString()); doc.Views.Redraw(); return rc; } } ``` ```python from Rhino.Commands import Result from Rhino.Input import RhinoGet from Rhino.DocObjects import ObjectType from scriptcontext import doc def RunCommand(): rc, obj_ref = RhinoGet.GetOneObject("Select surface", False, ObjectType.Surface) if rc != Result.Success: return rc if obj_ref == None: return Result.Failure surface = obj_ref.Surface() if surface == None: return Result.Failure nurbs_surface = surface.ToNurbsSurface() new_u_degree = -1 rc, new_u_degree = RhinoGet.GetInteger("New U degree <{0}...11>".format(nurbs_surface.Degree(0)), True, new_u_degree, nurbs_surface.Degree(0), 11) if rc != Result.Success: return rc new_v_degree = -1 rc, new_v_degree = RhinoGet.GetInteger("New V degree <{0}...11>".format(nurbs_surface.Degree(1)), True, new_v_degree, nurbs_surface.Degree(1), 11) if rc != Result.Success: return rc rc = Result.Failure if nurbs_surface.IncreaseDegreeU(new_u_degree): if nurbs_surface.IncreaseDegreeV(new_v_degree): if doc.Objects.Replace(obj_ref.ObjectId, nurbs_surface): rc = Result.Success print("Result: {0}".format(rc)) doc.Views.Redraw() return rc if __name__=="__main__": RunCommand() ``` -------------------------------------------------------------------------------- # Insert Knot Source: https://developer.rhino3d.com/en/samples/rhinocommon/insert-knot/ Demonstrates how to insert a knot into a user-selected NURBS curve. ```cs partial class Examples { public static Rhino.Commands.Result InsertKnot(Rhino.RhinoDoc doc) { const ObjectType filter = Rhino.DocObjects.ObjectType.Curve; Rhino.DocObjects.ObjRef objref; Result rc = Rhino.Input.RhinoGet.GetOneObject("Select curve for knot insertion", false, filter, out objref); if (rc != Rhino.Commands.Result.Success) return rc; Rhino.Geometry.Curve curve = objref.Curve(); if (null == curve) return Rhino.Commands.Result.Failure; Rhino.Geometry.NurbsCurve nurb = curve.ToNurbsCurve(); if (null == nurb) return Rhino.Commands.Result.Failure; Rhino.Input.Custom.GetPoint gp = new Rhino.Input.Custom.GetPoint(); gp.SetCommandPrompt("Point on curve to add knot"); gp.Constrain(nurb, false); gp.Get(); if (gp.CommandResult() == Rhino.Commands.Result.Success) { double t; Rhino.Geometry.Curve crv = gp.PointOnCurve(out t); if( crv!=null && nurb.Knots.InsertKnot(t) ) { doc.Objects.Replace(objref, nurb); doc.Views.Redraw(); } } return Rhino.Commands.Result.Success; } } ``` ```python import Rhino import scriptcontext def InsertKnot(): filter = Rhino.DocObjects.ObjectType.Curve rc, objref = Rhino.Input.RhinoGet.GetOneObject("Select curve for knot insertion", False, filter) if rc != Rhino.Commands.Result.Success: return rc curve = objref.Curve() if not curve: return Rhino.Commands.Result.Failure nurb = curve.ToNurbsCurve() if not nurb: return Rhino.Commands.Result.Failure gp = Rhino.Input.Custom.GetPoint() gp.SetCommandPrompt("Point on curve to add knot") gp.Constrain(nurb, False) gp.Get() if gp.CommandResult() == Rhino.Commands.Result.Success: crv, t = gp.PointOnCurve() if crv and nurb.Knots.InsertKnot(t): scriptcontext.doc.Objects.Replace(objref, nurb) scriptcontext.doc.Views.Redraw() return Rhino.Commands.Result.Success if __name__=="__main__": InsertKnot() ``` -------------------------------------------------------------------------------- # Installing Tools (Windows) Source: https://developer.rhino3d.com/en/guides/cpp/installing-tools-windows/ This guide covers all the necessary tools required to author Rhino plugins in C/C++ on Windows. By the end of this guide, you should have all the tools installed necessary for authoring, building, and debugging C/C++ plugins using the Rhino C/C++ SDK on Windows. ## Prerequisites This guide presumes you have: ### Rhino 8 - A PC running Microsoft Windows 10 or later. - [Rhino 8 for Windows](https://www.rhino3d.com/download). ### Rhino 7 - A PC running Microsoft Windows 8.1 or later. - [Rhino 7 for Windows](https://www.rhino3d.com/download). ## Install Visual Studio ### Rhino 8 To write C++ plugins for Rhino 8 using the Rhino 8 C/C++ SDK, you will need a version of Microsoft Visual Studio that includes the Visual Studio 2019 (v142) platform toolset. Thus, you can use either [Visual Studio 2022](https://visualstudio.microsoft.com/downloads/) or [Visual Studio 2019](https://visualstudio.microsoft.com/vs/older-downloads/). 1. Download **Microsoft Visual Studio**, either [Visual Studio 2022](https://visualstudio.microsoft.com/downloads/) or [Visual Studio 2019](https://visualstudio.microsoft.com/vs/older-downloads/). 2. Run the **Visual Studio installer** you just downloaded. ![Visual Studio Install](https://developer.rhino3d.com/images/installing-tools-windows-cpp-01.png) 3. Follow the onscreen prompts to install Visual Studio. 4. Check the **Desktop development with C++** workload. 5. Click the **Individual components** tab. 6. Scroll to the **Compilers, build tools, and runtimes** section and check the following option: 1. MSVC v142 - VS 2019 C++ x64/x86 build tools 7. Scroll to the **SDKs, libraries, and frameworks** section and check the following options: 1. C++ v14.2 ATL for v142 build tools (x86 & x64) 2. C++ v14.2 MFC for v142 build tools (x86 & x64) 3. Windows 10 SDK 8. Check any additional features required for your project. 9. When finished, click **Install**. 10. Depending on your internet connection, this can take several minutes to complete. If you already have Microsoft Visual Studio 2022 or 2019 installed, then you will want to re-run the **Visual Studio Installer** and verify you have all the the components installed. ### Rhino 7 To write C++ plugins for Rhino 7 using the Rhino 7 C/C++ SDK, you will need [Microsoft Visual Studio 2019](https://visualstudio.microsoft.com/vs/older-downloads/). 1. Download **[Visual Studio 2019](https://visualstudio.microsoft.com/vs/older-downloads/)**. 2. Run the **Visual Studio installer** you just downloaded. ![Visual Studio Install](https://developer.rhino3d.com/images/installing-tools-windows-cpp-02.png) 3. Follow the onscreen prompts to install Visual Studio. 4. Check the **Desktop development with C++** workload. 5. Click the **Individual components** tab. 6. Scroll to the **SDKs, libraries, and frameworks** section and check the following option: 1. Visual Studio SDK 7. Check any additional features required for your project. 8. When finished, click **Install**. 9. Depending on your internet connection, this can take several minutes to complete. If you already have Microsoft Visual Studio 2019 installed, then you will want to re-run the **Visual Studio Installer** and verify you have all the the components installed. ## Install the Rhino C/C++ SDK The **Rhino C/C++ SDK** is a set of tools for creating plugin using the C++ language. The SDK includes headers, libraries and Visual Studio project wizards to get you started creating plugins quickly. ### Rhino 8 1. Exit **Visual Studio**. 2. Download the **[Rhino 8 C/C++ SDK](https://www.rhino3d.com/download/rhino-sdk/8/latest/)**. 3. Run the **SDK installer** you downloaded. 4. Download the **[Rhino Visual Studio Extension (VSIX)](https://github.com/mcneel/RhinoVisualStudioExtensions/releases)**. 5. Run the **VSIX installer** you downloaded. 6. If the installation is successful, run Visual Studio. ### Rhino 7 1. Exit **Visual Studio**. 2. Download the **[Rhino 7 C/C++ SDK](https://www.rhino3d.com/download/Rhino-SDK/7.0/latest)**. 3. Run the **SDK installer** you downloaded. 4. If the installation is successful, run Visual Studio. ## Next Steps **Congratulations!** You have the tools to build a C/C++ plugin for Rhino for Windows. *Now what?* Check out the [Creating your first C/C++ plugin for Rhino](/guides/cpp/your-first-plugin-windows/) guide for instructions building your first plugin. -------------------------------------------------------------------------------- # Installing Tools (Windows) Source: https://developer.rhino3d.com/en/guides/grasshopper/installing-tools-windows/ This guide covers all the necessary tools required to author custom Grasshopper components on Windows. By the end of this guide, you should have all the tools installed necessary for authoring, building, and debugging custom Grasshopper components in Rhino for Windows. ## Prerequisites This guide presumes you have an: - A PC running Microsoft Windows 10 or later. - [Rhino 7 for Windows](https://www.rhino3d.com/download) ## Install Visual Studio [Visual Studio](https://www.visualstudio.com/en-us/visual-studio-homepage-vs.aspx) is Microsoft's flagship development platform and Integrated Development Environment (IDE). Visual Studio now comes in three major "streams": Visual Studio Code[^1], Visual Studio Online[^2], and Visual Studio "proper"[^3]. In order to author custom Grasshopper components, you will need Visual Studio "proper" (Visual Studio Code and Visual Studio Online are not supported). For the purposes of this guide, we will presume you are using Visual Studio 2022 Community Edition. #### Step-by-Step 1. **[Visual Studio Community Edition](https://visualstudio.microsoft.com/vs/)** is free from Microsoft for students, open-source contributors, and small teams. [Details here](https://www.visualstudio.com/en-us/support/legal/mt171547). Click the **Community** button to download the installer. 1. Run the **Visual Studio installer** you downloaded from Microsoft, in this case *VisualStudioSetup.exe*. 1. Follow the onscreen prompts to install Visual Studio. You will need the ".NET desktop development" workload for RhinoCommon based plug-in development. When successfully installed, click the *Launch* button. ## Grasshopper Templates The [RhinoCommon and Grasshopper templates for Rhino 7](https://marketplace.visualstudio.com/items?itemName=McNeel.Rhino7Templates2022) contains wizards to get you started creating components quickly. #### Step-by-Step 1. Launch **Visual Studio**. 1. Navigate to **Extensions** > **Manage Extensions** 1. In the left-hand sidebar, expand the **Online** section, then select the **Visual Studio Marketplace** entry... ![Extensions and Updates](https://developer.rhino3d.com/images/installing-tools-windows-grasshopper-01.png) 1. In the **Search** field, search for *RhinoCommon*. This filters the gallery list below. 1. Find **RhinoCommon and Grasshopper templates for Rhino 7** and select it. 1. Click the **Download** button. The extension installation should begin. 1. You must **Accept** the license agreement by clicking on the **Install** button. 1. Press the **Close** button and **Quit** Visual Studio. 1. The extension installer should start once you quit. Click the **Modify** button to install the extension. 1. Once this is done, the extension should appear in your list of **Installed** extensions in **Extensions** > **Manage Extensions**. ## Next Steps **Congratulations!** You have the tools to build custom Grasshopper components for Grasshopper for Windows. **Now what?** Check out the [Your First Component (Windows)](/guides/grasshopper/your-first-component-windows) guide for instructions building - your guessed it - your first component. **Footnotes** [^1]: Visual Studio Code is Microsoft's cross-platform source code editor for Windows, Linux, and macOS. At the time of this writing, Visual Studio code does not yet support the features required to author Grasshopper components [^2]: Visual Studio Online is Microsoft's online counterpart to the desktop edition of Visual Studio (referred to as Visual Studio "proper" above). We have not tested using Visual Studio Online to debug Grasshopper components as having a copy of Rhino and Grasshopper running would prove logistically difficult. [^3]: Visual Studio "proper" is the desktop version of Visual Studio...we are only attaching the "proper" epithet to distinguish it from the Visual Studio Code and Visual Studio Online. In subsequent guides this will be referred to as simply "Visual Studio." -------------------------------------------------------------------------------- # Installing Tools (Windows) Source: https://developer.rhino3d.com/en/guides/rhinocommon/installing-tools-windows/ This guide covers the tools required to author, build and debug Rhino plugins on Windows. By the end of this guide, you should have all the tools installed necessary for authoring, building, and debugging .NET plugins using RhinoCommon on Windows. ## Prerequisites This guide presumes you have: ### Rhino 8 - A PC running Microsoft Windows 10 or later. - [Rhino 8 for Windows](https://www.rhino3d.com/download). ### Rhino 7 - A PC running Microsoft Windows 8.1 or later. - [Rhino 7 for Windows](https://www.rhino3d.com/download). ## Install Visual Studio To write .NET plugins for Rhino using using RhinoCommon, you will Microsoft Visual Studio. As of this writing, the current version is [Visual Studio 2022](https://visualstudio.microsoft.com/downloads/). 1. Download [**Microsoft Visual Studio**](https://visualstudio.microsoft.com/downloads/). 2. Run the **Visual Studio installer** you just downloaded. ![Visual Studio Install](https://developer.rhino3d.com/images/installing-tools-windows-rhinocommon-01.png) 3. Follow the onscreen prompts to install Visual Studio. 4. Check the **.NET desktop development** workload. 5. Click the **Individual components** tab. 6. Scroll to the **.NET** section and check the following options: 1. .NET 7.0 Runtime 2. .NET Framework 4.8 SDK 3. .NET Framework 4.8 targeting pack 7. Check any additional features required for your project. 8. When finished, click **Install**. 9. Depending on your internet connection, this can take several minutes to complete. If you already have Microsoft Visual Studio installed, then you will want to re-run the **Visual Studio Installer** and verify you have all the the components installed. ## Installing Visual Studio Extension The **Rhino Visual Studio Extension** contains templates to get you started creating plugin projects quickly. 1. Download the **[Rhino Visual Studio Extension (VSIX)](https://github.com/mcneel/RhinoVisualStudioExtensions/releases)**. 2. Run the **VSIX installer** you downloaded. 3. If the installation is successful, run Visual Studio. ## Next Steps *Congratulations!* You have the tools to build a RhinoCommon plugin for Rhino for Windows. *Now what?* Check out the [Your First Plugin (Windows)](/guides/rhinocommon/your-first-plugin-windows) guide for instructions on how to build your first plugin. -------------------------------------------------------------------------------- # Instance Archive File Status Source: https://developer.rhino3d.com/en/samples/rhinocommon/instance-archive-file-status/ Demonstrates how to find the status of a file that contains an instance (block) definition. ```cs partial class Examples { public static Rhino.Commands.Result InstanceArchiveFileStatus(Rhino.RhinoDoc doc) { for (int i = 0; i < doc.InstanceDefinitions.Count; i++) { Rhino.DocObjects.InstanceDefinition iDef = doc.InstanceDefinitions[i]; Rhino.DocObjects.InstanceDefinitionArchiveFileStatus iDefStatus = iDef.ArchiveFileStatus; string status = "Unknown"; switch (iDefStatus) { case Rhino.DocObjects.InstanceDefinitionArchiveFileStatus.NotALinkedInstanceDefinition: status = "not a linked instance definition."; break; case Rhino.DocObjects.InstanceDefinitionArchiveFileStatus.LinkedFileNotReadable: status = "archive file is not readable."; break; case Rhino.DocObjects.InstanceDefinitionArchiveFileStatus.LinkedFileNotFound: status = "archive file cannot be found."; break; case Rhino.DocObjects.InstanceDefinitionArchiveFileStatus.LinkedFileIsUpToDate: status = "archive file is up-to-date."; break; case Rhino.DocObjects.InstanceDefinitionArchiveFileStatus.LinkedFileIsNewer: status = "archive file is newer."; break; case Rhino.DocObjects.InstanceDefinitionArchiveFileStatus.LinkedFileIsOlder: status = "archive file is older."; break; case Rhino.DocObjects.InstanceDefinitionArchiveFileStatus.LinkedFileIsDifferent: status = "archive file is different."; break; } Rhino.RhinoApp.WriteLine("{0} - {1}", iDef.Name, status); } return Rhino.Commands.Result.Success; } } ``` ```python # No Python sample available ``` -------------------------------------------------------------------------------- # Instance Definition Names Source: https://developer.rhino3d.com/en/samples/rhinocommon/instance-definition-names/ Demonstrates how to print the instance definition names. ```cs partial class Examples { public static Result InstanceDefinitionNames(RhinoDoc doc) { var instance-definition-names = (from instance_definition in doc.InstanceDefinitions where instance_definition != null && !instance_definition.IsDeleted select instance_definition.Name); foreach (var n in instance-definition-names) RhinoApp.WriteLine("Instance definition = {0}", n); return Result.Success; } } ``` ```python from scriptcontext import doc def RunCommand(): instanceDefinitionNames = [instanceDefinition.Name for instanceDefinition in doc.InstanceDefinitions if instanceDefinition != None and not instanceDefinition.IsDeleted] for n in instanceDefinitionNames: print("instance definition = {0}".format(n)) if __name__ == "__main__": RunCommand() ``` -------------------------------------------------------------------------------- # Instance Definition Objects Source: https://developer.rhino3d.com/en/samples/rhinocommon/instance-definition-objects/ Demonstrates how to print (or list) the objects that make up a block definition. ```cs partial class Examples { public static Rhino.Commands.Result InstanceDefinitionObjects(Rhino.RhinoDoc doc) { Rhino.DocObjects.ObjRef objref; var rc = Rhino.Input.RhinoGet.GetOneObject("Select instance", false, Rhino.DocObjects.ObjectType.InstanceReference, out objref); if (rc != Rhino.Commands.Result.Success) return rc; var iref = objref.Object() as Rhino.DocObjects.InstanceObject; if (iref != null) { var idef = iref.InstanceDefinition; if (idef != null) { var rhino_objects = idef.GetObjects(); for (int i = 0; i < rhino_objects.Length; i++) Rhino.RhinoApp.WriteLine("Object {0} = {1}", i, rhino_objects[i].Id); } } return Rhino.Commands.Result.Success; } } ``` ```python import Rhino import scriptcontext def InstanceDefinitionObjects(): rc, objref = Rhino.Input.RhinoGet.GetOneObject("Select instance", False, Rhino.DocObjects.ObjectType.InstanceReference) if rc != Rhino.Commands.Result.Success: return iref = objref.Object() if iref: idef = iref.InstanceDefinition if idef: rhino_objects = idef.GetObjects() for i, rhobj in enumerate(rhino_objects): print("Object", i, "=", rhobj.Id) if __name__=="__main__": InstanceDefinitionObjects() ``` -------------------------------------------------------------------------------- # Instance Definition Tree Source: https://developer.rhino3d.com/en/samples/rhinocommon/instance-definition-tree/ Demonstrates how to list or enumerate the objects that make up a nested block definition. ```cs partial class Examples { public static Result InstanceDefinitionTree(RhinoDoc doc) { var instance_definitions = doc.InstanceDefinitions; var instance_definition_count = instance_definitions.Count; if (instance_definition_count == 0) { RhinoApp.WriteLine("Document contains no instance definitions."); return Result.Nothing; } var dump = new TextLog(); dump.IndentSize = 4; for (int i = 0; i < instance_definition_count; i++) DumpInstanceDefinition(instance_definitions[i], ref dump, true); RhinoApp.WriteLine(dump.ToString()); return Result.Success; } private static void DumpInstanceDefinition(InstanceDefinition instanceDefinition, ref TextLog dump, bool isRoot) { if (instanceDefinition != null && !instanceDefinition.IsDeleted) { string node = isRoot ? "─" : "└"; dump.Print(string.Format("{0} Instance definition {1} = {2}\n", node, instanceDefinition.Index, instanceDefinition.Name)); if (instanceDefinition.ObjectCount > 0) { dump.PushIndent(); for (int i = 0; i < instanceDefinition.ObjectCount ; i++) { var obj = instanceDefinition.Object(i); if (obj == null) continue; if (obj is InstanceObject) DumpInstanceDefinition((obj as InstanceObject).InstanceDefinition, ref dump, false); // Recursive... else dump.Print("\u2514 Object {0} = {1}\n", i, obj.ShortDescription(false)); } dump.PopIndent(); } } } } ``` ```python from scriptcontext import doc import Rhino def RunCommand(): instanceDefinitions = doc.InstanceDefinitions instanceDefinitionCount = instanceDefinitions.Count if instanceDefinitionCount == 0: print("Document contains no instance definitions.") return dump = Rhino.FileIO.TextLog() dump.IndentSize = 4 for i in range(0, instanceDefinitionCount): DumpInstanceDefinition(instanceDefinitions[i], dump, True) print(dump.ToString()) def DumpInstanceDefinition(instanceDefinition, dump, isRoot): if instanceDefinition != None and not instanceDefinition.IsDeleted: if isRoot: node = '-' else: node = '+' dump.Print(u"{0} Instance definition {1} = {2}\n".format(node, instanceDefinition.Index, instanceDefinition.Name)) if instanceDefinition.ObjectCount > 0: dump.PushIndent() for i in range(0, instanceDefinition.ObjectCount): obj = instanceDefinition.Object(i) if obj != None and type(obj) == Rhino.DocObjects.InstanceObject: DumpInstanceDefinition(obj.InstanceDefinition, dump, False) # Recursive... else: dump.Print(u"+ Object {0} = {1}\n".format(i, obj.ShortDescription(False))) dump.PopIndent() if __name__ == "__main__": RunCommand() ``` -------------------------------------------------------------------------------- # Instance References with Non-Uniform Scales Source: https://developer.rhino3d.com/en/guides/opennurbs/instance-references-with-non-uniform-scales/ This guide discusses non-uniform scaling issues when using the openNURBS toolkit ## Problem In Rhino, you can insert an instance of a block and give it a non-uniform scale (transformation). But, when you read the model, using the openNURBS toolkit, and try to explode the block into its geometric form, the geometry is no longer non-uniformly scaled. ## Solution Not all `ON_Geometry`-derived classes can be accurately modified with transformations like projections, shears, and non-uniform scaling. For example, if you were to apply a non-uniform scale to a circle, which is represented by an `ON_ArcCurve` curve, the resulting geometry is no longer a circle. When exploding an instance reference into its geometric form, first test to see if the instance reference's transformation is a similarity transformation. This can be done by using `ON_XForm::IsSimilarity()`. See *opennurbs_xform.h* for more information. If the transformation is not a similarity, then convert the geometry into a form that can be accurately modified. This can be done by using the `ON_Geometry::MakeDeformable()` override on the geometric object... ```cpp if (bNeedXform) { // If not a similarity transformation, make sure geometry // can be deformed. Generally, this involves convert non-NURBS // geometry into NURBS geometry. if (0 == xform.IsSimilarity() ) geometry->MakeDeformable(); // Do the transformation geometry->Transform(xform); } ``` -------------------------------------------------------------------------------- # Intersect Curves with Meshes Source: https://developer.rhino3d.com/en/samples/cpp/intersect-curve-with-mesh/ Demonstrates how to intersect a curve with a mesh. ```cpp CRhinoCommand::result CCommandTest::RunCommand( const CRhinoCommandContext& context ) { CRhinoGetObject gm; gm.SetCommandPrompt( L"Select mesh to intersect" ); gm.SetGeometryFilter( CRhinoGetObject::mesh_object ); gm.GetObjects( 1, 1 ); if( gm.CommandResult() != CRhinoCommand::success ) return gm.CommandResult(); const ON_Mesh* mesh = gm.Object(0).Mesh(); if( 0 == mesh ) return CRhinoCommand::failure; CRhinoGetObject gc; gc.SetCommandPrompt( L"Select curve to intersect with mesh" ); gc.SetGeometryFilter( CRhinoGetObject::curve_object ); gc.EnablePreSelect( false ); gc.EnableDeselectAllBeforePostSelect( false ); gc.GetObjects( 1, 1 ); if( gc.CommandResult() != CRhinoCommand::success ) return gc.CommandResult(); const ON_Curve* curve = gc.Object(0).Curve(); if( 0 == curve ) return CRhinoCommand::failure; double tol = context.m_doc.AbsoluteTolerance(); ON_PolylineCurve pline; if( RhinoConvertCurveToPolyline(*curve, 0, 0, 0, 0, 0.0, tol, 0.0, 0, pline) ) { const ON_MeshTree* mesh_tree = mesh->MeshTree(true); if( mesh_tree ) { ON_SimpleArray cmx; if( mesh_tree->IntersectPolyline(pline.m_pline.Count(), pline.m_pline.Array(), cmx) ) { int i; for( i = 0; i < cmx.Count(); i++ ) context.m_doc.AddPointObject( cmx[i].m_M[0].m_P ); context.m_doc.Redraw(); } else { RhinoApp().Print( L"Objects do not intersect.\n" ); } } } return CRhinoCommand::success; } ``` -------------------------------------------------------------------------------- # Intersect Line Curves Source: https://developer.rhino3d.com/en/samples/cpp/intersect-line-curves/ Demonstrates how to intersect line curves. ```cpp CRhinoCommand::result CCommandTest::RunCommand( const CRhinoCommandContext& context ) { CRhinoGetObject go; go.SetCommandPrompt( L"Select lines" ); go.SetGeometryFilter( ON::curve_object ); go.GetObjects( 2, 2 ); if( go.CommandResult() != success ) return go.CommandResult(); if( go.ObjectCount() != 2 ) return failure; const ON_LineCurve* crv0 = ON_LineCurve::Cast( go.Object(0).Geometry() ); const ON_LineCurve* crv1 = ON_LineCurve::Cast( go.Object(1).Geometry() ); if( 0 == crv0 | 0 == crv1 ) return failure; ON_Line line0 = crv0->m_line; ON_Line line1 = crv1->m_line; ON_3dVector v0 = line0.to - line0.from; v0.Unitize(); ON_3dVector v1 = line1.to - line1.from; v1.Unitize(); if( v0.IsParallelTo(v1) != 0 ) { RhinoApp().Print( L"Selected lines are parallel.\n" ); return nothing; } ON_Line ray0( line0.from, line0.from + v0 ); ON_Line ray1( line1.from, line1.from + v1 ); double s = 0, t = 0; if( !ON_Intersect(ray0, ray1, &s, &t) ) { RhinoApp().Print( L"No intersection found.\n" ); return nothing; } ON_3dPoint pt0 = line0.from + s * v0; ON_3dPoint pt1 = line1.from + t * v1; // pt0 and pt1 should be equal, so we will // only add pt0 to the document context.m_doc.AddPointObject( pt0 ); context.m_doc.Redraw(); return CRhinoCommand::success; } ``` -------------------------------------------------------------------------------- # Intersect Lines Source: https://developer.rhino3d.com/en/samples/rhinocommon/intersect-lines/ Demonstrates how to find the intersection point of two (non-parallel) lines. ```cs partial class Examples { public static Rhino.Commands.Result IntersectLines(Rhino.RhinoDoc doc) { Rhino.Input.Custom.GetObject go = new Rhino.Input.Custom.GetObject(); go.SetCommandPrompt( "Select lines" ); go.GeometryFilter = Rhino.DocObjects.ObjectType.Curve; go.GetMultiple( 2, 2); if( go.CommandResult() != Rhino.Commands.Result.Success ) return go.CommandResult(); if( go.ObjectCount != 2 ) return Rhino.Commands.Result.Failure; LineCurve crv0 = go.Object(0).Geometry() as LineCurve; LineCurve crv1 = go.Object(1).Geometry() as LineCurve; if( crv0==null || crv1==null ) return Rhino.Commands.Result.Failure; Line line0 = crv0.Line; Line line1 = crv1.Line; Vector3d v0 = line0.Direction; v0.Unitize(); Vector3d v1 = line1.Direction; v1.Unitize(); if( v0.IsParallelTo(v1) != 0 ) { Rhino.RhinoApp.WriteLine("Selected lines are parallel."); return Rhino.Commands.Result.Nothing; } double a, b; if( !Rhino.Geometry.Intersect.Intersection.LineLine(line0, line1, out a, out b)) { Rhino.RhinoApp.WriteLine("No intersection found."); return Rhino.Commands.Result.Nothing; } Point3d pt0 = line0.PointAt(a); doc.Objects.AddPoint( pt0 ); doc.Views.Redraw(); return Rhino.Commands.Result.Success; } } ``` ```python import Rhino import scriptcontext def IntersectLines(): go = Rhino.Input.Custom.GetObject() go.SetCommandPrompt( "Select lines" ) go.GeometryFilter = Rhino.DocObjects.ObjectType.Curve go.GetMultiple( 2, 2) if go.CommandResult()!=Rhino.Commands.Result.Success: return go.CommandResult() if go.ObjectCount!=2: return Rhino.Commands.Result.Failure crv0 = go.Object(0).Geometry() crv1 = go.Object(1).Geometry() if not crv0 or not crv1: return Rhino.Commands.Result.Failure line0 = crv0.Line line1 = crv1.Line v0 = line0.Direction v0.Unitize() v1 = line1.Direction v1.Unitize() if v0.IsParallelTo(v1)!=0: print("Selected lines are parallel.") return Rhino.Commands.Result.Nothing rc, a, b = Rhino.Geometry.Intersect.Intersection.LineLine(line0, line1) if not rc: print("No intersection found.") return Rhino.Commands.Result.Nothing pt0 = line0.PointAt(a) pt1 = line1.PointAt(b) # pt0 and pt1 should be equal, so we will only add pt0 to the document scriptcontext.doc.Objects.AddPoint(pt0) scriptcontext.doc.Views.Redraw() return Rhino.Commands.Result.Success if __name__=="__main__": IntersectLines() ``` -------------------------------------------------------------------------------- # Intersecting Line and Circle Source: https://developer.rhino3d.com/en/samples/rhinocommon/intersect-line-and-circle/ Demonstrates how to find the intersection point(s) of a circle and a line. ```cs partial class Examples { public static Result IntersectLineCircle(RhinoDoc doc) { Circle circle; var rc = RhinoGet.GetCircle(out circle); if (rc != Result.Success) return rc; doc.Objects.AddCircle(circle); doc.Views.Redraw(); Line line; rc = RhinoGet.GetLine(out line); if (rc != Result.Success) return rc; doc.Objects.AddLine(line); doc.Views.Redraw(); double t1, t2; Point3d point1, point2; var line_circle_intersect = Intersection.LineCircle(line, circle, out t1, out point1, out t2, out point2); string msg = ""; switch (line_circle_intersect) { case LineCircleIntersection.None: msg = "line does not intersect circle"; break; case LineCircleIntersection.Single: msg = string.Format("line intersects circle at point ({0})", point1); doc.Objects.AddPoint(point1); break; case LineCircleIntersection.Multiple: msg = string.Format("line intersects circle at points ({0}) and ({1})", point1, point2); doc.Objects.AddPoint(point1); doc.Objects.AddPoint(point2); break; } RhinoApp.WriteLine(msg); doc.Views.Redraw(); return Result.Success; } } ``` ```python # No Python sample available ``` -------------------------------------------------------------------------------- # Intersecting Meshes Source: https://developer.rhino3d.com/en/samples/cpp/intersecting-meshes/ Demonstrates how to intersect two meshes. ```cpp /* Description: Intersects two meshes. Parameters: meshA - [in] The first mesh. meshB - [in] The second mesh. OutCurves - [out] The resulting polyline curves. NOTE: THE CALLER IS RESPONSIBLE FOR DESTROYING THESE CURVES. tolerance - [in] The intersection tolerance. Returns: True if successful, false otherwise. */ static bool MeshMeshIntersection( const ON_Mesh* meshA, const ON_Mesh* meshB, ON_SimpleArray& OutCurves, double tolerance = ON_ZERO_TOLERANCE ) { if( 0 == meshA | 0 == meshB ) return false; ON_ClassArray transverse; ON_ClassArray overlap; bool rc = ON_MeshMeshIntersect( meshA, meshB, transverse, overlap, tolerance, tolerance ); if( !rc ) return rc; ON_ClassArray& arr = transverse; int i, j, k; for( i = 0; i < 2; i++ ) { if( i == 1 ) arr = overlap; int ct = arr.Count(); for( j = 0; j < ct; j++ ) { ON_MMX_Polyline& mmxpline = arr[j]; if( 0 == mmxpline.Count() ) continue; ON_Polyline polyline; for( k = 0; k < mmxpline.Count(); k++ ) { double x = mmxpline[k].m_A.m_P.x; double y = mmxpline[k].m_A.m_P.y; double z = mmxpline[k].m_A.m_P.z; polyline.Append( ON_3dPoint(x,y,z) ); } if( false == polyline.IsValid() ) continue; ON_PolylineCurve* polyline_crv = new ON_PolylineCurve( polyline ); if( 0 == polyline_crv ) continue; OutCurves.Append( polyline_crv ); } } return ( OutCurves.Count() ) ? true : false; } CRhinoCommand::result CCommandTest::RunCommand( const CRhinoCommandContext& context ) { CRhinoGetObject go; go.SetCommandPrompt( L"Select meshes to intersect" ); go.SetGeometryFilter( CRhinoGetObject::mesh_object ); go.GetObjects( 2, 2 ); if( go.CommandResult() != success ) return go.CommandResult(); const ON_Mesh* meshA = go.Object(0).Mesh(); const ON_Mesh* meshB = go.Object(1).Mesh(); if( 0 == meshA | 0 == meshA ) return failure; ON_SimpleArray OutCurves; bool rc = MeshMeshIntersection( meshA, meshB, OutCurves ); if( rc ) { int i; for( i = 0; i < OutCurves.Count(); i++ ) { CRhinoCurveObject* curve_obj = new CRhinoCurveObject(); curve_obj->SetCurve( OutCurves[i] ); context.m_doc.AddObject( curve_obj ); } context.m_doc.Redraw(); } return success; } ``` -------------------------------------------------------------------------------- # Is Dynamic Array Dimensioned Source: https://developer.rhino3d.com/en/samples/rhinoscript/is-dynamic-array-dimensioned/ Demonstrates how to determine of a dynamic array has been dimensioned. ```vbnet ' Returns a Boolean value indicating whether an ' array has been dimensioned - determine ' whether or not the array has an upper bound. ' Function IsUpperBound(ByVal arr) IsUpperBound = False If IsArray(arr) Then On Error Resume Next UBound arr If Err.Number = 0 Then IsUpperBound = True End If End Function ``` -------------------------------------------------------------------------------- # Is Planar Surface in Plane Source: https://developer.rhino3d.com/en/samples/rhinocommon/is-planar-surface-in-plane/ Demonstrates how to determine if a user-selected surface is in plane. ```cs partial class Examples { public static Result IsPlanarSurfaceInPlane(RhinoDoc doc) { ObjRef obj_ref; var rc = RhinoGet.GetOneObject("select surface", true, ObjectType.Surface, out obj_ref); if (rc != Result.Success) return rc; var surface = obj_ref.Surface(); Point3d[] corners; rc = RhinoGet.GetRectangle(out corners); if (rc != Result.Success) return rc; var plane = new Plane(corners[0], corners[1], corners[2]); var is_or_isnt = " not "; if (IsSurfaceInPlane(surface, plane, doc.ModelAbsoluteTolerance)) is_or_isnt = ""; RhinoApp.WriteLine("Surface is{0} in plane.", is_or_isnt); return Result.Success; } private static bool IsSurfaceInPlane(Surface surface, Plane plane, double tolerance) { if (!surface.IsPlanar(tolerance)) return false; var bbox = surface.GetBoundingBox(true); return bbox.GetCorners().All(c => System.Math.Abs(plane.DistanceTo(c)) <= tolerance); } } ``` ```python import Rhino from Rhino.Geometry import Plane import rhinoscriptsyntax as rs from scriptcontext import doc import math def RunCommand(): surface_id = rs.GetSurfaceObject()[0] if surface_id == None: return surface = rs.coercesurface(surface_id) corners = rs.GetRectangle() if corners == None: return plane = Plane(corners[0], corners[1], corners[2]) is_or_isnt = "" if IsSurfaceInPlane(surface, plane, doc.ModelAbsoluteTolerance) else " not " print("Surface is{0} in plane.".format(is_or_isnt)) def IsSurfaceInPlane(surface, plane, tolerance): if not surface.IsPlanar(tolerance): return False bbox = surface.GetBoundingBox(True) rc = True for corner in bbox.GetCorners(): if math.fabs(plane.DistanceTo(corner)) > tolerance: rc = False break return rc if __name__ == "__main__": RunCommand() ``` -------------------------------------------------------------------------------- # IsBrepBox Test Source: https://developer.rhino3d.com/en/samples/rhinocommon/isbrepbox-test/ Demonstrates how to determine whether a given Brep is a box. ```cs partial class Examples { private static bool IsBrepBox(Rhino.Geometry.Brep brep) { const double zero_tolerance = 1.0e-6; // or whatever bool rc = brep.IsSolid; if( rc ) rc = brep.Faces.Count == 6; var N = new Rhino.Geometry.Vector3d[6]; for (int i = 0; rc && i < 6; i++) { Rhino.Geometry.Plane plane; rc = brep.Faces[i].TryGetPlane(out plane, zero_tolerance); if( rc ) { N[i] = plane.ZAxis; N[i].Unitize(); } } for (int i = 0; rc && i < 6; i++) { int count = 0; for (int j = 0; rc && j < 6; j++) { double dot = Math.Abs(N[i] * N[j]); if (dot <= zero_tolerance) continue; if (Math.Abs(dot - 1.0) <= zero_tolerance) count++; else rc = false; } if (rc) { if (2 != count) rc = false; } } return rc; } public static Rhino.Commands.Result IsBrepBox(Rhino.RhinoDoc doc) { Rhino.DocObjects.ObjRef obj_ref; var rc = Rhino.Input.RhinoGet.GetOneObject("Select Brep", true, Rhino.DocObjects.ObjectType.Brep, out obj_ref); if (rc == Rhino.Commands.Result.Success) { var brep = obj_ref.Brep(); if (brep != null) { Rhino.RhinoApp.WriteLine(IsBrepBox(brep) ? "Yes it is a box" : "No it is not a box"); } } return rc; } } ``` ```python import Rhino def IsBrepBox(brep): zero_tolerance = 1.0e-6 #or whatever rc = brep.IsSolid if rc: rc = brep.Faces.Count == 6 N = [] for i in range(6): if not rc: break rc, plane = brep.Faces[i].TryGetPlane(zero_tolerance) if rc: v = plane.ZAxis v.Unitize() N.append(v) for i in range(6): count = 0 for j in range(6): if not rc: break dot = abs(N[i] * N[j]) if dot<=zero_tolerance: continue if abs(dot-1.0)<=zero_tolerance: count += 1 else: rc = False if rc: if 2!=count: rc = False return rc if __name__=="__main__": rc, objref = Rhino.Input.RhinoGet.GetOneObject("Select Brep", True, Rhino.DocObjects.ObjectType.Brep) if rc==Rhino.Commands.Result.Success: brep = objref.Brep() if brep: if IsBrepBox(brep): print("Yes it is a box") else: print("No it is not a box") ``` -------------------------------------------------------------------------------- # Isocurve Density Source: https://developer.rhino3d.com/en/samples/rhinocommon/isocurve-density/ Demonstrates how to adjust the the isocurve density of a user-specified surface. ```cs partial class Examples { public static Rhino.Commands.Result IsocurveDensity(Rhino.RhinoDoc doc) { Rhino.DocObjects.ObjRef objref; var rc = Rhino.Input.RhinoGet.GetOneObject("Select surface", false, Rhino.DocObjects.ObjectType.Surface, out objref); if( rc!= Rhino.Commands.Result.Success ) return rc; var brep_obj = objref.Object() as Rhino.DocObjects.BrepObject; if( brep_obj!=null ) { brep_obj.Attributes.WireDensity = 3; brep_obj.CommitChanges(); doc.Views.Redraw(); } return Rhino.Commands.Result.Success; } } ``` ```python import Rhino import scriptcontext def IsocurveDensity(): rc, objref = Rhino.Input.RhinoGet.GetOneObject("Select surface", False, Rhino.DocObjects.ObjectType.Surface) if rc!= Rhino.Commands.Result.Success: return brep_obj = objref.Object() if brep_obj: brep_obj.Attributes.WireDensity = 3 brep_obj.CommitChanges() scriptcontext.doc.Views.Redraw() if __name__=="__main__": IsocurveDensity() ``` -------------------------------------------------------------------------------- # Isolate Layers Source: https://developer.rhino3d.com/en/samples/rhinoscript/isolate-layers/ Demonstrates how to isolate the layers of selected objects using RhinoScript. ```vbnet Option Explicit Sub IsolateLayers() ' Select objects Dim objects objects = Rhino.GetObjects("Select objects on layers to isolate", , True, True) If Not IsArray(objects) Then Exit Sub ' Determine the number of selected objects Dim bound bound = UBound(objects) ' Create an array to the object layer names Dim object_layers() ReDim object_layers( bound + 1 ) ' Fill the array Dim i For i = 0 To bound object_layers(i) = Rhino.ObjectLayer(objects(i)) Next ' Don't forget to include current layer object_layers(bound + 1) = Rhino.CurrentLayer ' Cull any duplicate layer names Dim culled_layers culled_layers = Rhino.CullDuplicateStrings(object_layers) ' Create an array containing all of the layer names Dim all_layers all_layers = Rhino.LayerNames ' For each layer name, search the list of culled layer names. ' If the layer name is not found, then turn it off. Dim layer For Each layer In all_layers If UBound(Filter(culled_layers, layer)) < 0 Then Rhino.LayerMode layer, 1 End If Next End Sub ``` -------------------------------------------------------------------------------- # Isometric Views Source: https://developer.rhino3d.com/en/guides/rhinoscript/isometric-views/ This guide demonstrates how to create isometric views using RhinoScript. ## Overview AutoCAD has a VPOINT command that allows you to create isometric views of the model. The VPOINT command uses the point entered by the user to create a vector that defines the direction from which the drawing is viewed. You can do this in Rhino using the ViewportProperties command. In the ViewportProperties dialog, first set the view to parallel projection. Then, set the target location to 0,0,0 and the camera location to where you want to be viewing from. If this seems too cumbersome, then you can also use the following RhinoScript subroutine... ## Example The following example subroutine mimics the VPOINT command using RhinoScript... ```vbnet Sub VPoint Dim strView strView = Rhino.CurrentView If Rhino.ViewProjection(strView) = 2 Then Rhino.Print "Viewport must be set for parallel projection." Exit Sub End If Dim arrOptions arrOptions = Array("NE Isometric", "NW Isometric", "SE Isometric", "SW Isometric", "User Defined") Dim strOption strOption = Rhino.ListBox(arrOptions, "Select viewing direction", "VPoint") If IsNull(strOption) Then Exit Sub Dim arrCamera Select Case strOption Case "NE Isometric" arrCamera = Array( 1, 1,1) Case "NW Isometric" arrCamera = Array(-1, 1,1) Case "SE Isometric" arrCamera = Array( 1,-1,1) Case "SW Isometric" arrCamera = Array(-1,-1,1) Case Else arrCamera = Rhino.GetPoint("View point") End Select If Not IsArray(arrCamera) Then Exit Sub Dim arrTarget, v arrTarget = Array(0,0,0) v = Rhino.VectorCreate(arrCamera, arrTarget) If Rhino.IsVectorTiny(v) Then Exit Sub Rhino.EnableRedraw False Rhino.ViewCameraTarget strView, arrCamera, arrTarget Rhino.ZoomExtents strView Rhino.EnableRedraw True End Sub ``` -------------------------------------------------------------------------------- # Iterating the Geometry Table Source: https://developer.rhino3d.com/en/guides/cpp/iterating-geometry-table/ This guide demonstrates how to use the C/C++ CRhinoObjectIterator class to iterate through the document. ## Overview The `CRhinoGetObject` class is useful when you need the user to interactively pick one or more objects. But, it is not too useful if you need to walk through the entire document looking for objects. This is where the `CRhinoObjectIterator` class comes in. The `CRhinoObjectIterator` class is used to iterate through the objects in a `CRhinoDoc` object. You can limit the iteration by specifying one of five mutually exclusive object states and one of three mutually exclusive object categories. ## Sample ```cpp CRhinoCommand::result CCommandTestIterator::RunCommand( const CRhinoCommandContext& context ) { CRhinoObjectIterator it( CRhinoObjectIterator::undeleted_objects, CRhinoObjectIterator::active_and_reference_objects ); it.IncludeLights( TRUE ); it.IncludeGrips( false ); int count = 0; for( CRhinoObject* pObject = it.First(); pObject; pObject = it.Next() ) { if( pObject->IsSelected() ) continue; if( !pObject->IsSelectable() ) continue; pObject->Select(); count++; } if( count ) context.m_doc.Redraw(); ::RhinoApp().Print( L"%d object(s) selected.\n", count ); return CRhinoCommand::success; } ``` For more information, see the *rhinoSdkDoc.h* SDK header file. -------------------------------------------------------------------------------- # Join the Dots Source: https://developer.rhino3d.com/en/samples/rhinoscript/join-the-dots/ Demonstrates joining points into polylines in RhinoScript. ```vbnet Option Explicit 'Script written by Steven Janssen 'Script version Monday, 9 July 2007 5:23:12 PM Call Main() Sub Main() Dim arrPoints, arrPoint1, arrPoint2, arrVirtual, arrClosestPt, arrExtractedCoords Dim arrVecBetweenPts, arrJoin Dim intTolerance, intCurrentDist, intMinDist Dim k, i i = 0 arrPoint1 = Rhino.GetPoint("Choose starting point") arrPoint2 = Rhino.GetPoint("Choose second point") intTolerance = 2 * rhino.Distance(arrPoint1, arrPoint2) 'intTolerance = Rhino.GetReal("Maximum Distance?",intTolerance) arrPoints = Rhino.GetObjects("Select other Points", 1) Rhino.EnableRedraw vbFalse Do 'add line between Point1 and Point2 i = i + 1 rhino.Print i rhino.addline arrPoint1, arrPoint2 'create the virtual point arrVecBetweenPts = rhino.VectorCreate(arrPoint2,arrPoint1) arrVecBetweenPts = rhino.VectorScale(arrVecBetweenPts,2) arrVirtual = rhino.VectorAdd(arrVecBetweenPts,arrPoint1) 'find closest point to the virtual point by sifting through all the other points intMinDist = 10 * intTolerance 'starting distance For k = 0 To Ubound(arrPoints) arrExtractedCoords = rhino.PointCoordinates(arrPoints(k)) If rhino.Pointcompare(arrExtractedCoords,arrPoint2) = vbFalse Then intCurrentDist = rhino.Distance(arrExtractedCoords,arrVirtual) If intCurrentDist < intMinDist Then intMinDist = intCurrentDist arrClosestPt = arrExtractedCoords End If End If Next 'adaptive Tolerance 'If (2 * intMinDist) > intTolerance Then ' intTolerance = (2 * intMinDist) 'End If 'check if distance is greater than tolerance and exit if it is 'rhino.print intMinDist & ", " If intMinDist > intTolerance Then Exit Do End If 'move the points so that Point2 is Point1 and the newly found point is Point2 arrPoint1 = arrPoint2 arrPoint2 = arrClosestPt 'the following prevents endless loops when there are two points close together at the end If rhino.distance(arrPoint1, arrPoint2) < (intTolerance/50) Then Exit Do End If Loop Rhino.EnableRedraw vbTrue End Sub ``` -------------------------------------------------------------------------------- # Knot Multiplicity Source: https://developer.rhino3d.com/en/samples/rhinoscript/knot-multiplicity/ Demonstrates how to determine curve and surface knot multiplicity using RhinoScript. ```vbnet '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' Description: ' Calculates the multiplicity of a knot. ' Parameters: ' knots - an array of knot values ' knot_index - the index of the knot to determine multiplicity ' Returns: ' The multiplicity if successful '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Function KnotMultiplicity(knots, knot_index) Dim knot_count, mult, index, t index = knot_index knot_count = UBound(knots) If (index < 0 Or index > knot_count) Then KnotMultiplicity = Null Exit Function End If t = knots(index) mult = 1 Do While (index < knot_count) If (knots(index + 1) - t) > 1.0e-12 Then Exit Do index = index + 1 mult = mult + 1 Loop KnotMultiplicity = mult End Function ``` -------------------------------------------------------------------------------- # Lengths of Curves Source: https://developer.rhino3d.com/en/guides/rhinoscript/lengths-of-curves/ This guide demonstrates how to calculate the lengths of curve objects using RhinoScript. ## Problem Imagine you wish to list of lengths for a selected series of curves. The Length command gives a composite length and the list command does not give the curve lengths. You can work around these limitations with a RhinoScript. ## Solution The following RhinoScript code demonstrates how to do the above... ```vbnet Option Explicit Sub CurveLength () Dim arrCurves, dblTotal, dblLength, i dblTotal = 0.0 arrCurves = Rhino.GetObjects("Select curves for length calculation", 4, True, True) If IsArray(arrCurves) Then For i = 0 To UBound(arrCurves) dblLength = Rhino.CurveLength(arrCurves(i)) Rhino.Print("Curve" & CStr(i) & " = " & CStr(dblLength)) dblTotal = dblTotal + dblLength Next Rhino.Print "Total length: " & " = " & CStr(dblTotal) End If End Sub ``` -------------------------------------------------------------------------------- # License Format Object Source: https://developer.rhino3d.com/en/guides/rhinocommon/cloudzoo/cloudzoo-licenseformat/ A License Format object defines a pattern for a license key. When a user enters a license key to be added to their account or their team, Cloud Zoo will find a product with a matching license format and notify its issuer about the user's intent to add the license. **Note:** Not all format possibilities are described in this document. If your license keys have a format that cannot be accurately defined in the fields below, we might be able to help. Please contact aj@mcneel.com for details. ## Structure "format": { "length": { "min": 24, "max": 48 }, "prefix": "RMA7-", "example": "RMA7-XXXX-XXXX-XXXX-XXXX-XXXX", "regexFilter": "[A-Za-z0-9]" }, ## Description - `length` - A range of `min` and `max` integers representing the minimum and maximum characters--inclusive--a license key is expected to contain. To avoid conflicts with other formats, this number should be as specific as possible. Note that the length is for the overall license key and includes the characters in the prefix. - `prefix` - A string representing the common prefix a license key is expected to begin with. The prefix should be as specific as possible to avoid conflicts with other formats. - ` example` - An example license key that may be shown to the user when entering a license. It should begin with the `prefix` specified. - `regexFilter`- (_optional_) - A regular expression defining the allowed characters in the license key. By default, only characters A-Z (both lowercase and uppercase) and numbers 0-9 are allowed. Whitespace, dashes, and slashes will not be considered for a pattern match. -------------------------------------------------------------------------------- # Licensing & Billing Source: https://developer.rhino3d.com/en/guides/compute/core-hour-billing/ ## About Core-Hour Billing When Rhino is logged in to a service account and is running on a Windows Server-based operating system, you will be billed **$0.10 per core per hour** that Rhino is running (pro-rated per minute). ***Example 1:** Rhino running on a 32-core server for one hour:* * 1 computer * 32-cores * 1 hour * $0.10/core-hour = $3.20 ***Example 2:** Rhino running on 200 4-core servers for 6 minutes:* * 200 computers * 4 cores * 0.1 hour * $0.10/core-hour = $8.00 ***Example 3:** 1 Rhino instance running on a 2-core server 8 hours a day for 30 days:* * 1 computer * 2 cores * 8 hours/day * 30 days/month * $0.10/core-hour = $48/month ***Example 4:** 10 Rhino instances running on a 2-core server 8 hours a day for 30 days:* * 1 computer * 2 cores * 8 hours/day * 30 days/month * $0.10/core-hour = $48/month * (Notice that the number of instances of Rhino does not affect your bill) **Billing is based on uptime**, not on usage - we don’t track the activity of each core, just that you have one running with Rhino. You can scale your workloads up and down to optimize performance and cost to you. **Multiple instances are allowed** - you may run as many instances of Rhino on the same machine as you want, and the cost will be the same as running one instance. ## Setting Up Core-Hour Billing Core-hour billing is required when running Rhino on a Windows Server-based operating system. 1. Go to the [Licenses Portal](https://www.rhino3d.com/licenses?_forceEmpty=true) (login to your Rhino account if prompted). 2. Click _Create New Team_ and create a team to use for your compute project. The team you use for core-hour billing **must have no licenses in it**. Core-hour billing is not compatible with Rhino Educational or Commercial licenses. Core-hour billing cannot be enabled on a team that contains any licenses. If you have an existing team with licenses, you must create a fresh team with no licenses before proceeding. 3. Click _Manage Team_ -> _Manage Core-Hour Billing_. 4. Check the checkbox next to the products you want to enable. \ **Note, if you've had a team running for years, you may need to enable newer versions of Rhino.** 5. Click _Save_, and enter payment information when prompted for your new team. ## Using Core-Hour Billing 1. Go to the [Licenses Portal](https://www.rhino3d.com/licenses?_forceEmpty=true) and select the team that you just set up with Core-hour billing. 1. Click _Manage Team_ -> _Manage Core-Hour Billing_. 2. Click _Action_ -> _Get Auth Token_ to get a token. 3. Create a new environment variable with the name `RHINO_TOKEN` and use the token as the value. Since the token is too long for Windows' Environment Variables dialog, it's easiest to do this via a PowerShell command. ```ps [System.Environment]::SetEnvironmentVariable('RHINO_TOKEN', 'your token here', 'Machine') ``` From now on, when you start Rhino on this machine it will use your core-hour billing team. Warning! This token allows anyone with it to charge your team at will. Do NOT share this token with anyone. ## Single-Computer licensing Not Supported When running on Windows Server, it is not possible to enter a license key to run as a single-computer license, as Rhino requires a license per core. -------------------------------------------------------------------------------- # Line Between Curves Source: https://developer.rhino3d.com/en/samples/rhinocommon/line-between-curves/ Demonstrates how to draw a line between two user-specified curves. ```cs partial class Examples { public static Rhino.Commands.Result LineBetweenCurves(Rhino.RhinoDoc doc) { Rhino.Input.Custom.GetObject go = new Rhino.Input.Custom.GetObject(); go.SetCommandPrompt("Select two curves"); go.GeometryFilter = Rhino.DocObjects.ObjectType.Curve; go.GetMultiple(2, 2); if (go.CommandResult() != Rhino.Commands.Result.Success) return go.CommandResult(); Rhino.DocObjects.ObjRef objRef0 = go.Object(0); Rhino.DocObjects.ObjRef objRef1 = go.Object(1); double t0 = Rhino.RhinoMath.UnsetValue; double t1 = Rhino.RhinoMath.UnsetValue; Rhino.Geometry.Curve curve0 = objRef0.CurveParameter(out t0); Rhino.Geometry.Curve curve1 = objRef1.CurveParameter(out t1); if (null == curve0 || !Rhino.RhinoMath.IsValidDouble(t0) || null == curve1 || !Rhino.RhinoMath.IsValidDouble(t1) ) return Rhino.Commands.Result.Failure; Rhino.Geometry.Line line = Rhino.Geometry.Line.Unset; bool rc = Rhino.Geometry.Line.TryCreateBetweenCurves(curve0, curve1, ref t0, ref t1, false, false, out line); if (rc) { if (Guid.Empty != doc.Objects.AddLine(line)) { doc.Views.Redraw(); return Rhino.Commands.Result.Success; } } return Rhino.Commands.Result.Failure; } } ``` ```python # No Python sample available ``` -------------------------------------------------------------------------------- # Linear Regression Source: https://developer.rhino3d.com/en/guides/rhinoscript/linear-regression/ This guide demonstrates how to calculate linear regression using RhinoScript. ## Overview Linear regression is a method to best fit a linear equation, or a straight line, of the form $$y(x) = a + bx$$ to a collection of n points $$(x, y)$$, where $$b$$ is the slope and $$a$$ the intercept on the y-axis. ## Discussion In the following implementation, the result will be stated below without derivation, that requires minimization of the sum of the squared distance from the data points and the proposed line. This function is minimized by calculating the derivative with respect to a and b and setting these to zero. This method assumes there is no known variance for the x and y values. There are solutions which can take this into account, this is particularly important if some values are known with less error than others. Also, this method requires that the slope is not infinite... ```vbnet ' Description: ' Linear Regression ' y(x) = a + bx, for n samples. ' Parameters: ' data - [in] An array of (x,y) values. ' a - [out] The slope. ' b - [out] The y-axis intersect. ' c - [out] The regression coefficient. ' Returns: ' True if successful, False otherwise. Function LinearRegression(ByVal data, ByRef a, ByRef b, ByRef r) ' Local variables Dim d, x, y, n Dim sumx, sumy, sumx2, sumy2, sumxy, sxx, syy, sxy ' Initialize variables sumx = 0 : sumy = 0 : sumx2 = 0 : sumy2 = 0 : sumxy = 0 n = UBound(data) + 1 ' Initialize output a = 0 : b = 0 : r = 0 ' Default return value LinearRegression = False ' Must have at least two points If (n < 2) Then Exit Function ' Compute some things we need For Each d In data x = d(0) y = d(1) sumx = sumx + x sumy = sumy + y sumx2 = sumx2 + (x * x) sumy2 = sumy2 + (y * y) sumxy = sumxy + (x * y) Next sxx = sumx2 - (sumx * sumx / n) syy = sumy2 - (sumy * sumy / n) sxy = sumxy - (sumx * sumy / n) ' Infinite slope (b), non existant intercept (a) If (Abs(sxx) = 0) Then Exit Function ' Compute slope (b) and intercept (a) b = sxy / sxx a = sumy / n - b * sumx / n ' Compute regression coefficient If (Abs(syy) = 0) Then r = 1 Else r = sxy / Sqr(sxx * syy) End If LinearRegression = True End Function ``` ## Best Fit ![Best Fit Graph](https://developer.rhino3d.com/images/linear-regression-01.png) The following example shows the points and the best fit line as determined using the techniques demonstrated above... ```vbnet Sub Main() Dim data(9), a, b, r data(0) = Array(-0.20707, -0.319029) data(1) = Array(0.706672, 0.0931669) data(2) = Array(1.63739, 2.17286) data(3) = Array(2.03117, 2.76818) data(4) = Array(3.31874, 3.56743) data(5) = Array(5.38201, 4.11772) data(6) = Array(6.79971, 5.52709) data(7) = Array(6.31814, 7.46613) data(8) = Array(8.20829, 8.7654) data(9) = Array(8.53994, 9.58096) If (LinearRegression(data, a, b, r) = True) Then Call Rhino.Print("Slope (b) = " & FormatNumber(b, 3)) Call Rhino.Print("Y Intercept (a) = " & FormatNumber(a, 3)) Call Rhino.Print("Regression Coefficient = " & FormatNumber(r, 3)) End If End Sub ``` -------------------------------------------------------------------------------- # List AutoCAD Export Schemes Source: https://developer.rhino3d.com/en/samples/rhinoscript/list-autocad-export-schemes/ Demonstrates how to build a list of AutoCAD export schemes using RhinoScript. ```vbnet Option Explicit Function GetAcadExportSchemes() Const HKEY_CURRENT_USER = &H80000001 Dim objReg, strComputer, strKey, arrSubKeys strComputer = "." strKey = "Software\McNeel\Rhinoceros\4.0\Scheme: Default\Plug-ins\39a88493-9e97-4f15-bd62-ad25896a2632\Settings" On Error Resume Next Set objReg = GetObject("winmgmts:{impersonationLevel=impersonate}!\\" & strComputer & "\root\default:StdRegProv") If Err.Number = 0 Then Call objReg.EnumKey(HKEY_CURRENT_USER, strKey, arrSubKeys) End If If IsArray(arrSubKeys) Then GetAcadExportSchemes = Rhino.SortStrings(arrSubKeys) Else GetAcadExportSchemes = Null End If End Function ``` -------------------------------------------------------------------------------- # List Components Source: https://developer.rhino3d.com/en/guides/grasshopper/list-components/ This guide demonstrates how to operate on more than one item at a time. ## Overview So far the example components have all operated on individual data items. This is known as One-In-One-Out. But what if you want to operate on more than one item at a time; One-In-Many-Out, Many-In-One-Out or Many-In-Many-Out? This requires that input or output parameters have a non-standard [Grasshopper.Kernel.GH_ParamAccess](/api/grasshopper/html/T_Grasshopper_Kernel_GH_ParamAccess.htm) flag. ## List Parameters Input and Output parameters that are part of Grasshopper components have an access flag that affects how the component treats data stored in these parameters. Take for example the Polyline component. It creates a single polyline object from a *collection* of corner-points. This is a Many-In-One-Out kind of logic. The Divide component creates a whole bunch of division points from a single curve. This is an example of One-In-Many-Out. The Cull components remove certain items from lists, this is an example of Many-In-Many-Out. Most components treat their inputs and outputs as parameters that provide individual instances of data, rather than related collections of data. This is indicated by all parameters having an [GH_ParamAccess.item](/api/grasshopper/html/T_Grasshopper_Kernel_GH_ParamAccess.htm) access flag. You can however assign different access flags to parameters. Preferably this flag should be assigned only once, namely in the [RegisterInputParams](/api/grasshopper/html/M_Grasshopper_Kernel_GH_Component_RegisterInputParams.htm) or [RegisterOutputParams](/api/grasshopper/html/M_Grasshopper_Kernel_GH_Component_RegisterOutputParams.htm) method overrides. It is legal to modify an access flag as long as a solution is not currently in progress, but it is not recommended. In this guide, we'll be writing a component that removes (culls) the bottom-most N objects in a collection of geometric shapes. As inputs we'll need a collection of geometry and an integer indicating how many objects the user wants to remove, and as output we'll provide the same collection of geometry, but with the bottom-most objects missing. This is therefore a Many-In-Many-Out case. The Component code (without the `RegisterInputParams`, `RegisterOutputParams` and `SolveInstance` methods) may look like this: ```cs public class Component_CullByElevation : GH_Component { public Component_CullByElevation() : base("Cull Elevation", "CullZ", "Cull objects by relative elevation", "Sets", "Sequence") { } public override Kernel.GH_Exposure Exposure { get { return GH_Exposure.primary | GH_Exposure.obscure; } } public override System.Guid ComponentGuid { get { return new Guid("{A8FF9CBA-0837-4cd6-9198-0D17325D3F8F}"); } } //...additional component code will go here.. } ``` ```vbnet Public Class Component_CullByElevation Inherits GH_Component Public Sub New() MyBase.New("Cull Elevation", "CullZ", "Cull objects by relative elevation", "Sets", "Sequence") End Sub Protected Overrides ReadOnly Property Icon() As System.Drawing.Bitmap Get Return My.Resources.TheIconNameForThisComponent End Get End Property Public Overrides ReadOnly Property Exposure() As Kernel.GH_Exposure Get Return GH_Exposure.primary Or GH_Exposure.obscure End Get End Property Public Overrides ReadOnly Property ComponentGuid() As System.Guid Get Return New Guid("{A8FF9CBA-0837-4cd6-9198-0D17325D3F8F}") End Get End Property '...further example code will come here... End Class ``` We'll need to register the parameters as well, and we'll use the overloaded methods to immediately assign the correct parameter access flags... ```cs protected override void RegisterInputParams(Kernel.GH_Component.GH_InputParamManager pManager) { pManager.AddGeometryParameter("Geometry", "G", "Geometry to cull", GH_ParamAccess.list); pManager.AddIntegerParameter("Count", "C", "Number of objects to cull", GH_ParamAccess.item, 1); } protected override void RegisterOutputParams(Kernel.GH_Component.GH_OutputParamManager pManager) { pManager.AddGeometryParameter("Geometry", "G", "Culled geometry", GH_ParamAccess.list); } ``` ```vbnet Protected Overrides Sub RegisterInputParams(ByVal pManager As Kernel.GH_Component.GH_InputParamManager) 'list access is non-standard, so we need to specifically assign it. pManager.AddGeometryParameter("Geometry", "G", "Geometry to cull", GH_ParamAccess.list) pManager.AddIntegerParameter("Count", "C", "Number of objects to cull", GH_ParamAccess.item, 1) End Sub Protected Overrides Sub RegisterOutputParams(ByVal pManager As Kernel.GH_Component.GH_OutputParamManager) 'Again, we need to specify list access as that is not default. pManager.AddGeometryParameter("Geometry", "G", "Culled geometry", GH_ParamAccess.list) End Sub ``` Input parameters rigorously enforce their access. You are only allowed to retrieve individual items from inputs that have the [GH_ParamAccess.item](/api/grasshopper/html/T_Grasshopper_Kernel_GH_ParamAccess.htm) flag set. Lists can only be retrieved when the access is set to `GH_ParamAccess.list` and data trees can only be gotten from a `GH_ParamAccess.tree` parameter. Failure to do so will result in an error message at runtime. Output parameter are more flexible, but this is only because output access was added only in Grasshopper 0.9.0001 and strict enforcement would result in SDK breakage with previous versions. ## Solving Routine The `SolveInstance` implementation is basically identical to previous examples, the only difference is the way the component interacts with the parameters... ```cs protected override void SolveInstance(Kernel.IGH_DataAccess DA) { //Declare a new List(Of T) to hold your data. //This list must exist and should probably be empty. List geometry = new List(); Int32 count = 0; //Retrieve the whole list using Da.GetDataList(). if ((!DA.GetDataList(0, geometry))) return; if ((!DA.GetData(1, count))) return; //Validate inputs. if ((count < 0)) { AddRuntimeMessage(GH_RuntimeMessageLevel.Error, "Count must be a positive integer"); return; } //The number of objects to cull is larger than or //equal to the total number of objects. I.e. cull them all. if ((geometry.Count <= count)) return; //Iteratively remove the lowest object from the list. for (Int32 N = 1; N <= count; N++) { double lowestElevation = double.MaxValue; Int32 lowestIndex = -1; //Iterate over all remaining geometry and find the lowest one. for (Int32 i = 0; i <= geometry.Count - 1; i++) { if ((geometry[i] == null)) continue; BoundingBox bbox = geometry[i].Boundingbox; if ((!bbox.IsValid)) continue; double localElevation = bbox.Min.Z; if ((localElevation < lowestElevation)) { lowestElevation = localElevation; lowestIndex = i; } } //Delete the lowest object. geometry.RemoveAt(lowestIndex); } //Assign the remaining geometry //(even if it is only a single item!) //using the DA.SetDataList() method. DA.SetDataList(0, geometry); } ``` ```vbnet Protected Overrides Sub SolveInstance(ByVal DA As Kernel.IGH_DataAccess) 'Declare a new List(Of T) to hold your data. 'This list cannot be a null reference. Dim geometry As New List(Of IGH_GeometricGoo) Dim count As Int32 = 0 'Retrieve the whole list using DA.GetDataList(). If (Not DA.GetDataList(0, geometry)) Then Return If (Not DA.GetData(1, count)) Then Return 'Validate inputs. If (count < 0) Then AddRuntimeMessage(GH_RuntimeMessageLevel.Error, "Count must be a positive integer") Return End If 'The number of objects to cull is larger than or 'equal to the total number of objects. I.e. cull them all. If (geometry.Count <= count) Then Return 'Iteratively remove the lowest object from the list. For N As Int32 = 1 To count Dim lowestElevation As Double = Double.MaxValue Dim lowestIndex As Int32 = -1 'Iterate over all remaining geometry and find the lowest one. For i As Int32 = 0 To geometry.Count - 1 If (geometry(i) Is Nothing) Then Continue For Dim bbox As BoundingBox = geometry(i).Boundingbox If (Not bbox.IsValid) Then Continue For Dim localElevation As Double = bbox.Min.Z If (localElevation < lowestElevation) Then lowestElevation = localElevation lowestIndex = i End If Next 'Delete the lowest object. If (lowestIndex >= 0) Then geometry.RemoveAt(lowestIndex) Next 'Assign the remaining geometry using the DA.SetDataList() method. DA.SetDataList(0, geometry) End Sub ``` ## Related Topics - [What is a Grasshopper Component?](/guides/grasshopper/what-is-a-grasshopper-component) - [Your First Component](/guides/grasshopper/your-first-component-windows) - [Simple Component](/guides/grasshopper/simple-component) - [Simple Mathematics Component](/guides/grasshopper/simple-mathematics-component) - [Simple Geometry Component](/guides/grasshopper/simple-geometry-component) -------------------------------------------------------------------------------- # List IGES Export Schemes Source: https://developer.rhino3d.com/en/samples/rhinoscript/list-iges-export-schemes/ Demonstrates how to build a list of IGES export schemes using RhinoScript. ```vbnet Option Explicit Function GetIgesExportSchemes() Const HKEY_CURRENT_USER = &H80000001 Dim objReg, strComputer, strKey, arrSubKeys strComputer = "." strKey = "Software\McNeel\Rhinoceros\4.0\Scheme: Default\Plug-ins\7f0ca561-0c7c-4cea-b822-b95ebe71c409\Settings" On Error Resume Next Set objReg = GetObject("winmgmts:{impersonationLevel=impersonate}!\\" & strComputer & "\root\default:StdRegProv") If Err.Number = 0 Then Call objReg.EnumKey(HKEY_CURRENT_USER, strKey, arrSubKeys) End If If IsArray(arrSubKeys) Then GetIgesExportSchemes = Rhino.SortStrings(arrSubKeys) Else GetIgesExportSchemes = Null End If End Function ``` -------------------------------------------------------------------------------- # List Knot Vector of NURBS Curve Source: https://developer.rhino3d.com/en/samples/rhinoscript/list-knot-vector-of-nurbs-curve/ Demonstrates how to print the knot vector of NURBS curve using RhinoScript. ```vbnet Option Explicit Sub ListKnotVector Dim curve : curve = Rhino.GetObject("Select curve", 4) If IsNull(curve) Then Exit Sub Dim knot : knot = Rhino.CurveKnots(curve) If Not IsArray(knot) Then Rhino.Print "NULL knot vector" End If ' order = degree + 1 Dim order : order = Rhino.CurveDegree(curve) + 1 If (order < 2) Then Rhino.Print "knot vector order < 2" End If Dim cv_count : cv_count = Rhino.CurvePointCount(curve) If (cv_count < order) Then Rhino.Print "knot vector cv_count < order" End If Dim knot_count, i, i0, mult If (order >= 2) And (cv_count >= order) And IsArray(knot) Then knot_count = order + cv_count - 2 i = 0 i0 = 0 Rhino.Print "index, value, mult, delta" While (i < knot_count) mult = 1 Do While (i + mult < knot_count) If (i + mult < knot_count) Then If (knot(i) = knot(i+mult)) Then mult = mult + 1 Else Exit Do End If Else Exit Do End If Loop If (i = 0) Then Rhino.Print CStr(i) & ", " & CStr(knot(i)) & ", " & CStr(mult) Else Rhino.Print CStr(i) & ", " & CStr(knot(i)) & ", " & CStr(mult) & ", " & CStr(knot(i)-knot(i0)) End If i0 = i i = i + mult Wend End If End Sub ``` -------------------------------------------------------------------------------- # Loading Plugins at Startup Source: https://developer.rhino3d.com/en/guides/cpp/loading-plugins-at-startup/ This guide discusses how to configure plugins to load at startup using C/C++. ## Problem You would like your plugin to load at Rhino's startup. ## Solution Rhino will load plugins in two ways: 1. When Needed (Default). Plugin will not be loaded when Rhino starts. Plugin will be loaded when a plugin defined command is run, when a user selects a plugin defined file import/export type, or if a 3DM file has user data that was created by your plugin. 1. At Startup. Plugin is loaded when Rhino is loaded and initialized. To set your plugin to load on startup, you need to override your plugin object's `CRhinoPlugIn::PlugInLoadTime()` virtual function and return the `CRhinoPlugIn::load_plugin_at_startup` enumerated value. See *rhinoSdkPlugIn.h* for details. ## Sample ```cpp // Description: // Called by Rhino when writing plug-in information to the registry. This // information will be read the next time Rhino starts to identify properly // installed plug-ins. CRhinoPlugIn::plugin_load_time CTestPlugIn::PlugInLoadTime() { return CRhinoPlugIn::load_plugin_at_startup; } ``` ## Details If you have already loaded your plugin using Rhino's plugin manager, when debugging for example, then you will need to either remove your plugin's registry key, which can be found here: ``` HKEY_LOCAL_MACHINE\SOFTWARE\McNeel\Rhinoceros\\\Plug-Ins\ ```

WARNING

if you are running on a system with limited rights, with user-account control enabled for example, then there will be a corresponding key in HKEY_CURRENT_USER Or, you can just modify your plugin's "LoadMode" registry key value. The available values for this key are as follows: | Load mode | | | Registry Value | | | Description | | :------------- | --- | --- | :------------- | --- | --- | :------------- | | load_plugin_when_needed | | | 2 - REG_DWORD, Decimal | | | Default. Load the first time a plugin command used | | load_plugin_at_startup | | | 1 - REG_DWORD, Decimal | | | Load when Rhino is loaded | The reason this step is required is that the "LoadMode" Registry key value is only written the first time the plugin is loaded (when it is initially installed or registered). This will not be an issue for customers of your plugin for the correct registry key value will be written the first time they load your plugin. -------------------------------------------------------------------------------- # Localizing Plugin Toolbars Source: https://developer.rhino3d.com/en/guides/rhinocommon/localize-plugin-toolbar/ This guide covers the localization of plugin toolbars. ## Question What is the best way to prepare a Rhino toolbar for multi-language support? ## Answer If you want to create Rhino-style toolbars, then use Rhino's `Toolbar` command. You can save your custom toolbars in your own Rhino User Interface (RUI) file. For details on creating toolbars, see the Rhino help file. An RUI file is an XML file that can be viewed and edited in an ordinary text editor. If you open an RUI file, that contains a toolbar that contains a button, you might see a block of XML that looks similar to the following: ```xml RenderSettings Render settings Render ``` Notice the `````` tag, which denotes the text used by Rhino when configured for English (United States). It is possible to add additional locale tags for supported language. ```xml RenderSettings Rendereinstellungen RenderizadoOpciones ParamètresRendu RenderingImpostazioni 렌더링_설정 渲染设置 彩現設定 Render settings Rendereinstellungen Opciones de renderizado Paramètres du rendu Impostazioni rendering レンダリング設定 렌더링 설정 渲染设置 彩現設定 Render Rendern Renderizar Rendu Rendering レンダリング 렌더링 渲染 彩現 ``` Note, it is not possible to localize toolbar bitmaps. -------------------------------------------------------------------------------- # Lock Layer Source: https://developer.rhino3d.com/en/samples/rhinocommon/lock-layer/ Demonstrates how to lock a user-specified layer. ```cs partial class Examples { public static Result LockLayer(RhinoDoc doc) { string layer_name = ""; var rc = RhinoGet.GetString("Name of layer to lock", true, ref layer_name); if (rc != Result.Success) return rc; if (String.IsNullOrWhiteSpace(layer_name)) return Result.Nothing; // because of sublayers it's possible that mone than one layer has the same name // so simply calling doc.Layers.Find(layerName) isn't good enough. If "layerName" returns // more than one layer then present them to the user and let him decide. var matching_layers = (from layer in doc.Layers where layer.Name == layer_name select layer).ToList(); Rhino.DocObjects.Layer layer_to_lock = null; if (matching_layers.Count == 0) { RhinoApp.WriteLine("Layer \"{0}\" does not exist.", layer_name); return Result.Nothing; } else if (matching_layers.Count == 1) { layer_to_lock = matching_layers[0]; } else if (matching_layers.Count > 1) { for (int i = 0; i < matching_layers.Count; i++) { RhinoApp.WriteLine("({0}) {1}", i+1, matching_layers[i].FullPath.Replace("::", "->")); } int selected_layer = -1; rc = RhinoGet.GetInteger("which layer?", true, ref selected_layer); if (rc != Result.Success) return rc; if (selected_layer > 0 && selected_layer <= matching_layers.Count) layer_to_lock = matching_layers[selected_layer - 1]; else return Result.Nothing; } if (layer_to_lock == null) return Result.Nothing; if (!layer_to_lock.IsLocked) { layer_to_lock.IsLocked = true; layer_to_lock.CommitChanges(); return Result.Success; } else { RhinoApp.WriteLine("layer {0} is already locked.", layer_to_lock.FullPath); return Result.Nothing; } } } ``` ```python import rhinoscriptsyntax as rs from scriptcontext import doc def lock(): layerName = rs.GetString("Name of layer to lock") matchingLayers = [layer for layer in doc.Layers if layer.Name == layerName] layerToLock = None if len(matchingLayers) == 0: print("Layer \"{0}\" does not exist.".format(layerName)) elif len(matchingLayers) == 1: layerToLock = matchingLayers[0] elif len(matchingLayers) > 1: i = 0; for layer in matchingLayers: print("({0}) {1}".format(i+1, matchingLayers[i].FullPath.replace("::", "->"))) i += 1 selectedLayer = rs.GetInteger("which layer?", -1, 1, len(matchingLayers)) if selectedLayer == None: return layerToLock = matchingLayers[selectedLayer - 1] if layerToLock.IsLocked: print("layer {0} is already locked.".format(layerToLock.FullPath)) else: layerToLock.IsLocked = True layerToLock.CommitChanges() if __name__ == "__main__": lock() ``` -------------------------------------------------------------------------------- # Loft Surfaces Source: https://developer.rhino3d.com/en/samples/cpp/loft-surfaces/ Demonstrates how to use the CArgsRhinoLoft class and the RhinoSdkLoftSurface function. ```cpp //The following sample code demonstrates how to use the CArgsRhinoLoft class and the RhinoSdkLoftSurface function. //The definitions of these can be found in rhinoSdkLoft.h. //Note, this example does not perform any curve sorting or direction matching. //This is the responsibility of the the SDK developer. CRhinoCommand::result CCommandTest::RunCommand( const CRhinoCommandContext& context ) { // Select curves to loft CRhinoGetObject go; go.SetCommandPrompt( L"Select curves to loft" ); go.SetGeometryFilter( CRhinoGetObject::curve_object ); go.SetGeometryAttributeFilter( CRhinoGetObject::open_curve ); go.EnableSubObjectSelect( FALSE ); go.GetObjects( 2, 0 ); if( go.CommandResult() != CRhinoCommand::success ) return go.CommandResult(); // Fill in loft arguments class CArgsRhinoLoft args; args.m_bAllowStartTangent = FALSE; args.m_bAllowEndTangent = FALSE; args.m_bUseStartpoint = FALSE; args.m_bUseEndpoint = FALSE; args.m_bClosed = FALSE; args.m_loft_type = CArgsRhinoLoft::ltTight; args.m_simplify_method = CArgsRhinoLoft::lsNone; args.m_start_condition = CArgsRhinoLoft::leNatural; args.m_end_condition = CArgsRhinoLoft::leNatural; args.m_rebuild_point_count = 10; args.m_refit_tolerance = context.m_doc.AbsoluteTolerance(); int i, object_count = go.ObjectCount(); args.m_loftcurves.SetCapacity( object_count ); // Add loft curves for( i = 0; i < object_count; i++ ) { const ON_Curve* curve = go.Object(i).Curve(); if( curve ) { CRhinoLoftCurve* loft_curve = new CRhinoLoftCurve; loft_curve->m_curve = curve->DuplicateCurve(); loft_curve->m_curve->RemoveShortSegments( ON_ZERO_TOLERANCE, true ); loft_curve->m_pick-point = ON_UNSET_POINT; loft_curve->m_pick_t = ON_UNSET_VALUE; loft_curve->m_trim = 0; loft_curve->m_bClosed = loft_curve->m_curve->IsClosed() ? true : false; loft_curve->m_bPlanar = loft_curve->m_curve->IsPlanar(&loft_curve->m_plane)? true : false; args.m_loftcurves.Append( loft_curve ); } } // Do the loft operation ON_SimpleArray surface_list; bool rc = RhinoSdkLoftSurface( args, surface_list ); // Delete the loft curves used in the surface calculation for( i = 0; i < args.m_loftcurves.Count(); i++ ) delete args.m_loftcurves[i]; args.m_loftcurves.Empty(); // If only one surface was calculated, add it to Rhino if( surface_list.Count() == 1 ) { CRhinoSurfaceObject* surf_obj = context.m_doc.AddSurfaceObject(*surface_list[0]); // AddSurfaceObject make a copy... delete surface_list[0]; if( surf_obj ) context.m_doc.Redraw(); return ( surf_obj ? success : failure ); } // If more than one surface was calculated, create a list of breps. ON_SimpleArray brep_list; for( i = 0; i < surface_list.Count(); i++) { if( surface_list[i]->IsValid() ) { ON_Brep* brep = ON_Brep::New(); brep->NewFace( *surface_list[i] ); delete surface_list[i]; brep_list.Append( brep ); } } // Try joining all breps ON_Brep* brep = RhinoJoinBreps( brep_list, context.m_doc.AbsoluteTolerance() ); if( brep ) { CRhinoBrepObject* brep_object = context.m_doc.AddBrepObject( *brep ); // AddBrepObject makes a copy... delete brep; if( brep_object ) context.m_doc.Redraw(); return ( brep_object ? success : failure ); } // Othewise just add the individual breps to Rhino. for( i = 0; i < brep_list.Count(); i++ ) { if( brep_list[i] ) { CRhinoBrepObject* brep_object = context.m_doc.AddBrepObject( *brep_list[i] ); // AddBrepObject make a copy... delete brep_list[i]; } } context.m_doc.Redraw(); return CRhinoCommand::success; } ``` -------------------------------------------------------------------------------- # Loft Surfaces Source: https://developer.rhino3d.com/en/samples/rhinocommon/loft-surfaces/ Demonstrates how to create a lofted surface from a set of user-specified curves. ```cs partial class Examples { public static Result Loft(RhinoDoc doc) { // select curves to loft var gs = new GetObject(); gs.SetCommandPrompt("select curves to loft"); gs.GeometryFilter = ObjectType.Curve; gs.DisablePreSelect(); gs.SubObjectSelect = false; gs.GetMultiple(2, 0); if (gs.CommandResult() != Result.Success) return gs.CommandResult(); var curves = gs.Objects().Select(obj => obj.Curve()).ToList(); var breps = Brep.CreateFromLoft(curves, Point3d.Unset, Point3d.Unset, LoftType.Tight, false); foreach (var brep in breps) doc.Objects.AddBrep(brep); doc.Views.Redraw(); return Result.Success; } } ``` ```python import rhinoscriptsyntax as rs def RunCommand(): crvids = rs.GetObjects(message="select curves to loft", filter=rs.filter.curve, minimum_count=2) if not crvids: return rs.AddLoftSrf(object_ids=crvids, loft_type = 3) #3 = tight if __name__ == "__main__": RunCommand() ``` -------------------------------------------------------------------------------- # Lofting Surfaces that Maintain Tangency Source: https://developer.rhino3d.com/en/guides/cpp/lofting-surface-that-maintain-tangency/ This guide demonstrates how to loft surfaces that maintain tangency using C/C++. ## Overview When trying to loft a surface with starting or ending tangency, it is not enough just to set `CArgsRhinoLoft` object's `m_start_condition` and `m_end_condition` members to `CArgsRhinoLoft::leTangent`. You also need to tell Rhino's lofter what it is that this lofted surface need to be tangent to. You do this by setting the `m_trim` parameter of the starting and ending `CRhinoLoftCurve` objects. This is a constant `ON_BrepTrim` pointer. If you are lofting curves that you have picked using a `CRhinoGetObject` object, you can retrieve this pointer by simply calling `CRhinoObjRef::Trim()`. ## Sample The following sample code demonstrates how to loft surfaces that maintain tangency with adjacent surfaces using the `CArgsRhinoLoft` class and the `RhinoSdkLoftSurface` function. The definitions of these are in *rhinoSdkLoft.h*. **NOTE**: This sample does not perform any curve sorting or direction matching. This is the responsibility of the plugin developer. ```cpp CRhinoCommand::result CCommandTest::RunCommand( const CRhinoCommandContext& context ) { // Select curves to loft CRhinoGetObject go; go.SetCommandPrompt( L"Select curves to loft" ); go.SetGeometryFilter( CRhinoGetObject::curve_object | CRhinoGetObject::edge_object); go.SetGeometryAttributeFilter( CRhinoGetObject::open_curve ); go.EnablePreSelect( false ); go.GetObjects( 2, 0 ); if( go.CommandResult() != CRhinoCommand::success ) return go.CommandResult(); // Create loft arguments object const int obj_count = go.ObjectCount(); CArgsRhinoLoft args; args.m_loftcurves.SetCapacity( obj_count ); // Add curves to loft arguments object int i; for( i = 0; i < obj_count; i++ ) { const CRhinoObjRef& ref = go.Object(i); const ON_Curve* crv = ref.Curve(); if( crv ) { // New loft curve CRhinoLoftCurve* lc = new CRhinoLoftCurve; // Duplicate the selected curve. Note, // the loft curve will delete this curve. lc->m_curve = crv->DuplicateCurve(); lc->m_curve->RemoveShortSegments( ON_ZERO_TOLERANCE ); // Set other loft curve parameters lc->m_bPlanar = ( lc->m_curve->IsPlanar(&lc->m_plane) ? true : false ); // If referenced geometry is a surface edge, // assign associated brep trim. lc->m_trim = ref.Trim(); // Append loft curve to loft argument args.m_loftcurves.Append( lc ); } } // If we do not have enough loft curves, // clean up and bail. const int lc_count = args.m_loftcurves.Count(); if( lc_count < 2 ) { for( i = 0; i < lc_count; i++ ) delete args.m_loftcurves[i]; return failure; } // If the starting loft curve has a trim, // set the start condition to tangent. if( args.m_loftcurves[0] && args.m_loftcurves[0]->m_trim ) { args.m_start_condition = CArgsRhinoLoft::leTangent; args.m_bAllowStartTangent = TRUE; } // If the ending loft curve has a trim, // set the end condition to tangent. if( args.m_loftcurves[lc_count-1] && args.m_loftcurves[lc_count-1]->m_trim ) { args.m_end_condition = CArgsRhinoLoft::leTangent; args.m_bAllowEndTangent = TRUE; } // Do the loft calculation ON_SimpleArray srf_list; bool rc = RhinoSdkLoftSurface( args, srf_list ); // Delete the loft curves so we do not leak memory. for( i = 0; i < args.m_loftcurves.Count(); i++ ) delete args.m_loftcurves[i]; args.m_loftcurves.Empty(); // If the loft operation failed, bail. if( !rc ) return failure; // If only one surface was calculated, add it to Rhino if( srf_list.Count() == 1 ) { context.m_doc.AddSurfaceObject( *srf_list[0] ); // CRhinoDoc::AddSurfaceObject() make a copy. // So, delete original so memory is not leaked. delete srf_list[0]; } else { // If more than one surface was calculated, // create a list of breps. ON_SimpleArray brep_list; for( i = 0; i < srf_list.Count(); i++) { if( srf_list[i]->IsValid() ) { ON_Brep* brep = ON_Brep::New(); brep->NewFace( *srf_list[i] ); // ON_Brep::NewFace() make a copy. // So, delete original so memory is not leaked. delete srf_list[i]; brep_list.Append( brep ); } } // Try joining all breps double tol = context.m_doc.AbsoluteTolerance(); ON_Brep* brep = RhinoJoinBreps( brep_list, tol ); if( brep ) { context.m_doc.AddBrepObject( *brep ); // CRhinoDoc::AddBrepObject() make a copy. // So, delete original so memory is not leaked. delete brep; } else { // Othewise just add the individual breps to Rhino. for( i = 0; i < brep_list.Count(); i++ ) { if( brep_list[i] ) { context.m_doc.AddBrepObject( *brep_list[i] ); // CRhinoDoc::AddBrepObject() make a copy. // So, delete original so memory is not leaked. delete brep_list[i]; } } } } context.m_doc.Redraw(); return success; } ``` -------------------------------------------------------------------------------- # Logging Debug Info Source: https://developer.rhino3d.com/en/guides/cpp/logging-debug-info/ This brief guide discusses the use of the ON_TextLog class for debugging C/C++ plugins. ## Overview The openNURBS C/C++ SDK, which is also included with the Rhino C/C++ SDK, contains a `ON_TextLog` class that makes it very simple to write, or dump, information to a text file. The class can be very handy when trying to debug geometric objects, for most objects have the ability to dump their contents to a log file. ## Sample The following is an example of using the `ON_TextLog` class to dump the contents of a brep object to a text file. For more information on `ON_TextLog`, see *opennurbs_textlog.h* ```cpp CRhinoCommand::result CCommandTest::RunCommand( const CRhinoCommandContext& context ) { CRhinoGetObject go; go.SetCommandPrompt( L"Select brep" ); go.SetGeometryFilter( CRhinoGetObject::surface_object | CRhinoGetObject::polysrf_object ); go.GetObjects( 1, 1 ); if( go.CommandResult() == CRhinoCommand::success ) { const ON_Brep* brep = go.Object(0).Brep(); if( brep ) { FILE* fp = ON::OpenFile( L"c:\\bug_report.txt", L"w" ); if( fp ) { ON_TextLog text_log( fp ); text_log.Print( L"Dumping Brep...\n" ); brep->Dump( text_log ); ON::CloseFile( fp ); } } } return CRhinoCommand::success; } ``` -------------------------------------------------------------------------------- # Making Plugins That Expire Source: https://developer.rhino3d.com/en/guides/cpp/making-plugins-that-expire/ This guide demonstrates an easy way to make a plugin expire using C/C++. ## Problem You would like to release a work-in-progress, or WIP, version of your plugin so you can get some customer feedback before shipping. You would like the plugin to expire after a number of days. Is there something in Rhino that could help with this? ## Solution Rhino does not have any feature that can help with this. But, it is not too difficult to compare the compile date of the plugin with the current date to see if you plugin should load or not. The following example code demonstrates this... ## Sample ```cpp BOOL CTestPlugIn::OnLoadPlugIn() { #if defined(WIP) // Easy cheezy way of testing to see if plugin has expired. COleDateTime compile_time; compile_time.ParseDateTime( ON_wString(__DATE__" "__TIME__), 0, 0x0409 ); COleDateTime current_time( COleDateTime::GetCurrentTime() ); COleDateTimeSpan span = current_time - compile_time; int elapsed_days = span.GetDays(); const int warn_after_days = 30; // or whatever... const int expire_after_days = 40; // or whatever... if( elapsed_days >= warn_after_days ) { HWND hWnd = RhinoApp().MainWnd(); if( elapsed_days >= expire_after_days ) { ON_wString str = L"This WIP has expired. Please download the latest and greatest..."; ::MessageBox( hWnd, str, PlugInName(), MB_OK | MB_ICONERROR ); return -1; // This will cause the plugin not to load and not display an Rhino error. } else { ON_wString str; str.Format( L"This WIP will expire in %d days. Please download the latest and greatest...", expire_after_days - elapsed_days ); ::MessageBox( hWnd, str, PlugInName(), MB_OK | MB_ICONEXCLAMATION ); } } #endif // If we got here, then the WIP was good. // So, do other plugin initialization // and return TRUE to run. return TRUE; } ``` -------------------------------------------------------------------------------- # Mark Points on a Line Source: https://developer.rhino3d.com/en/samples/rhinoscript/mark-points-on-a-line/ Demonstrates how to mark points on a line using RhinoScript. ```vbnet ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' MarkLine.rvb -- March 2010 ' If this code works, it was written by Dale Fugier. ' If not, I don't know who wrote it. ' Works with Rhino 4.0. ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Sub MarkLine() Dim arrLast Call Rhino.Command("_Line _Pause _Pause") If (Rhino.LastCommandResult() = 0) Then Call Rhino.EnableRedraw(False) arrLast = Rhino.LastCreatedObjects() If IsArray(arrLast) Then Call Rhino.AddPoint(Rhino.CurveStartPoint(arrLast(0))) Call Rhino.AddPoint(Rhino.CurveMidPoint(arrLast(0))) Call Rhino.AddPoint(Rhino.CurveEndPoint(arrLast(0))) Call Rhino.DeleteObjects(arrLast) End If Call Rhino.EnableRedraw(True) End If End Sub ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Call Rhino.AddStartupScript(Rhino.LastLoadedScriptFile) Call Rhino.AddAlias("MarkLine", "_NoEcho _-RunScript (MarkLine)") ``` -------------------------------------------------------------------------------- # Match Object Attributes Source: https://developer.rhino3d.com/en/samples/rhinoscript/match-object-attributes/ Demonstrates a custom object attribute matching function in RhinoScript. ```vbnet ' Description: ' Matches, or copies, the attributes of a source object to a ' target object or an array of target objects. If the source ' object is not specified, the attributes of the target object(s) ' will be reset to Rhino's default object attributes. ' Parameters: ' arrTargets - An array of strings identifying the target objects. ' strSource - The identifier of the source object. If the source ' object is not specified, the attributes of the target ' object(s) will be reset to Rhino's default object ' attributes. ' intMode - The group mode flag, where: ' intMode = -1, remove all groups from targets ' intMode = 0, do not copy source groups to targets ' intMode = 1, copy source groups to targets ' Function MatchObjectAttributesEx(arrTargets, strSource, intMode) Dim strTarget, arrGroups, strGroup If intMode < -1 Then intMode = -1 ElseIf intMode > 1 Then intMode = 1 End If For Each strTarget In arrTargets If intMode = 0 Then arrGroups = Rhino.ObjectGroups(strTarget) Else arrGroups = Null End If Call Rhino.MatchObjectAttributes(strTarget, strSource) If intMode = -1 Or intMode = 0 Then Call Rhino.RemoveObjectFromAllGroups(strTarget) End If If intMode = 0 And Not IsNull(arrGroups) Then For Each strGroup In arrGroups Call Rhino.AddObjectToGroup(strTarget, strGroup) Next End If Next End Function ``` -------------------------------------------------------------------------------- # Match Text Properties Source: https://developer.rhino3d.com/en/samples/rhinoscript/match-text-properties/ Demonstrates how to match text object properties in RhinoScript. ```vbnet ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' MatchText.rvb -- July 2009 ' If this code works, it was written by Dale Fugier. ' If not, I don't know who wrote it. ' Works with Rhino 4.0 and 5.0 ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Option Explicit Sub MatchText() Dim objects, source, match, items, values, i Dim font, height, style objects = Rhino.GetObjects("Select text objects to modify", 512,, True) If IsNull(objects) Then Exit Sub source = Rhino.GetObject("Select source object", 512) If IsNull(source) Then Exit Sub items = Array("Font", "Height", "Style") values = Array(True, True, True) match = Rhino.CheckListBox(items, values, "Properties to match:", "Match Text") If IsNull(match) Then Exit Sub font = Rhino.TextObjectFont(source) height = Rhino.TextObjectHeight(source) style = Rhino.TextObjectStyle(source) Call Rhino.EnableRedraw(False) For i = 0 To UBound(objects) If match(0) = True Then Call Rhino.TextObjectFont(objects(i), font) If match(1) = True Then Call Rhino.TextObjectHeight(objects(i), height) If match(2) = True Then Call Rhino.TextObjectStyle(objects(i), style) Next Call Rhino.EnableRedraw(True) End Sub ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Rhino.AddStartupScript Rhino.LastLoadedScriptFile Rhino.AddAlias "MatchText", "_NoEcho _-RunScript (MatchText)" ``` -------------------------------------------------------------------------------- # Maximize View Source: https://developer.rhino3d.com/en/samples/cpp/maximize-view/ Demonstrates how to maximize a view. ```cpp CRhinoCommand::result CCommandTest::RunCommand(const CRhinoCommandContext& context) { CRhinoGetString gs; gs.SetCommandPrompt( L"Name of viewport to maximize" ); gs.GetString(); if( gs.CommandResult() != CRhinoCommand::success ) return gs.CommandResult(); ON_wString view_name( gs.String() ); view_name.TrimLeftAndRight(); if( view_name.IsEmpty() ) return CRhinoCommand::cancel; ON_SimpleArray view_list; int view_count = context.m_doc.GetViewList( view_list ); CRhinoView* active_view = NULL; int i; for( i = 0; i < view_count; i++ ) { CRhinoView* view = view_list[i]; if( view && view_name.CompareNoCase(view->Viewport().Name()) == 0 ) { active_view = view; break; } } if( !active_view ) { RhinoApp().Print( L"Unable to find viewport named %s\n", view_name ); return CRhinoCommand::nothing; } ::RhinoApp().SetActiveView( active_view ); CWnd* frame_wnd = active_view->GetParent(); if( frame_wnd ) { frame_wnd->ShowWindow( SW_SHOWMAXIMIZED ); frame_wnd->BringWindowToTop(); } return CRhinoCommand::success; } ``` -------------------------------------------------------------------------------- # Mesh Boolean Difference Source: https://developer.rhino3d.com/en/samples/cpp/mesh-boolean-difference/ Demonstrates how to use the RhinoMeshBooleanDifference function. ```cpp CRhinoCommand::result CCommandTest::RunCommand( const CRhinoCommandContext& context ) { CRhinoGetObject go0; go0.SetCommandPrompt( L"Select first set of meshes" ); go0.SetGeometryFilter( CRhinoGetObject::mesh_object ); go0.GetObjects( 1, 0 ); if( go0.CommandResult() != success ) return go0.CommandResult(); CRhinoGetObject go1; go1.SetCommandPrompt( L"Select second set of meshes" ); go1.SetGeometryFilter( CRhinoGetObject::mesh_object ); go1.EnablePreSelect( false ); go1.EnableDeselectAllBeforePostSelect( false ); go1.GetObjects( 1, 0 ); if( go1.CommandResult() != success ) return go1.CommandResult(); ON_SimpleArray DeleteList( go0.ObjectCount() + go1.ObjectCount() ); int i = 0; ON_SimpleArray InMeshes0( go0.ObjectCount() ); ON_SimpleArray InAttributes0( go0.ObjectCount() ); for( i = 0; i < go0.ObjectCount(); i++ ) { const CRhinoObject* p = go0.Object(i).Object(); if( !p ) return failure; const ON_Mesh* mesh = ON_Mesh::Cast( p->Geometry() ); if( !mesh ) return failure; InMeshes0.Append( mesh ); InAttributes0.Append( &p->Attributes() ); DeleteList.Append( p ); } ON_SimpleArray InMeshes1( go1.ObjectCount() ); for( i = 0; i < go1.ObjectCount(); i++ ) { const CRhinoObject* p = go1.Object(i).Object(); if( !p ) return failure; const ON_Mesh* mesh = ON_Mesh::Cast( p->Geometry() ); if( !mesh ) return failure; InMeshes1.Append( mesh ); DeleteList.Append( p ); } ON_SimpleArray OutMeshes; ON_SimpleArray OutAttributes; bool bResult = false; bool rc = RhinoMeshBooleanDifference( InMeshes0, InMeshes1, ON_SQRT_EPSILON*10, ON_SQRT_EPSILON*10, &bResult, OutMeshes, &InAttributes0, &OutAttributes ); if( !rc ) return failure; for( i = 0; i < OutMeshes.Count(); i++ ) { CRhinoMeshObject* pMeshObj = 0; if( i < OutAttributes.Count() ) pMeshObj = new CRhinoMeshObject( *OutAttributes[i] ); else pMeshObj = new CRhinoMeshObject(); if( pMeshObj ) { pMeshObj->SetMesh( OutMeshes[i] ); context.m_doc.AddObject( pMeshObj ); } else { delete OutMeshes[i]; } OutMeshes[i] = 0; } for( i = 0; i < DeleteList.Count(); i++ ) { if( DeleteList[i] ) context.m_doc.DeleteObject( DeleteList[i] ); } context.m_doc.Redraw(); return success; } ``` -------------------------------------------------------------------------------- # Mesh Boolean Intersection Source: https://developer.rhino3d.com/en/samples/cpp/mesh-boolean-intersection/ Demonstrates how to intersect meshes. ```cpp CRhinoCommand::result CCommandTest::RunCommand( const CRhinoCommandContext& context ) { int i = 0; CRhinoGetObject go; go.SetCommandPrompt( L"Select first set of meshes" ); go.SetGeometryFilter( CRhinoGetObject::mesh_object ); go.GetObjects( 1, 0 ); if( go.CommandResult() != CRhinoCommand::success ) return go.CommandResult(); ON_SimpleArray InMeshes0( go.ObjectCount() ); for( i = 0; i < go.ObjectCount(); i++ ) { const ON_Mesh* mesh = go.Object(i).Mesh(); if( 0 == mesh ) return CRhinoCommand::failure; InMeshes0.Append( mesh ); } go.SetCommandPrompt( L"Select second set of meshes" ); go.EnablePreSelect( false ); go.EnableDeselectAllBeforePostSelect( false ); go.GetObjects( 1, 0 ); if( go.CommandResult() != CRhinoCommand::success ) return go.CommandResult(); ON_SimpleArray InMeshes1( go.ObjectCount() ); for( i = 0; i < go.ObjectCount(); i++ ) { const ON_Mesh* mesh = go.Object(i).Mesh(); if( 0 == mesh ) return CRhinoCommand::failure; InMeshes1.Append( mesh ); } double intersection_tolerance = ON_SQRT_EPSILON * 10; double overlap_tolerance = ON_SQRT_EPSILON * 10; ON_SimpleArray OutMeshes; bool bSomethingHappened = false; bool rc = RhinoMeshBooleanIntersection( InMeshes0, InMeshes1, intersection_tolerance, overlap_tolerance, &bSomethingHappened, OutMeshes ); if( !rc ) { RhinoApp().Print( L"No mesh intersections found.\n" ); return CRhinoCommand::nothing; } for( i = 0; i < OutMeshes.Count(); i++ ) { CRhinoMeshObject* mesh_obj = new CRhinoMeshObject(); mesh_obj->SetMesh( OutMeshes[i] ); OutMeshes[i] = 0; if( context.m_doc.AddObject(mesh_obj) ) mesh_obj->Select( true ); else delete mesh_obj; } context.m_doc.Redraw(); return CRhinoCommand::success; } ``` -------------------------------------------------------------------------------- # Mesh Types Source: https://developer.rhino3d.com/en/guides/cpp/mesh-types/ This brief guide discusses the types of meshes found in Rhino. ## Which Mesh? There is an `ON_Brep::GetMeshes()` routine of the C/C++ SDK. You might find that very adequate meshes can be pulled from this routine when specifying `ON::render_mesh`. You may have also noticed that you can also run this routine with an enumeration for an "analysis mesh," a "default mesh," a "preview mesh," or "any mesh." What are the differences between all of these options? ## Discussion Here is an overview of the mesh types: 1. `ON::render_mesh` is a mesh for, obviously, rendering. This rendering can be for shaded or rendered display. It can also be used by rendering plugins. The quality of these meshes is controlled by the Meshes page in the Document Properties dialog, but can also be overridden on a per-object basis. 1. `ON::analysis_mesh` is used by analysis modes, such as curvature, draft angle, environment map, and zebra. 1. `ON::preview_mesh` is used when you use the Mesh command and poke the preview button - the pipeline needs a way to display preview meshes and this is it. 1. `ON::default` mesh returns `ON::render_mesh` if it exists. Otherwise it returns `ON::analysis_mesh` if it exists. 1. `ON::any_mesh` is only used when we want delete all meshes at one time. **NOTE**: render and analysis meshes do not appear automatically. Some command must trigger their creation, whether its just setting viewport for rendered display or running an analysis command. The can also be generated from plugins that call SDK functions. -------------------------------------------------------------------------------- # Mesh Volume Source: https://developer.rhino3d.com/en/samples/rhinocommon/mesh-volume/ Demonstrates how to calculate the volume of a user-specified closed mesh. ```cs partial class Examples { public static Result MeshVolume(RhinoDoc doc) { var gm = new GetObject(); gm.SetCommandPrompt("Select solid meshes for volume calculation"); gm.GeometryFilter = ObjectType.Mesh; gm.GeometryAttributeFilter = GeometryAttributeFilter.ClosedMesh; gm.SubObjectSelect = false; gm.GroupSelect = true; gm.GetMultiple(1, 0); if (gm.CommandResult() != Result.Success) return gm.CommandResult(); double volume = 0.0; double volume_error = 0.0; foreach (var obj_ref in gm.Objects()) { if (obj_ref.Mesh() != null) { var mass_properties = VolumeMassProperties.Compute(obj_ref.Mesh()); if (mass_properties != null) { volume += mass_properties.Volume; volume_error += mass_properties.VolumeError; } } } RhinoApp.WriteLine("Total volume = {0:f} (+/- {1:f})", volume, volume_error); return Result.Success; } } ``` ```python from Rhino.Input.Custom import GetObject, GeometryAttributeFilter from Rhino.DocObjects import ObjectType from Rhino.Geometry import VolumeMassProperties from Rhino.Commands import Result def RunCommand(): gm = GetObject() gm.SetCommandPrompt("Select solid meshes for volume calculation") gm.GeometryFilter = ObjectType.Mesh gm.GeometryAttributeFilter = GeometryAttributeFilter.ClosedMesh gm.SubObjectSelect = False gm.GroupSelect = True gm.GetMultiple(1, 0) if gm.CommandResult() != Result.Success: return gm.CommandResult() volume = 0.0 volume_error = 0.0 for obj_ref in gm.Objects(): if obj_ref.Mesh() != None: mass_properties = VolumeMassProperties.Compute(obj_ref.Mesh()) if mass_properties != None: volume += mass_properties.Volume volume_error += mass_properties.VolumeError print("Total volume = {0:f} (+/- {1:f})".format(volume, volume_error)) return Result.Success if __name__ == "__main__": RunCommand() ``` -------------------------------------------------------------------------------- # Mesh Volume Centroid Source: https://developer.rhino3d.com/en/samples/rhinoscript/mesh-volume-centroid/ Demonstrates how to calculate the volume centroid of a mesh. ```vbnet ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' MeshVolumeCentroid.rvb -- July 2009 ' Mary Fugier, Robert McNeel & Associates ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Option Explicit ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' Calculates the volume centroid of a mesh object. ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Sub MeshVolumeCentroid() Const RHINO_MESH = 32 Dim strObject, arrCentroid strObject = Rhino.GetObject("Select mesh for volume centroid calculation", RHINO_MESH) If Not IsNull(strObject) Then arrCentroid = Rhino.MeshVolumeCentroid(strObject) If IsArray(arrCentroid) Then Call Rhino.AddPoint(arrCentroid) Call Rhino.Print("Volume Centroid = " & Rhino.Pt2Str(arrCentroid)) End If End If End Sub ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' If dragged and dropped on Rhino, a "MeshVolumeCentroid" alias ' will be created. ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Rhino.AddStartupScript Rhino.LastLoadedScriptFile Rhino.AddAlias "MeshVolumeCentroid", "_NoEcho _-RunScript (MeshVolumeCentroid)" ``` -------------------------------------------------------------------------------- # Meshing Objects Source: https://developer.rhino3d.com/en/samples/cpp/meshing-objects/ Demonstrates how to mesh surface and polysurface objects using the RhinoMeshObjects function. ```cpp CRhinoCommand::result CCommandTest::RunCommand( const CRhinoCommandContext& context ) { // Select some geometry to mesh CRhinoGetObject go; go.SetCommandPrompt( L"Select surface or polysurface to mesh" ); go.SetGeometryFilter( CRhinoGetObject::surface_object | CRhinoGetObject::polysrf_object ); go.GetObjects( 1, 1 ); if( go.CommandResult() != success ) return go.CommandResult(); ON_SimpleArray objects; objects.Append( go.Object(0).Object() ); // RhinoMeshObjects need to know how to mesh the objects. This information is provided // by passing the function a ON_MeshParameters object. For details on this class, see // opennurbs_mesh.h. // In this example, instead of making up our own default mesh parameters, we will just // get some existing ones that we know work well. const CRhinoAppRenderMeshSettings& rms = RhinoApp().AppSettings().RenderMeshSettings(); //ON_MeshParameters mp = rms.QualityMeshParameters(); ON_MeshParameters mp = rms.FastMeshParameters(); // Set the user interface style. int ui_style = 0; // simple ui ON_ClassArray meshes; // Mesh the selected objects. CRhinoCommand::result rc = RhinoMeshObjects( objects, mp, ui_style, meshes ); if( rc == success ) { int i; for( i = 0; i < meshes.Count(); i++ ) { CRhinoObjectMesh& mesh = meshes[i]; CRhinoMeshObject* mesh_object = new CRhinoMeshObject( mesh.m_mesh_attributes ); mesh_object->SetMesh( mesh.m_mesh ); mesh.m_mesh = 0; context.m_doc.AddObject( mesh_object ); } context.m_doc.Redraw(); } return rc; } ``` -------------------------------------------------------------------------------- # Modify an Object's Color Source: https://developer.rhino3d.com/en/guides/cpp/modifying-an-objects-color/ This brief guide discuss how to modify an object's color using C/C++. ## Overview The color used to display an object is specified in one of four ways... 1. `ON::color_from_layer` - the object's layer color, `ON_Layer::Color()`, determines the object's color. This is the default method used when adding new objects to Rhino 1. `ON::color_from_object` - the value of an object's `m_color` attribute determines the object's color. 1. `ON::color_from_material` - the diffuse color of the object's render material determines the object's color. 1. `ON::color_from_parent` - if the object is part of an instance reference, the color is taken from the instance ## Sample The following code sample demonstrates how to override the default "color by layer" behavior and set an object to use "color by object." ```cpp CRhinoCommand::result CCommandTest::RunCommand(const CRhinoCommandContext& context) { CRhinoGetObject go; go.SetCommandPrompt( L"Select object" ); go.GetObjects( 1, 1 ); if( go.CommandResult() != CRhinoCommand::success ) return go.CommandResult(); const CRhinoObjRef& obj_ref = go.Object( 0 ); const CRhinoObject* obj = obj_ref.Object(); if( !obj ) return CRhinoCommand::failure; ON_Color old_color = obj->Attributes().DrawColor(); ON::object_color_source color_source = obj->Attributes().ColorSource(); ON_Color new_color( old_color ); if( !RhinoColorDialog( ::RhinoApp().MainWnd(), new_color) ) return CRhinoCommand::cancel; if( new_color == old_color ) return CRhinoCommand::nothing; CRhinoObjectAttributes att( obj->Attributes() ); att.m_color = new_color; att.SetColorSource( ON::color_from_object ); context.m_doc.ModifyObjectAttributes( obj_ref, att ); context.m_doc.Redraw(); return CRhinoCommand::success; } ``` When adding new objects using the C/C++ SDK, you can specify the attributes of the object when adding it to Rhino. Thus, if you want to override the default color behavior for new objects, just get a copy of the active document's default new object attributes, modify it in whatever way you want, and pass it (along with the geometry) to the appropriate object creation function... ```cpp CRhinoCommand::result CCommandTest::RunCommand(const CRhinoCommandContext& context) { ON_Circle circle; circle.Create( RhinoActiveCPlane(), 5.0 ); ON_3dmObjectAttributes att; context.m_doc.GetDefaultObjectAttributes( att ); att.m_color = RGB(255,191,191); att.SetColorSource( ON::color_from_object ); context.m_doc.AddCurveObject( circle, &att ); context.m_doc.Redraw(); return CRhinoCommand::success; } ``` -------------------------------------------------------------------------------- # Modify an Object's Name Source: https://developer.rhino3d.com/en/samples/cpp/modify-object-name/ Demonstrates how to modify an object's user-defined name. ```cpp //This sample code demonstrates how to modify the name of a single object. CRhinoCommand::result CCommandTest::RunCommand(const CRhinoCommandContext& context) { // Select an object to modify CRhinoGetObject go; go.SetCommandPrompt( L"Select object to change name" ); go.EnablePreSelect( TRUE ); go.EnableSubObjectSelect( FALSE ); go.GetObjects( 1, 1 ); if( go.CommandResult() != CRhinoCommand::success ) return go.CommandResult(); // Get the object reference const CRhinoObjRef& objref = go.Object(0); // Get the object const CRhinoObject* obj = objref.Object(); if( !obj ) return CRhinoCommand::failure; // Make copy of object attributes. This objects // holds an object's user-defined name. ON_3dmObjectAttributes obj_attribs = obj->Attributes(); // Prompt for new object name CRhinoGetString gs; gs.SetCommandPrompt( L"New object name" ); gs.SetDefaultString( obj_attribs.m_name ); gs.AcceptNothing( TRUE ); gs.GetString(); if( gs.CommandResult() != CRhinoCommand::success ) return gs.CommandResult(); // Get the string entered by the user ON_wString obj_name = gs.String(); obj_name.TrimLeftAndRight(); // Is name the same? if( obj_name.Compare(obj_attribs.m_name) == 0 ) return CRhinoCommand::nothing; // Modify the attributes of the object obj_attribs.m_name = obj_name; context.m_doc.ModifyObjectAttributes( objref, obj_attribs ); return CRhinoCommand::success; } ``` Alternatively: ```cpp //This sample code demonstrates how to modify the name of one or more objects. CRhinoCommand::result CCommandTest::RunCommand( const CRhinoCommandContext& context ) { // Select objects to modify CRhinoGetObject go; go.SetCommandPrompt( L"Select objects to change name" ); go.EnablePreSelect( TRUE ); go.EnableSubObjectSelect( FALSE ); go.GetObjects( 1, 0 ); if( go.CommandResult() != CRhinoCommand::success ) return go.CommandResult(); // Prompt for new object name CRhinoGetString gs; gs.SetCommandPrompt( L"New object name" ); gs.GetString(); if( gs.CommandResult() != CRhinoCommand::success ) return gs.CommandResult(); // Get the string entered by the user ON_wString obj_name = gs.String(); obj_name.TrimLeftAndRight(); // Process each selected object int i; for( i = 0; i < go.ObjectCount(); i++ ) { // Get the object reference const CRhinoObjRef& objref = go.Object(i); // Get the object const CRhinoObject* obj = objref.Object(); if( !obj ) return CRhinoCommand::failure; // Make copy of object attributes. This objects // holds an object's user-defined name. ON_3dmObjectAttributes obj_attribs = obj->Attributes(); // Is name the same? if( obj_name.Compare(obj_attribs.m_name) == 0 ) continue; // Modify the attributes of the object obj_attribs.m_name = obj_name; context.m_doc.ModifyObjectAttributes( objref, obj_attribs ); } return CRhinoCommand::success; } ``` ...or: ```cpp //This sample code demonstrates how to modify all normal (not locked and not hidden) objects. CRhinoCommand::result CCommandTest::RunCommand( const CRhinoCommandContext& context ) { // Prompt for new object name CRhinoGetString gs; gs.SetCommandPrompt( L"New object name" ); gs.GetString(); if( gs.CommandResult() != CRhinoCommand::success ) return gs.CommandResult(); // Get the string entered by the user ON_wString obj_name = gs.String(); obj_name.TrimLeftAndRight(); // Iterate through all normal (not locked and not hidden) objects CRhinoObjectIterator it( CRhinoObjectIterator::normal_objects, CRhinoObjectIterator::active_objects ); CRhinoObject* obj = 0; for( obj = it.First(); obj; obj = it.Next() ) { // Make copy of object attributes. This objects // holds an object's user-defined name. ON_3dmObjectAttributes obj_attribs = obj->Attributes(); // Is name the same? if( obj_name.Compare(obj_attribs.m_name) == 0 ) continue; // Modify the attributes of the object obj_attribs.m_name = obj_name; context.m_doc.ModifyObjectAttributes( CRhinoObjRef(obj), obj_attribs ); } return CRhinoCommand::success; } ``` -------------------------------------------------------------------------------- # Modify Flamingo Plant Source: https://developer.rhino3d.com/en/samples/rhinoscript/modify-flamingo-plant/ Demonstrates how to modify an existing Flamingo nXt plant using RhinoScript. ```vbnet Option Explicit Call Main() Sub Main() Dim objPlugIn, strObject, plant, xml, xmlDoc, node On Error Resume Next Set objPlugIn = Rhino.GetPluginObject("8008880f-8d13-4b2d-92b0-727e12878a4c") If Err Then MsgBox Err.Description Exit Sub End If strObject = Rhino.GetObject("Select object") If Not IsNull(strObject) And Not strObject = "" Then Set plant = objPlugIn.PlantFromObject(strObject) If IsNull(plant) Then Rhino.Print("Object is not a plant") Else xml = plant.XmlWithoutHeader If IsNull(xml) Then Rhino.Print("Error extracting plant XML") Else Set xmlDoc = CreateObject("Microsoft.XMLDOM") If IsNull(xmlDoc) Then Rhino.Print("Error creating XML document") Else xmlDoc.LoadXml(xml) Set node = xmlDoc.SelectSingleNode("ArPlantDef/PlantDef/nTrunks") If IsNull(node) Or IsEmpty(node) Then Rhino.Print("Error getting plant XML") Else 'Mofiy the trunk value changing it to 3 trunks node.Text = "3" If objPlugIn.ModifyPlantObject(strObject, xmlDoc.xml) Then Rhino.Print("Plant now has three trunks...") Else Rhino.Print("Error modifing plant...") End If End If End If End If End If End If Set plant = Nothing Set objPlugIn = Nothing End Sub ``` -------------------------------------------------------------------------------- # Modify Grip Weight Source: https://developer.rhino3d.com/en/samples/cpp/modify-grip-weight/ Demonstrates how to modify the weight of a grip object. ```cpp CRhinoCommand::result CCommandTest::RunCommand( const CRhinoCommandContext& context ) { // Pick a grip object CRhinoGetObject go; go.SetCommandPrompt( RHSTR(L"Select control point for weight editing") ); go.SetGeometryFilter( CRhinoGetObject::grip_object ); go.EnableReferenceObjectSelect( false ); go.GetObjects( 1, 1 ); if( go.CommandResult() != CRhinoCommand::success ) return go.CommandResult(); // Add the object to an xform object list CRhinoXformObjectList objlist; if( objlist.AddObjects(go, true) < 1 ) return CRhinoCommand::failure; // Get the grip's weight double weight = objlist.m_grips[0]->Weight(); // Prompt for a new value CRhinoGetNumber gn; gn.SetCommandPrompt( RHSTR(L"New control point weight") ); gn.SetDefaultNumber( weight ); gn.AcceptNothing(); // Validate the results CRhinoGet::result res = gn.GetNumber(); if( res == CRhinoGet::number ) weight = gn.Number(); else if( res == CRhinoGet::nothing ) return CRhinoCommand::nothing; else return CRhinoCommand::cancel; // Do nothing if weights are the same if( weight == objlist.m_grips[0]->Weight() ) return CRhinoCommand::nothing; // Modify the grip's weight objlist.m_grips[0]->SetWeight( weight ); // Get the grip object's owning object CRhinoObject* old_object = objlist.m_grip_owners[0]; if( old_object && old_object->m_grips ) { // Create a new object based on the updated grip information CRhinoObject* new_object = old_object->m_grips->NewObject(); if( new_object ) { // Replace the old object with the new object context.m_doc.ReplaceObject( CRhinoObjRef(old_object), new_object ); context.m_doc.Redraw(); } } return CRhinoCommand::success; } ``` -------------------------------------------------------------------------------- # Modify Light Color Source: https://developer.rhino3d.com/en/samples/rhinocommon/modify-light-color/ Demonstrates how to change the color of a user-specified light. ```cs partial class Examples { public static Result ModifyLightColor(RhinoDoc doc) { ObjRef obj_ref; var rc = RhinoGet.GetOneObject("Select light to change color", true, ObjectType.Light, out obj_ref); if (rc != Result.Success) return rc; var light = obj_ref.Light(); if (light == null) return Result.Failure; var diffuse_color = light.Diffuse; if (Dialogs.ShowColorDialog(ref diffuse_color)) { light.Diffuse = diffuse_color; } doc.Lights.Modify(obj_ref.ObjectId, light); return Result.Success; } } ``` ```python from Rhino import * from Rhino.DocObjects import * from Rhino.Input import * from Rhino.UI import * from Rhino.Commands import Result from scriptcontext import doc def RunCommand(): rc, obj_ref = RhinoGet.GetOneObject("Select light to change color", True, ObjectType.Light) if rc != Result.Success: return rc light = obj_ref.Light() if light == None: return Result.Failure b, color = Dialogs.ShowColorDialog(light.Diffuse) if b: light.Diffuse = color doc.Lights.Modify(obj_ref.ObjectId, light) return Result.Success if __name__ == "__main__": RunCommand() ``` -------------------------------------------------------------------------------- # Modify Object Color Source: https://developer.rhino3d.com/en/samples/rhinocommon/modify-object-color/ Demonstrates how to modify the color of a user-specified object. ```cs partial class Examples { public static Result ModifyObjectColor(RhinoDoc doc) { ObjRef obj_ref; var rc = RhinoGet.GetOneObject("Select object", false, ObjectType.AnyObject, out obj_ref); if (rc != Result.Success) return rc; var rhino_object = obj_ref.Object(); var color = rhino_object.Attributes.ObjectColor; bool b = Rhino.UI.Dialogs.ShowColorDialog(ref color); if (!b) return Result.Cancel; rhino_object.Attributes.ObjectColor = color; rhino_object.Attributes.ColorSource = ObjectColorSource.ColorFromObject; rhino_object.CommitChanges(); // an object's color attributes can also be specified // when the object is added to Rhino var sphere = new Sphere(Point3d.Origin, 5.0); var attributes = new ObjectAttributes(); attributes.ObjectColor = Color.CadetBlue; attributes.ColorSource = ObjectColorSource.ColorFromObject; doc.Objects.AddSphere(sphere, attributes); doc.Views.Redraw(); return Result.Success; } } ``` ```python from System.Drawing import * from Rhino import * from Rhino.DocObjects import * from Rhino.Geometry import * from Rhino.Input import * from Rhino.Commands import * from Rhino.UI.Dialogs import ShowColorDialog from scriptcontext import doc def RunCommand(): rc, obj_ref = RhinoGet.GetOneObject("Select object", False, ObjectType.AnyObject) if rc != Result.Success: return rc rhino_object = obj_ref.Object() color = rhino_object.Attributes.ObjectColor b, color = ShowColorDialog(color) if not b: return Result.Cancel rhino_object.Attributes.ObjectColor = color rhino_object.Attributes.ColorSource = ObjectColorSource.ColorFromObject rhino_object.CommitChanges() # an object's color attributes can also be specified # when the object is added to Rhino sphere = Sphere(Point3d.Origin, 5.0) attributes = ObjectAttributes() attributes.ObjectColor = Color.CadetBlue attributes.ColorSource = ObjectColorSource.ColorFromObject doc.Objects.AddSphere(sphere, attributes) doc.Views.Redraw() return Result.Success if __name__ == "__main__": RunCommand() ``` -------------------------------------------------------------------------------- # Modifying a Light's Color Source: https://developer.rhino3d.com/en/guides/cpp/modifying-light-colors/ This brief guide describes how to modify the diffuse color of an existing light using C/C++. ## Overview The process for modifying a light object is slightly different than the process for modifying other geometric objects, such as points, curves, and surfaces. This is because light objects are stored in a different location in the Rhino document. ## How To Light objects are stored in a `CRhinoLightTable` object that is held by the active document object. Thus, instead of using one of the `ModifyObject()` members found on `CRhinoDoc`, you need to use the `ModifyLight()` member found on `CRhinoLightTable` in order to modify an existing light object. For more details on `CRhinoLight` and `CRhinoLightTable`, see *rhinoSdkLight.h* included with the C/C++ SDK. ## Sample The following example demonstrates how to modify the diffuse color of a light object... ```cpp CRhinoCommand::result CCommandTest::RunCommand( const CRhinoCommandContext& context ) { // Pick an existing light object CRhinoGetObject go; go.SetCommandPrompt( L"Select light to change color" ); go.SetGeometryFilter( CRhinoGetObject::light_object ); go.GetObjects( 1, 1 ); if( go.CommandResult() != CRhinoCommand::success ) return go.CommandResult(); // The the light object CRhinoObjRef& ref = go.Object(0); const CRhinoLight* light_obj = CRhinoLight::Cast( ref.Object() ); if( !light_obj ) return CRhinoCommand::failure; // Prompt the user to pick a new color ON_Color color = light_obj->Light().Diffuse(); if( !RhinoColorDialog(RhinoApp().MainWnd(), color) ) CRhinoCommand::cancel; // Copy the light object's underlying ON_Light ON_Light light( light_obj->Light() ); // Modify the diffuse color light.SetDiffuse( color ); // Modify the light CRhinoLightTable& light_table = context.m_doc.m_light_table; light_table.ModifyLight( light, light_obj->LightIndex() ); return CRhinoCommand::success; } ``` -------------------------------------------------------------------------------- # Modifying Advanced Display Settings Source: https://developer.rhino3d.com/en/guides/cpp/modifying-advanced-display-settings/ This guide demonstrates how to modify advanced display settings using C/C++. ## Overview The advanced display features in Rhino give the user almost unlimited control over how objects appear on the screen. All of these features are also exposed to the C/C++ developer. Rhino maintains advanced display settings using the `CDisplayPipelineAttributes` class. Rhino will maintain a number of these objects, one for each advanced display setting created by the user (i.e. Wireframe, Shaded, Rendered, Ghosted, X-Ray, etc.) or by 3rd party plugins. The C/C++ developer can gain access to these objects using the Display Attributes Manager, which is implemented as a number of static functions found on the `CRhinoDisplayAttrsMgr` class. The process for updating advanced display settings is similar to updating or modifying other objects in Rhino. 1. Make a copy of the original. 1. Modify one or more setting or parameters. 1. Replace the original object with the modified copy. ## Sample ```cpp // The following example code demonstrates how to modify advanced display settings using // the Rhino SDK. In this example, a display mode's mesh wireframe thickness (in pixels) // will be modified. CRhinoCommand::result CCommandTest::RunCommand( const CRhinoCommandContext& context ) { // Use the display attributes manager to build a list of display modes. // Note, these are copies of the originals... DisplayAttrsMgrList attrs_list; int attrs_count = CRhinoDisplayAttrsMgr::GetDisplayAttrsList( attrs_list ); if( attrs_count == 0 ) return failure; // Construct an options picker so the user can pick which // display mode they want modified CRhinoGetOption go; go.SetCommandPrompt( L"Display mode to modify mesh thickness" ); ON_SimpleArray opt_list( attrs_count ); opt_list.SetCount( attrs_count ); for( int i = 0; i < attrs_count; i++ ) { // Verify the display mode had a valid // CDisplayPipelineAttributes pointer if( 0 == attrs_list[i].m_pAttrs ) { opt_list[i] = 0; continue; } // Get the display attributes English name ON_wString english_name = attrs_list[i].m_pAttrs->EnglishName(); english_name.Remove( L'_' ); english_name.Remove( L' ' ); english_name.Remove( L'-' ); english_name.Remove( L',' ); english_name.Remove( L'.' ); // Get the display attributes localized name ON_wString local_name = attrs_list[i].m_pAttrs->LocalName(); local_name.Remove( L'_' ); local_name.Remove( L' ' ); local_name.Remove( L'-' ); local_name.Remove( L',' ); local_name.Remove( L'.' ); // Add the command option opt_list[i] = go.AddCommandOption( CRhinoCommandOptionName(english_name, local_name) ); } // Get the command option go.GetOption(); if( go.CommandResult() != success ) return go.CommandResult(); const CRhinoCommandOption* opt = go.Option(); if( 0 == opt ) return failure; // Figure out which command option was picked int attrs_index = -1; for( int i = 0; i < opt_list.Count(); i++ ) { if( opt_list[i] == opt->m_option_index ) { attrs_index = i; break; } } // Validate... if( attrs_index < 0 | attrs_index >= attrs_count ) return failure; // Get the display mode requested by the user DisplayAttrsMgrListDesc desc = attrs_list[attrs_index]; if( 0 == desc.m_pAttrs ) return failure; // Modify the desired display mode. In this case, we // will just set the mesh wireframe thickness to zero. desc.m_pAttrs->m_nMeshWireThickness = 0; // Use the display attributes manager to update the display mode. CRhinoDisplayAttrsMgr::UpdateAttributes( desc ); // Force the document to regenerate. context.m_doc.Regen(); return success; } ``` -------------------------------------------------------------------------------- # Modifying Object Colors Source: https://developer.rhino3d.com/en/guides/rhinoscript/modifying-object-colors/ This guide demonstrates how to modify the color of objects using RhinoScript. ## Problem Changing object colors using Rhino's properties command can be slow when assigning colors to lots of objects. Imagine you would like to assign randomized colors across multiple objects or pick two colors and have Rhino generate all the blend colors. All of this is possible with RhinoScript. ## Solution All this is possible with the help of RhinoScript's `GetColor` and `ObjectColor` methods... The following sample script contains the following subroutines: - `SetObjectColor` - sets object colors. - `SetObjectColorRandom` - sets random object colors. - `SetObjectColorGraded` - sets gradient object colors based on the order picked. ```vbnet ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' ObjectColor.rvb -- February 2009 ' If this code works, it was written by Dale Fugier. ' If not, I don't know who wrote it. ' Works with Rhino 4.0. ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Option Explicit Randomize ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' Sets object colors ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Sub SetObjectColor() Dim objects, color objects = Rhino.GetObjects("Select objects to change colors", 0, True, True) If IsNull(objects) Then Exit Sub color = Rhino.GetColor If IsNull(color) Then Exit Sub Rhino.ObjectColor objects, color End Sub ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' Set random object colors ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Sub SetObjectColorRandom() Dim objects, red, green, blue, i objects = Rhino.GetObjects("Select objects for randomly color change", 0, True, True) If IsNull(objects) Then Exit Sub Rhino.EnableRedraw False For i = 0 To UBound(objects) red = Int(255 * Rnd) green = Int(255 * Rnd) blue = Int(255 * Rnd) Rhino.ObjectColor objects(i), RGB(red, green, blue) Next Rhino.EnableRedraw True End Sub ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' Sets gradient object colors (based on the order picked) ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Sub SetObjectColorGraded() Dim objects, color0, color1, color, i, bound Dim red, red0, red1 Dim green, green0, green1 Dim blue, blue0, blue1, percent objects = Rhino.GetObjects("Select objects for gradient color change", 0, True, True) If IsNull(objects) Then Exit Sub color0 = Rhino.GetColor If IsNull(color0) Then Exit Sub color1 = Rhino.GetColor If IsNull(color1) Then Exit Sub ' Extract red-green-blue components red0 = color0 And &HFF red1 = color1 And &HFF green0 = (color0 \ &H100) And &HFF green1 = (color1 \ &H100) And &HFF blue0 = (color0 \ &H10000) And &HFF blue1 = (color1 \ &H10000) And &HFF bound = UBound(objects) Rhino.EnableRedraw False For i = 0 To bound ' A linearly interpreted gradient just calculates the new RGB values by applying a ' target value percent of the linear range to the each RGB component range. percent = i/bound red = red0 + Int(percent * (red1 - red0)) green = green0 + Int(percent * (green1 - green0)) blue = blue0 + Int(percent * (blue1 - blue0)) Rhino.ObjectColor objects(i), RGB(red, green, blue) Next Rhino.EnableRedraw True End Sub ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Rhino.AddStartupScript Rhino.LastLoadedScriptFile Rhino.AddAlias "SetObjectColor", "_NoEcho _-RunScript (SetObjectColor)" Rhino.AddAlias "SetObjectColorRandom", "_NoEcho _-RunScript (SetObjectColorRandom)" Rhino.AddAlias "SetObjectColorGraded", "_NoEcho _-RunScript (SetObjectColorGraded)" ``` -------------------------------------------------------------------------------- # Morphing a Surface Source: https://developer.rhino3d.com/en/samples/rhinoscript/morphing-a-surface/ An example of how to morph a surface using RhinoScript. ```vbnet '' '' Morphing example (morph.rvb) '' '' Copyright 2004 Stylianos Dritsas '' '' This software is provided 'as-is', without any expressed or implied warranty. '' In no event will the author be held liable for any damages arising from the '' use of this software. '' '' Permission is granted to anyone to use this software for any purpose, '' including commercial applications, and to alter it and redistribute it '' freely, subject to the following restrictions: '' '' 1. The origin of this software must not be misrepresented; you must not '' claim that you wrote the original software. If you use this software '' in a product, an acknowledgment in the product documentation would be '' appreciated but is not required. '' 2. Altered source versions must be plainly marked as such, and must not be '' misrepresented as being the original software. '' 3. This notice may not be removed or altered from any source distribution. '' '' Stylianos Dritsas dritsas@alum.mit.edu '' Dim u: u = 0: Dim min: min = 0 Dim v: v = 1: Dim max: max = 1 sa = Rhino.GetObject( "sa" ) sb = Rhino.GetObject( "sb" ) Dim steps: steps = 12 For i = 1 To steps - 1 Call surface_interpolate( sa, sb, 10, 10, i/steps ) Next Function surface_interpolate( sa, sb, cols, rows, factor ) Dim nu: nu = cols Dim nv: nv = rows Dim cpa: cpa = Array( nu, nv ) Dim cpb: cpb = Array( nu, nv ) Dim da: da = Array( Rhino.surfacedomain( sa, U ), Rhino.surfacedomain( sa, V ) ) Dim db: db = Array( Rhino.surfacedomain( sb, U ), Rhino.surfacedomain( sb, V ) ) Dim dua: dua = ( da( U )( MAX ) - da( U )( MIN ) ) / ( cpa( U ) - 1 ) Dim dva: dva = ( da( V )( MAX ) - da( V )( MIN ) ) / ( cpa( V ) - 1 ) Dim ua: ua = da( U )( MIN ) Dim va: va = da( V )( MIN ) Dim dub: dub = ( db( U )( MAX ) - db( U )( MIN ) ) / ( cpb( U ) - 1 ) Dim dvb: dvb = ( db( V )( MAX ) - db( V )( MIN ) ) / ( cpb( V ) - 1 ) Dim ub: ub = db( U )( MIN ) Dim vb: vb = db( V )( MIN ) Dim point: point = 0 ReDim points( nu * nv - 1 ) Dim pu, pv For pu = 0 To nu - 1 For pv = 0 To nv - 1 Dim pa: pa = Rhino.evaluatesurface( sa, Array( pu * dua + ua, pv * dva + va ) ) Dim pb: pb = Rhino.evaluatesurface( sb, Array( pu * dub + ub, pv * dvb + vb ) ) points( point ) = vertex_interpolate( pa, pb, factor ) point = point + 1 Next Next surface_interpolate = Rhino.addsrfptgrid( cpa, points ) End Function ``` -------------------------------------------------------------------------------- # Move a Construction Plane Source: https://developer.rhino3d.com/en/samples/cpp/move-construction-plane/ Demonstrates how to move the origin of a construction plane. ```cpp class CTestMoveCPlanePoint : public CRhinoGetPoint { public: CTestMoveCPlanePoint( const ON_3dmConstructionPlane& cplane ); ~CTestMoveCPlanePoint() {} void SetConstructionPlane(const ON_3dmConstructionPlane& cplane); void OnMouseMove( CRhinoViewport& vp, UINT flags, const ON_3dPoint& pt, const CPoint* pt2d ); void DynamicDraw( HDC hdc, CRhinoViewport& vp, const ON_3dPoint& pt ); private: ON_3dmConstructionPlane m_cplane; }; CTestMoveCPlanePoint::CTestMoveCPlanePoint(const ON_3dmConstructionPlane& cplane) : m_cplane(cplane) { } void CTestMoveCPlanePoint::OnMouseMove( CRhinoViewport& vp, UINT flags, const ON_3dPoint& pt, const CPoint* pt2d ) { m_cplane.m_plane.CreateFromFrame( pt, m_cplane.m_plane.xaxis, m_cplane.m_plane.yaxis ); CRhinoGetPoint::OnMouseMove( vp, flags, pt, pt2d ); } void CTestMoveCPlanePoint::DynamicDraw(HDC hdc, CRhinoViewport& vp, const ON_3dPoint& pt) { vp.DrawConstructionPlane( m_cplane, FALSE, TRUE ); CRhinoGetPoint::DynamicDraw( hdc, vp, pt ); } CRhinoCommand::result CCommandTest::RunCommand(const CRhinoCommandContext& context) { CRhinoView* view = ::RhinoApp().ActiveView(); if( !view ) return CRhinoCommand::failure; ON_3dmConstructionPlane cplane = view->Viewport().ConstructionPlane(); ON_3dPoint origin = cplane.m_plane.origin; CTestMoveCPlanePoint gp( cplane ); gp.SetCommandPrompt( L"CPlane origin" ); gp.SetBasePoint( origin ); gp.DrawLineFromPoint( origin, TRUE ); gp.GetPoint(); if( gp.CommandResult() != CRhinoCommand::success ) return gp.CommandResult(); ON_3dPoint pt = gp.Point(); ON_3dVector v = origin - pt; if( v.IsTiny() ) return CRhinoCommand::nothing; cplane.m_plane.CreateFromFrame( pt, cplane.m_plane.xaxis, cplane.m_plane.yaxis ); view->Viewport().SetConstructionPlane( cplane ); view->Redraw(); return CRhinoCommand::success; } ``` -------------------------------------------------------------------------------- # Move CPlane Source: https://developer.rhino3d.com/en/samples/rhinocommon/move-cplane/ Demonstrates how to move a CPlane in the active viewport. ```cs class MoveCPlanePoint : Rhino.Input.Custom.GetPoint { readonly Rhino.DocObjects.ConstructionPlane m_cplane; public MoveCPlanePoint(Rhino.DocObjects.ConstructionPlane cplane) { m_cplane = cplane; } protected override void OnMouseMove(Rhino.Input.Custom.GetPointMouseEventArgs e) { Plane pl = m_cplane.Plane; pl.Origin = e.Point; m_cplane.Plane = pl; } protected override void OnDynamicDraw(Rhino.Input.Custom.GetPointDrawEventArgs e) { e.Display.DrawConstructionPlane(m_cplane); } } partial class Examples { public static Rhino.Commands.Result MoveCPlane(Rhino.RhinoDoc doc) { Rhino.Display.RhinoView view = doc.Views.ActiveView; if (view == null) return Rhino.Commands.Result.Failure; Rhino.DocObjects.ConstructionPlane cplane = view.ActiveViewport.GetConstructionPlane(); Point3d origin = cplane.Plane.Origin; MoveCPlanePoint gp = new MoveCPlanePoint(cplane); gp.SetCommandPrompt("CPlane origin"); gp.SetBasePoint(origin, true); gp.DrawLineFromPoint(origin, true); gp.Get(); if (gp.CommandResult() != Rhino.Commands.Result.Success) return gp.CommandResult(); Point3d point = gp.Point(); Vector3d v = origin - point; if (v.IsTiny()) return Rhino.Commands.Result.Nothing; Plane pl = cplane.Plane; pl.Origin = point; cplane.Plane = pl; view.ActiveViewport.SetConstructionPlane(cplane); view.Redraw(); return Rhino.Commands.Result.Success; } } ``` ```python import Rhino import scriptcontext class MoveCPlanePoint(Rhino.Input.Custom.GetPoint): def __init__(self, cplane): self.m_cplane = cplane def OnMouseMove(self, e): pl = self.m_cplane.Plane pl.Origin = e.Point self.m_cplane.Plane = pl def OnDynamicDraw(self, e): e.Display.DrawConstructionPlane(self.m_cplane); def MoveCPlane(): view = scriptcontext.doc.Views.ActiveView if not view: return Rhino.Commands.Result.Failure cplane = view.ActiveViewport.GetConstructionPlane() origin = cplane.Plane.Origin gp = MoveCPlanePoint(cplane) gp.SetCommandPrompt("CPlane origin") gp.SetBasePoint(origin, True) gp.DrawLineFromPoint(origin, True) gp.Get() if gp.CommandResult()!=Rhino.Commands.Result.Success: return gp.CommandResult() point = gp.Point() v = origin - point if v.IsTiny(): return Rhino.Commands.Result.Nothing pl = cplane.Plane pl.Origin = point cplane.Plane = pl view.ActiveViewport.SetConstructionPlane(cplane) view.Redraw() return Rhino.Commands.Result.Success if __name__=="__main__": MoveCPlane() ``` -------------------------------------------------------------------------------- # Move Curve Grips Source: https://developer.rhino3d.com/en/samples/rhinoscript/move-curve-grips/ Demonstrates how to move a curve's grips using RhinoScript. ```vbnet Sub MoveCurveGrip Const rhCurve = 4 Dim sCurve : sCurve = Rhino.GetObject("Select a curve", rhCurve) If IsNull(sCurve) Then Exit Sub Dim bGripsOn : bGripsOn = Rhino.ObjectGripsOn(sCurve) If (bGripsOn = False) Then bGripsOn = Rhino.EnableObjectGrips(sCurve, True) End If Dim aGrip : aGrip = Rhino.GetObjectGrip("Select a curve grip") If IsArray(aGrip) Then Dim aPt : aPt = aGrip(2) aPt(2) = aPt(2) + 1.0 Rhino.ObjectGripLocation sCurve, aGrip(1), aPt End If If (bGripsOn = True) Then Rhino.EnableObjectGrips sCurve, False End If End Sub ``` -------------------------------------------------------------------------------- # Move Grip Objects Source: https://developer.rhino3d.com/en/samples/rhinocommon/move-grip-objects/ Demonstrates how to move grip objects on a selected surface. ```cs partial class Examples { public static Rhino.Commands.Result MoveGripObjects(Rhino.RhinoDoc doc) { // The following example demonstrates how to move a surface's grip objects. // In this sample, all grips will be moved a fixed distance of 0.5 units // in the normal direction of the surface at that grip location. Rhino.DocObjects.ObjRef objRef; Rhino.Commands.Result rc = Rhino.Input.RhinoGet.GetOneObject("Select surface for control point editing", false, Rhino.DocObjects.ObjectType.Surface, out objRef); if (rc != Rhino.Commands.Result.Success) return rc; Rhino.DocObjects.RhinoObject obj = objRef.Object(); if (null == obj) return Rhino.Commands.Result.Failure; Rhino.Geometry.Surface srf = objRef.Surface(); if (null == srf) return Rhino.Commands.Result.Failure; // Make sure the object's grips are enabled obj.GripsOn = true; doc.Views.Redraw(); Rhino.DocObjects.GripObject[] grips = obj.GetGrips(); for (int i = 0; i < grips.Length; i++) { Rhino.DocObjects.GripObject grip = grips[i]; // Calculate the point on the surface closest to our test point, // which is the grip's 3-D location (for this example). double u, v; if (srf.ClosestPoint(grip.CurrentLocation, out u, out v)) { // Compute the surface normal at a point Rhino.Geometry.Vector3d dir = srf.NormalAt(u, v); dir.Unitize(); // Scale by our fixed distance dir *= 0.5; // Move the grip to a new location grip.Move(dir); } } // Altered grip positions on a RhinoObject are used to calculate an updated // object that is added to the document. doc.Objects.GripUpdate(obj, false); doc.Views.Redraw(); return Rhino.Commands.Result.Success; } } ``` ```python # No Python sample available ``` -------------------------------------------------------------------------------- # Move Objects to Current Layer Source: https://developer.rhino3d.com/en/samples/rhinocommon/move-objects-to-current-layer/ Demonstrates how to move object to the currently active layer. ```cs partial class Examples { public static Result MoveObjectsToCurrentLayer(RhinoDoc doc) { // all non-light objects that are selected var object_enumerator_settings = new ObjectEnumeratorSettings(); object_enumerator_settings.IncludeLights = false; object_enumerator_settings.IncludeGrips = true; object_enumerator_settings.NormalObjects = true; object_enumerator_settings.LockedObjects = true; object_enumerator_settings.HiddenObjects = true; object_enumerator_settings.ReferenceObjects = true; object_enumerator_settings.SelectedObjectsFilter = true; var selected_objects = doc.Objects.GetObjectList(object_enumerator_settings); var current_layer_index = doc.Layers.CurrentLayerIndex; foreach (var selected_object in selected_objects) { selected_object.Attributes.LayerIndex = current_layer_index; selected_object.CommitChanges(); } doc.Views.Redraw(); return Result.Success; } } ``` ```python from Rhino import * from Rhino.Commands import * from Rhino.DocObjects import * from scriptcontext import doc def RunCommand(): # all non-light objects that are selected object_enumerator_settings = ObjectEnumeratorSettings() object_enumerator_settings.IncludeLights = False object_enumerator_settings.IncludeGrips = True object_enumerator_settings.NormalObjects = True object_enumerator_settings.LockedObjects = True object_enumerator_settings.HiddenObjects = True object_enumerator_settings.ReferenceObjects = True object_enumerator_settings.SelectedObjectsFilter = True selected_objects = doc.Objects.GetObjectList(object_enumerator_settings) current_layer_index = doc.Layers.CurrentLayerIndex for selected_object in selected_objects: selected_object.Attributes.LayerIndex = current_layer_index selected_object.CommitChanges() doc.Views.Redraw() return Result.Success if __name__ == "__main__": RunCommand() ``` -------------------------------------------------------------------------------- # Move Objects to the Current Layer Source: https://developer.rhino3d.com/en/samples/cpp/move-objects-to-current-layer/ Demonstrates how to iterate through the Rhino geometry table and modify the layer of selected objects. ```cpp CRhinoCommand::result CCommandTest::RunCommand( const CRhinoCommandContext& context ) { // Get the current layer index const CRhinoLayerTable& layer_table = context.m_doc.m_layer_table; int layer_index = layer_table.CurrentLayerIndex(); // Create an object iterator that filters on selected, // non-light objects in the current document only CRhinoObjectIterator it( CRhinoObjectIterator::normal_objects, CRhinoObjectIterator::active_objects ); it.EnableSelectedFilter( TRUE ); it.IncludeLights( FALSE ); CRhinoObject* obj = NULL; int count = 0; // Walk the geometry table for( obj = it.First(); obj; obj = it.Next() ) { // Ignore select objects that are already on the current layer if( obj->Attributes().m_layer_index == layer_index ) continue; // Copy the object's attributes and set the new layer index CRhinoObjectAttributes atts( obj->Attributes() ); atts.m_layer_index = layer_index; // Modify the object's attributes CRhinoObjRef ref(obj); if( context.m_doc.ModifyObjectAttributes(ref, atts) ) count++; } if( count > 0 ) context.m_doc.Redraw(); return CRhinoCommand::success; } ``` -------------------------------------------------------------------------------- # Move Points Source: https://developer.rhino3d.com/en/samples/rhinocommon/move-points/ Demonstrates how to move user-specified points to a new location. ```cs partial class Examples { public static Result MovePointObjects(RhinoDoc doc) { ObjRef[] obj_refs; var rc = RhinoGet.GetMultipleObjects("Select points to move", false, ObjectType.Point, out obj_refs); if (rc != Result.Success || obj_refs == null) return rc; var gp = new GetPoint(); gp.SetCommandPrompt("Point to move from"); gp.Get(); if (gp.CommandResult() != Result.Success) return gp.CommandResult(); var start_point = gp.Point(); gp.SetCommandPrompt("Point to move to"); gp.SetBasePoint(start_point, false); gp.DrawLineFromPoint(start_point, true); gp.Get(); if (gp.CommandResult() != Result.Success) return gp.CommandResult(); var end_point = gp.Point(); var xform = Transform.Translation(end_point - start_point); foreach (var obj_ref in obj_refs) { doc.Objects.Transform(obj_ref, xform, true); } doc.Views.Redraw(); return Result.Success; } } ``` ```python from Rhino import * from Rhino.DocObjects import * from Rhino.Commands import * from Rhino.Input import * from Rhino.Input.Custom import * from Rhino.Geometry import * from scriptcontext import doc def RunCommand(): rc, obj_refs = RhinoGet.GetMultipleObjects("Select points to move", False, ObjectType.Point) if rc != Result.Success or obj_refs == None: return rc gp = GetPoint() gp.SetCommandPrompt("Point to move from") gp.Get() if gp.CommandResult() != Result.Success: return gp.CommandResult() start_point = gp.Point() gp.SetCommandPrompt("Point to move to") gp.SetBasePoint(start_point, False) gp.DrawLineFromPoint(start_point, True) gp.Get() if gp.CommandResult() != Result.Success: return gp.CommandResult() end_point = gp.Point() xform = Transform.Translation(end_point - start_point) for obj_ref in obj_refs: doc.Objects.Transform(obj_ref, xform, True) doc.Views.Redraw() return Result.Success if __name__ == "__main__": RunCommand() ``` -------------------------------------------------------------------------------- # Move Points Non Uniform Source: https://developer.rhino3d.com/en/samples/rhinocommon/move-points-non-uniform/ Demonstrates how to move points in a non-uniform manner. ```cs partial class Examples { public static Result MovePointObjectsNonUniform(RhinoDoc doc) { ObjRef[] obj_refs; var rc = RhinoGet.GetMultipleObjects("Select points to move", false, ObjectType.Point, out obj_refs); if (rc != Result.Success || obj_refs == null) return rc; foreach (var obj_ref in obj_refs) { var point3d = obj_ref.Point().Location; // modify the point coordinates in some way ... point3d.X++; point3d.Y++; point3d.Z++; doc.Objects.Replace(obj_ref, point3d); } doc.Views.Redraw(); return Result.Success; } } ``` ```python from Rhino import * from Rhino.DocObjects import * from Rhino.Commands import * from Rhino.Input import * from scriptcontext import doc def RunCommand(): rc, obj_refs = RhinoGet.GetMultipleObjects("Select points to move", False, ObjectType.Point) if rc != Result.Success or obj_refs == None: return rc for obj_ref in obj_refs: point3d = obj_ref.Point().Location point3d.X += 1 point3d.Y += 1 point3d.Z += 1 doc.Objects.Replace(obj_ref, point3d) doc.Views.Redraw() return Result.Success if __name__ == "__main__": RunCommand() ``` -------------------------------------------------------------------------------- # Move Surface Grips Source: https://developer.rhino3d.com/en/samples/rhinoscript/move-surface-grips/ Demonstrates how to move a surface's grip objects using RhinoScript. ```vbnet Option Explicit ' Moves a surface grip Sub MoveSurfaceGrip ' The name of the surface we are going to manipulate Dim object_name object_name = "Hull" ' The distance to move the control point(s) Dim distance distance = 0.5 ' Find the object by object_name Dim object_list object_list = Rhino.ObjectsByName(object_name) If Not IsArray(object_list) Then Rhino.Print "Object not found." Exit Sub End If ' In case more than one were found, ' use the first on in the array Dim object_id object_id = object_list(0) ' Make sure is really is a surface If Not Rhino.IsSurface(object_id) Then Rhino.Print "Object is not a surface." Exit Sub End If ' Get all of the selected grips Dim grip_list grip_list = Rhino.SelectedObjectGrips(object_id) If Not IsArray(grip_list) Then Rhino.Print "No grips selected." Exit Sub End If ' Process each grip Dim grip_idx, grip_pt, new_pt, uv Dim normal_pts, normal, normal_scaled For Each grip_idx In grip_list ' Get the grip's location grip_pt = Rhino.ObjectGripLocation(object_id, grip_idx) ' Find the parameter on the surface closest to the location uv = Rhino.SurfaceClosestPoint(object_id, grip_pt) ' Find the normal to the parameter normal_pts = Rhino.SurfaceNormal(object_id, uv) ' Create a vector from the results normal = VectorCreate( normal_pts(0), normal_pts(1) ) ' Scale the vector based on the specified distance normal_scaled = VectorScale( normal, distance ) ' Modify the point location new_pt = PointAddVector( grip_pt, normal_scaled ) ' Update the grip location Rhino.ObjectGripLocation object_id, grip_idx, new_pt Next End Sub ' NOTE, V4 contains a number of vector related ' functions. Thus, the following would not be ' necessary if we were scripting in V4. ' Creates a 3D vector from 2- 3D points Function VectorCreate(p1, p2) VectorCreate = Null If Not IsArray(p1) Or (UBound(p1) <> 2) Then Exit Function If Not IsArray(p2) Or (UBound(p2) <> 2) Then Exit Function VectorCreate = Array(p2(0) - p1(0), p2(1) - p1(1), p2(2) - p1(2)) End Function ' Scale a 3D vector Function VectorScale(v, d) VectorScale = Null If Not IsArray(v) Or (UBound(v) <> 2) Then Exit Function If Not IsNumeric(d) Then Exit Function VectorScale = Array(v(0) * d, v(1) * d, v(2) * d) End Function ' Adds a 3D vector to a 3D point Function PointAddVector(p, v) PointAddVector = Null If Not IsArray(p) Or (UBound(p) <> 2) Then Exit Function If Not IsArray(v) Or (UBound(v) <> 2) Then Exit Function PointAddVector = Array(p(0) + v(0), p(1) + v(1), p(2) + v(2)) End Function ``` -------------------------------------------------------------------------------- # Moving Curve and Surface Grips Source: https://developer.rhino3d.com/en/guides/cpp/moving-curve-and-surface-grips/ This guide demonstrates how to move curve and surface object grips using C/C++. ## Problem You would like to move the control points of a curve or surface object using the Rhino C/C++ SDK. ## Solution The curve and surface grips you see on the screen, after running Rhino's *PointsOn* command, are represented by `CRhinoGripObject`-derived objects. To move a grip object, you have to do a few things: 1. Get a `CRhinoGripObject`. You can use a `CRhinoGetObject` object to do this. 1. Call `CRhinoGripObject::MoveGrip` to transform the grip's location. 1. Call `CRhinoGripObject::Owner` to get the grips owning `CRhinoObject` object. 1. Call `CRhinoObject::NewObject` to create a new `CRhinoObject` object based on the new grip location. 1. Call `CRhinoDoc::ReplaceObject` to replace the original owning object with the new one. ## Sample The following sample code demonstrates this. **NOTE**: This sample uses a `CRhinoXformObjectList` object to maintain the list of grips and grip owners. ```cpp CRhinoCommand::result CCommandMoveGrips::RunCommand( const CRhinoCommandContext& context ) { CRhinoGetObject go; go.SetCommandPrompt( L"Select grips to move" ); go.SetGeometryFilter( CRhinoGetObject::grip_object ); go.GetObjects( 1, 0 ); if( go.CommandResult() != success ) return go.CommandResult(); CRhinoXformObjectList xform_list; if( xform_list.AddObjects(go, true) < 1 ) return failure; CRhinoGetPoint gp; gp.SetCommandPrompt( L"Point to move from" ); gp.GetPoint(); if( gp.CommandResult() != success ) return gp.CommandResult(); ON_3dPoint from = gp.Point(); gp.SetCommandPrompt( L"Point to move to" ); gp.SetBasePoint( from ); gp.DrawLineFromPoint( from, TRUE ); gp.GetPoint(); if( gp.CommandResult() != success ) return gp.CommandResult(); ON_3dPoint to = gp.Point(); ON_Xform xform; xform.Translation( to - from ); if( xform.IsValid() ) { // Transform the grip objects int i; for( i = 0; i < xform_list.m_grips.Count(); i++ ) xform_list.m_grips[i]->MoveGrip( xform ); // Replace the old owner with a new one for( i = 0; i < xform_list.m_grip_owners.Count(); i++ ) { CRhinoObject* old_object = xform_list.m_grip_owners[i]; if( old_object ) { CRhinoObject* new_object = old_object->m_grips->NewObject(); if( new_object ) context.m_doc.ReplaceObject( CRhinoObjRef(old_object), new_object, true ); } } context.m_doc.Redraw(); } return success; } ``` -------------------------------------------------------------------------------- # Moving Mesh Vertices Source: https://developer.rhino3d.com/en/guides/cpp/moving-mesh-vertices/ This brief guide demonstrates how to move mesh vertices using C/C++. ## Problem You would like to modify a particular point, or vertex, of `CRhinoMeshObject` object. ## Solution A `CRhinoMeshObject`'s geometric data member is an `ON_Mesh` object. For information on the `ON_Mesh` class, the *opennurbs_mesh.h* header file. Mesh vertices are stored on an `ON_Mesh` in an `m_V` data member, which is simply an array of points. So, if you want to modify the vertices of a mesh, you need to modify the data in this array. In order to modify anything in Rhino, you might: 1. Get the object. 1. Make a copy of the object. 1. Modify this copied object. 1. Call one of the `CRhinoDoc::ReplaceObject` overrides to update the object. ## Sample The following sample demonstrates how you might do this from a command... ```cpp CRhinoCommand::result CCommandTest::RunCommand( const CRhinoCommandContext& context ) { CRhinoGetObject gv; gv.SetCommandPrompt( L"Select mesh vertex to move" ); gv.SetGeometryFilter( CRhinoGetObject::meshvertex_object ); gv.GetObjects( 1, 1 ); if( gv.CommandResult() != success ) return gv.CommandResult(); const CRhinoObject* obj = gv.Object(0).Object(); const ON_MeshVertexRef* vertex = gv.Object(0).MeshVertex(); if( 0 == obj | 0 == vertex ) return failure; const ON_Mesh* mesh = vertex->m_mesh; if( 0 == mesh ) return failure; ON_3dPoint pt = mesh->m_V[vertex->m_mesh_vi]; CRhinoGetPoint gp; gp.SetCommandPrompt( L"New location" ); gp.SetBasePoint( pt ); gp.DrawLineFromPoint( pt, TRUE ); gp.GetPoint(); if( gp.CommandResult() != success ) return gp.CommandResult(); ON_Mesh dupe_mesh( *mesh ); dupe_mesh.SetVertex( vertex->m_mesh_vi, gp.Point() ); // Since we've modified ON_Mesh.m_V array, // we need to invalidate a few things so they // can be recalculated based on the new data. dupe_mesh.InvalidateVertexBoundingBox(); dupe_mesh.InvalidateVertexNormalBoundingBox(); dupe_mesh.InvalidateCurvatureStats(); dupe_mesh.m_FN.SetCount(0); dupe_mesh.m_N.SetCount(0); dupe_mesh.ComputeFaceNormals(); dupe_mesh.ComputeVertexNormals(); dupe_mesh.SetClosed(-1); if( dupe_mesh.IsValid() ) { context.m_doc.ReplaceObject( CRhinoObjRef(obj), dupe_mesh ); context.m_doc.Redraw(); } return success; } ``` -------------------------------------------------------------------------------- # Multidimensional Arrays Source: https://developer.rhino3d.com/en/guides/rhinoscript/multidimensional-arrays/ This guide discusses rectangular and ragged multidimensional arrays. ## Overview VBScript supports two kinds of multidimensional arrays, called rectangular and ragged. Why are there two types of multidimensional arrays? What is the difference between the (x)(y) and (x,y) notation? We will cover these questions as well as talk about resizing multidimensional arrays. ## Rectangular A rectangular array is, well, "rectangular." For example: ```vbnet Dim MyArray(3,2) ``` and you get: ```vbs (0,0) (0,1) (0,2) (1,0) (1,1) (1,2) (2,0) (2,1) (2,2) (3,0) (3,1) (3,2) ``` which makes a nice rectangle. A three-dimensional array makes a rectangular prism, and so on up into the higher dimensions. ## Ragged A common practice used by RhinoScript is to simulate multidimensional arrays by making an array of arrays known as ragged or nested arrays. For example: ```vbnet Dim MyArray MyArray = Array(Array(1, 2, 3), Array(4, 5), Array(6, 7, 8, 9)) ``` And so dereferencing the outer array gives you the inner array, which can then be dereferenced itself: ```vbnet Rhino.Print MyArray(2)(0) ' 6 ``` But, you notice something about the indices if we write them out as before: ```vbs (0)(0) (0)(1) (0)(2) (1)(0) (1)(1) (2)(0) (2)(1) (2)(2) (2)(3) ``` The indices make a ragged pattern, not a straight rectangular pattern. It is possible to create ragged higher dimensional, but allocating all the sub-arrays can be difficult. Thus, in VBScript if you say: ```vbnet MyArray(2,3) ``` then you are talking to a rectangular two-dimensional array. And, if you say: ```vbnet MyArray(2)(3) ``` then you are talking to a one dimensional array that contains another one dimensional array. ## Resizing The `ReDim` statement is used to size or resize a dynamic array that has already been formally declared using a `Private`, `Public`, or `Dim` statement with empty parentheses (without dimension subscripts). You can use the `ReDim` statement repeatedly to change the number of elements and dimensions in an array. If you use the `Preserve` keyword, you can resize only the last array dimension, and you can't change the number of dimensions at all. For example, if your array has only one dimension, you can resize that dimension because it is the last and only dimension. However, if your array has two or more dimensions, you can change the size of only the last dimension and still preserve the contents of the array.

WARNING

If you make an array smaller than it was originally, data in the eliminated elements is lost. The following example shows how you can increase the size of the last dimension of a dynamic array without erasing any existing data contained in the array. ```vbnet ReDim arr(10, 10, 10) ... ReDim Preserve arr(10, 10, 15) ``` -------------------------------------------------------------------------------- # Multiple Pipe Source: https://developer.rhino3d.com/en/samples/rhinoscript/multiple-pipe/ Demonstrates how to Pipe on multiple curves at a time using RhinoScript. ```vbnet ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' PipeAll.rvb -- September 2008 ' If this code works, it was written by Rajaa Issa. ' If not, I don't know who wrote it. ' Works with Rhino 4.0. Option Explicit Sub PipeOne(strRail, strRadius) Dim strCmd strCmd = "! _-Pipe _SelID " & strRail & " " & strRadius & " _Cap=_Round _Enter _Enter" Call Rhino.Command(strCmd, 0) End Sub Sub PipeAll Dim arrCurves, name, pipeRadius arrCurves = Rhino.GetObjects("Select curves to pipe", 4) pipeRadius = Rhino.GetReal("Pipe radius") 'PIPE If IsArray(arrCurves) Then For Each name In arrCurves Call PipeOne(name, pipeRadius) Next End If End Sub ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Rhino.AddStartupScript Rhino.LastLoadedScriptFile Rhino.AddAlias "PipeAll", "_NoEcho _-RunScript (PipeAll)" ``` -------------------------------------------------------------------------------- # Nothing vs Empty vs Null Source: https://developer.rhino3d.com/en/guides/rhinoscript/nothing-empty-null/ This guide discusses what nothing means in VBScript. ## Overview There has always been confusion about the semantics of data that are not even there. Usually they've written code something like: ```vbnet If varValue = Nothing Then ``` or ```vbnet If varValue = Empty Then ``` or ```vbnet If varValue = Null Then ``` Why does VBScript have `Null`, `Nothing` and `Empty`, and what are the differences between them? ## Empty When you declare a variable in VBScript, the variable's value before the first assignment is undefined, or `Empty`. ```vbnet Dim varValue ' Empty value ``` So basically, `Empty` says "I am an uninitialized variant." If you need to detect whether a variable actually is an empty variant and not a string or a number, you can use `IsEmpty`. Alternatively, you could use `TypeName` or `VarType`, but `IsEmpty` is best. ## Nothing `Nothing` is similar to `Empty` but subtly different. If `Empty` says "I am an uninitialized variant," `Nothing` says "I am an object reference that refers to no object." Objects are assigned to variables using the `Set` statement. Since the equality operator on objects checks for equality on the default property of an object, any attempt to say: ```vbnet If varValue = Nothing Then ``` is doomed to failure; `Nothing` does not have a default property, so this will produce a run-time error. To check to see if an object reference is invalid, use: ```vbnet If varValue Is Nothing Then ``` ## Null `Null` is more obscure. The semantics of `Null` are very poorly understood, particularly amongst people who have little experience with programming. Empty says "I'm an uninitialized variant," `Nothing` says "I'm an invalid object" and `Null` says "I represent a value which is not known." Null is not *True*, not *False*, but `Null`! The correct way to check for `Null` is much as you'd do for `Empty`: use `IsNull` (or `TypeName` or `VarType`.) -------------------------------------------------------------------------------- # Object Color Source: https://developer.rhino3d.com/en/samples/rhinocommon/object-color/ Demonstrates how to set the color of user-specified objects. ```cs partial class Examples { public static Rhino.Commands.Result ObjectColor(Rhino.RhinoDoc doc) { Rhino.DocObjects.ObjRef[] objRefs; Rhino.Commands.Result cmdResult = Rhino.Input.RhinoGet.GetMultipleObjects("Select objects to change color", false, Rhino.DocObjects.ObjectType.AnyObject, out objRefs); if (cmdResult != Rhino.Commands.Result.Success) return cmdResult; System.Drawing.Color color = System.Drawing.Color.Black; bool rc = Rhino.UI.Dialogs.ShowColorDialog(ref color); if (!rc) return Rhino.Commands.Result.Cancel; for (int i = 0; i < objRefs.Length; i++) { Rhino.DocObjects.RhinoObject obj = objRefs[i].Object(); if (null == obj || obj.IsReference) continue; if (color != obj.Attributes.ObjectColor) { obj.Attributes.ObjectColor = color; obj.Attributes.ColorSource = Rhino.DocObjects.ObjectColorSource.ColorFromObject; obj.CommitChanges(); } } doc.Views.Redraw(); return Rhino.Commands.Result.Success; } } ``` ```python # No Python sample available ``` -------------------------------------------------------------------------------- # Object Display Mode Source: https://developer.rhino3d.com/en/samples/rhinocommon/object-display-mode/ Demonstrates how to set the object display mode of a user-specified mesh or surface. ```cs partial class Examples { public static Rhino.Commands.Result ObjectDisplayMode(Rhino.RhinoDoc doc) { const ObjectType filter = ObjectType.Mesh | ObjectType.Brep; ObjRef objref; Result rc = Rhino.Input.RhinoGet.GetOneObject("Select mesh or surface", true, filter, out objref); if (rc != Rhino.Commands.Result.Success) return rc; Guid viewportId = doc.Views.ActiveView.ActiveViewportID; ObjectAttributes attr = objref.Object().Attributes; if (attr.HasDisplayModeOverride(viewportId)) { RhinoApp.WriteLine("Removing display mode override from object"); attr.RemoveDisplayModeOverride(viewportId); } else { Rhino.Display.DisplayModeDescription[] modes = Rhino.Display.DisplayModeDescription.GetDisplayModes(); Rhino.Display.DisplayModeDescription mode = null; if (modes.Length == 1) mode = modes[0]; else { Rhino.Input.Custom.GetOption go = new Rhino.Input.Custom.GetOption(); go.SetCommandPrompt("Select display mode"); string[] str_modes = new string[modes.Length]; for (int i = 0; i < modes.Length; i++) str_modes[i] = modes[i].EnglishName.Replace(" ", "").Replace("-", ""); go.AddOptionList("DisplayMode", str_modes, 0); if (go.Get() == Rhino.Input.GetResult.Option) mode = modes[go.Option().CurrentListOptionIndex]; } if (mode == null) return Rhino.Commands.Result.Cancel; attr.SetDisplayModeOverride(mode, viewportId); } doc.Objects.ModifyAttributes(objref, attr, false); doc.Views.Redraw(); return Rhino.Commands.Result.Success; } } ``` ```python import Rhino import scriptcontext def ObjectDisplayMode(): filter = Rhino.DocObjects.ObjectType.Brep | Rhino.DocObjects.ObjectType.Mesh rc, objref = Rhino.Input.RhinoGet.GetOneObject("Select mesh or surface", True, filter) if rc != Rhino.Commands.Result.Success: return rc; viewportId = scriptcontext.doc.Views.ActiveView.ActiveViewportID attr = objref.Object().Attributes if attr.HasDisplayModeOverride(viewportId): print("Removing display mode override from object") attr.RemoveDisplayModeOverride(viewportId) else: modes = Rhino.Display.DisplayModeDescription.GetDisplayModes() mode = None if len(modes) == 1: mode = modes[0] else: go = Rhino.Input.Custom.GetOption() go.SetCommandPrompt("Select display mode") str_modes = [] for m in modes: s = m.EnglishName.replace(" ","").replace("-","") str_modes.append(s) go.AddOptionList("DisplayMode", str_modes, 0) if go.Get()==Rhino.Input.GetResult.Option: mode = modes[go.Option().CurrentListOptionIndex] if not mode: return Rhino.Commands.Result.Cancel attr.SetDisplayModeOverride(mode, viewportId) scriptcontext.doc.Objects.ModifyAttributes(objref, attr, False) scriptcontext.doc.Views.Redraw() if __name__=="__main__": ObjectDisplayMode() ``` -------------------------------------------------------------------------------- # Object Iterator Source: https://developer.rhino3d.com/en/samples/rhinocommon/object-iterator/ Demonstrates how to iterate (or enumerate) through Rhino's geometry table. ```cs partial class Examples { public static Result ObjectIterator(RhinoDoc doc) { var object_enumerator_settings = new ObjectEnumeratorSettings(); object_enumerator_settings.IncludeLights = true; object_enumerator_settings.IncludeGrips = false; var rhino_objects = doc.Objects.GetObjectList(object_enumerator_settings); int count = 0; foreach (var rhino_object in rhino_objects) { if (rhino_object.IsSelectable() && rhino_object.IsSelected(false) == 0) { rhino_object.Select(true); count++; } } if (count > 0) { doc.Views.Redraw(); RhinoApp.WriteLine("{0} object{1} selected", count, count == 1 ? "" : "s"); } return Result.Success; } } ``` ```python from Rhino import * from Rhino.DocObjects import * from Rhino.Commands import * from scriptcontext import doc def RunCommand(): object_enumerator_settings = ObjectEnumeratorSettings() object_enumerator_settings.IncludeLights = True object_enumerator_settings.IncludeGrips = False rhino_objects = doc.Objects.GetObjectList(object_enumerator_settings) count = 0 for rhino_object in rhino_objects: if rhino_object.IsSelectable() and rhino_object.IsSelected(False) == 0: rhino_object.Select(True) count += 1; if count > 0: doc.Views.Redraw() RhinoApp.WriteLine("{0} object{1} selected", count, "" if count == 1 else "s") return Result.Success if __name__ == "__main__": RunCommand() ``` -------------------------------------------------------------------------------- # Object Properties Page Icons Source: https://developer.rhino3d.com/en/guides/cpp/object-properties-page-icons/ This brief guide discusses how to provide an icon for a custom object properties page using C/C++. ## Problem In Rhino, the object properties dialog shows a list of icons that allows you to select between the available properties pages. You would like to add a custom icon to the object properties dialog when your plugin adds a custom page. ## Solution Derive your custom object properties page from `CRhinoObjectPropertiesDialogPageEx`, which has a virtual `Icon()` member that you must override and implement. You will want to implement this virtual function as follows: ```cpp HICON CTestObjectPropertiesPageExDlg::Icon() const { AFX_MANAGE_STATE( AfxGetStaticModuleState() ); return (HICON)::LoadImage(AfxGetInstanceHandle(), MAKEINTRESOURCE(IDI_OBJPROPPAGE_DIALOG), IMAGE_ICON, 24, 24, LR_SHARED); } ``` **NOTE**: Make carefully the `AFX_MANAGE_STATE` macro. See [MFC Technical Notes 33](https://msdn.microsoft.com/en-us/library/hw85e4bb.aspx) and [58](https://msdn.microsoft.com/en-us/library/ft1t4bbc.aspx) for additional details. ## Related Topics - [TN033: DLL Version of MFC (on MSDN)](https://msdn.microsoft.com/en-us/library/hw85e4bb.aspx) - [TN058: MFC Module State Implementation (on MSDN)](https://msdn.microsoft.com/en-us/library/ft1t4bbc.aspx) -------------------------------------------------------------------------------- # Object Selection with Options Source: https://developer.rhino3d.com/en/guides/rhinocommon/object-selection-options/ This guide covers how to pick some objects, select command options, return to picking more objects, all while keeping your current selection set. ## GetObject RhinoCommon's [GetObject class](/api/RhinoCommon/html/T_Rhino_Input_Custom_GetObject.htm) has a few properties and methods that you need to use, including: - [GetObject.EnableClearObjectsOnEntry](/api/RhinoCommon/html/M_Rhino_Input_Custom_GetObject_EnableClearObjectsOnEntry.htm) - [GetObject.EnableUnselectObjectsOnExit](/api/RhinoCommon/html/M_Rhino_Input_Custom_GetObject_EnableUnselectObjectsOnExit.htm) - [GetObject.DeselectAllBeforePostSelect](/api/RhinoCommon/html/P_Rhino_Input_Custom_GetObject_DeselectAllBeforePostSelect.htm) Also, after clicking a command line option, turn off pre-selection, using `GetObject.EnablePreSelect`. Otherwise, `GetObject.GetMultiple` will return with a `GetResult.Object` return code. For example: ```cs using System; using Rhino; using Rhino.Commands; using Rhino.DocObjects; using Rhino.Input; using Rhino.Input.Custom; ... protected override Result RunCommand(RhinoDoc doc, RunMode mode) { const ObjectType geometryFilter = ObjectType.Surface | ObjectType.PolysrfFilter | ObjectType.Mesh; int integer1 = 300; int integer2 = 300; OptionInteger optionInteger1 = new OptionInteger(integer1, 200, 900); OptionInteger optionInteger2 = new OptionInteger(integer2, 200, 900); GetObject go = new GetObject(); go.SetCommandPrompt("Select surfaces, polysurfaces, or meshes"); go.GeometryFilter = geometryFilter; go.AddOptionInteger("Option1", ref optionInteger1); go.AddOptionInteger("Option2", ref optionInteger2); go.GroupSelect = true; go.SubObjectSelect = false; go.EnableClearObjectsOnEntry(false); go.EnableUnselectObjectsOnExit(false); go.DeselectAllBeforePostSelect = false; bool bHavePreselectedObjects = false; for (;;) { GetResult res = go.GetMultiple(1, 0); if (res == GetResult.Option) { go.EnablePreSelect(false, true); continue; } else if (res != GetResult.Object) return Result.Cancel; if (go.ObjectsWerePreselected) { bHavePreselectedObjects = true; go.EnablePreSelect(false, true); continue; } break; } if (bHavePreselectedObjects) { // Normally, pre-selected objects will remain selected, when a // command finishes, and post-selected objects will be unselected. // This this way of picking, it is possible to have a combination // of pre-selected and post-selected. So, to make sure everything // "looks the same", lets unselect everything before finishing // the command. for (int i = 0; i < go.ObjectCount; i++) { RhinoObject rhinoObject = go.Object(i).Object(); if (null != rhinoObject) rhinoObject.Select(false); } doc.Views.Redraw(); } int objectCount = go.ObjectCount; integer1 = optionInteger1.CurrentValue; integer2 = optionInteger2.CurrentValue; RhinoApp.WriteLine(string.Format("Select object count = {0}", objectCount)); RhinoApp.WriteLine(string.Format("Value of integer1 = {0}", integer1)); RhinoApp.WriteLine(string.Format("Value of integer2 = {0}", integer2)); return Result.Success; } ``` ```vbnet Imports Rhino Imports Rhino.Commands Imports Rhino.DocObjects Imports Rhino.Input Imports Rhino.Input.Custom ... Protected Overrides Function RunCommand(doc As RhinoDoc, mode As RunMode) As Result Const geometryFilter As ObjectType = ObjectType.Surface Or ObjectType.PolysrfFilter Or ObjectType.Mesh Dim integer1 As Integer = 300 Dim integer2 As Integer = 300 Dim optionInteger1 As New OptionInteger(integer1, 200, 900) Dim optionInteger2 As New OptionInteger(integer2, 200, 900) Dim go As New GetObject() go.SetCommandPrompt("Select surfaces, polysurfaces, or meshes") go.GeometryFilter = geometryFilter go.AddOptionInteger("Option1", optionInteger1) go.AddOptionInteger("Option2", optionInteger2) go.GroupSelect = True go.SubObjectSelect = False go.EnableClearObjectsOnEntry(False) go.EnableUnselectObjectsOnExit(False) go.DeselectAllBeforePostSelect = False Dim bHavePreselectedObjects As Boolean = False While True Dim res As GetResult = go.GetMultiple(1, 0) If res = GetResult.[Option] Then go.EnablePreSelect(False, True) Continue While ElseIf res <> GetResult.[Object] Then Return Result.Cancel End If If go.ObjectsWerePreselected Then bHavePreselectedObjects = True go.EnablePreSelect(False, True) Continue While End If Exit While End While If bHavePreselectedObjects Then ' Normally, pre-selected objects will remain selected, when a ' command finishes, and post-selected objects will be unselected. ' This this way of picking, it is possible to have a combination ' of pre-selected and post-selected. So, to make sure everything ' "looks the same", lets unselect everything before finishing ' the command. For i As Integer = 0 To go.ObjectCount - 1 Dim rhinoObject As RhinoObject = go.[Object](i).[Object]() If rhinoObject IsNot Nothing Then rhinoObject.[Select](False) End If Next doc.Views.Redraw() End If Dim objectCount As Integer = go.ObjectCount integer1 = optionInteger1.CurrentValue integer2 = optionInteger2.CurrentValue RhinoApp.WriteLine(String.Format("Select object count = {0}", objectCount)) RhinoApp.WriteLine(String.Format("Value of integer1 = {0}", integer1)) RhinoApp.WriteLine(String.Format("Value of integer2 = {0}", integer2)) Return Result.Success End Function ``` -------------------------------------------------------------------------------- # Offset Curve Source: https://developer.rhino3d.com/en/samples/rhinocommon/offset-curve/ Demonstrates how to offset curves to one side or another by a distance. ```cs partial class Examples { public static Result OffsetCurve(RhinoDoc doc) { ObjRef obj_ref; var rs = RhinoGet.GetOneObject( "Select Curve", false, ObjectType.Curve, out obj_ref); if (rs != Result.Success) return rs; var curve = obj_ref.Curve(); if (curve == null) return Result.Nothing; Point3d point; rs = RhinoGet.GetPoint("Select Side", false, out point); if (rs != Result.Success) return rs; if (point == Point3d.Unset) return Result.Nothing; var curves = curve.Offset(point, Vector3d.ZAxis, 1.0, doc.ModelAbsoluteTolerance, CurveOffsetCornerStyle.None); foreach (var offset-curve in curves) doc.Objects.AddCurve(offset-curve); doc.Views.Redraw(); return Result.Success; } } ``` ```python from Rhino.DocObjects import ObjectType from Rhino.Geometry import Point3d, Vector3d, CurveOffsetCornerStyle from Rhino.Input import RhinoGet from Rhino.Commands import Result from scriptcontext import doc import rhinoscriptsyntax as rs import System def RunCommand(): rc, obj_ref = RhinoGet.GetOneObject("Select Curve", False, ObjectType.Curve) if rc != Result.Success: return rc curve = obj_ref.Curve() if curve == None: return Result.Nothing rc, point = RhinoGet.GetPoint("Select Side", False) if rc != Result.Success: return rc if point == Point3d.Unset: return Result.Nothing curves = curve.Offset(point, Vector3d.ZAxis, 1.0, doc.ModelAbsoluteTolerance, System.Enum.Parse(CurveOffsetCornerStyle, "None")) for offset_curve in curves: doc.Objects.AddCurve(offset_curve) doc.Views.Redraw() return Result.Success if __name__ == "__main__": RunCommand() ``` -------------------------------------------------------------------------------- # Offset Curve Source: https://developer.rhino3d.com/en/samples/rhinoscript/offset-curve/ Demonstrates how to offset a curve inside or outside using RhinoScript. ```vbnet ' Description: ' Offsets a closed planar curve inside or outside. ' Parameters: ' strSurface - String, The identifier of the closed planar curve to offset. ' dblDistance - Number, The distance to offset. ' blnOutside - Boolean, Offset outside (true) or inside (false). ' Returns: ' Array, An array containing the identifiers of the new objects if successful. ' Null on error Option Explicit Function RhinoOffsetClosedPlanarCurve(ByVal strCurve, ByVal dblDistance, ByVal blnOutside) ' Local variables Dim arrPlane, arrOldPlane, strView, arrBox, arrPoint ' Default return value RhinoOffsetClosedPlanarCurve = Null ' Quick parameter validation If (VarType(strCurve) <> vbString) Then Exit Function If Not IsNumeric(dblDistance) Then Exit Function If (VarType(blnOutside) <> vbBoolean) Then Exit Function ' Curve validation If (Rhino.IsCurve(strCurve) = False) Then Exit Function If (Rhino.IsCurveClosed(strCurve) = False) Then Exit Function arrPlane = Rhino.CurvePlane(strCurve) If Not IsArray(arrPlane) Then Exit Function ' Calculate plane-based bounding box strView = Rhino.CurrentView() arrOldPlane = Rhino.ViewCPlane(strView, arrPlane) arrBox = Rhino.BoundingBox(strCurve, strView) Call Rhino.ViewCPlane(strView, arrOldPlane) ' Offset point so its outside of bounding box arrPoint = Rhino.PointAdd(arrBox(0), Rhino.VectorCreate(arrBox(0), arrBox(2))) ' Offset the curve If (blnOutside = False) Then dblDistance = -dblDistance RhinoOffsetClosedPlanarCurve = Rhino.OffsetCurve(strCurve, arrPoint, dblDistance, arrPlane(3)) End Function ``` -------------------------------------------------------------------------------- # Offsetting Curves on Surfaces Source: https://developer.rhino3d.com/en/guides/cpp/offsetting-curves-on-surfaces/ This guide demonstrates how to offset a curve on a surface using C/C++. ## Problem You are using the `RhinoOffsetCurveOnSrf` function to offset a curve which was interpolated on a cylindrical surface. The problem is that the results do not seem to match those of Rhino's `OffsetCrvOnSrf` command. That is, the offset curve does not extend to the edges of the surfaces. Why is this? ## Solution After calculating the offset curve, the `OffsetCrvOnSrf` command extends both ends of that curve to the surface edge using `RhinoExtendCrvOnSrf`. Below is an sample of how you can do this using the SDK... ## Sample ```cpp CRhinoCommand::result CCommandTest::RunCommand( const CRhinoCommandContext& context ) { // Select curve on surface CRhinoGetObject gc; gc.SetCommandPrompt( L"Select curve on surface" ); gc.SetGeometryFilter( CRhinoGetObject::curve_object ); gc.GetObjects( 1, 1 ); if( gc.CommandResult() != CRhinoCommand::success ) return gc.CommandResult(); // Validate curve const ON_Curve* crv = gc.Object(0).Curve(); if( 0 == crv ) return CRhinoCommand::failure; // Select base surface CRhinoGetObject gs; gs.SetCommandPrompt( L"Select base surface" ); gs.SetGeometryFilter( CRhinoGetObject::surface_object ); gs.EnablePreSelect( false ); gs.EnableDeselectAllBeforePostSelect( false ); gs.GetObjects( 1, 1 ); if( gs.CommandResult() != CRhinoCommand::success ) return gs.CommandResult(); // Validate face const ON_BrepFace* face = gs.Object(0).Face(); if( 0 == face ) return CRhinoCommand::failure; // Validate brep const ON_Brep* brep = face->Brep(); if( 0 == brep ) return CRhinoCommand::failure; // Specify offset distance CRhinoGetNumber gd; gd.SetCommandPrompt( L"Offset distance" ); gd.SetDefaultNumber( 1.0 ); gd.GetNumber(); if( gd.CommandResult() != CRhinoCommand::success ) return gd.CommandResult(); double dist = gd.Number(); // Do offset curve on surface double tol = context.m_doc.AbsoluteTolerance(); ON_SimpleArray offset_curves; CRhinoCommand::result cmdrc = RhinoOffsetCurveOnSrf( crv, brep, face->m_face_index, dist, tol, offset_curves ); if( cmdrc == CRhinoCommand::success ) { int i = 0; ON_SimpleArray curves_to_join; curves_to_join.Append( offset_curves.Count(), offset_curves.Array() ); // Try joining the offset curves ON_SimpleArray joined_curves; BOOL rc = RhinoMergeCurves( curves_to_join, joined_curves, 2.0 * tol, TRUE ); for( i = 0; i < curves_to_join.Count(); i++ ) curves_to_join[i] = 0; if( rc ) { for( i = 0; i < joined_curves.Count(); i++ ) { ON_Curve* pC = joined_curves[i]; if( pC ) { // Extend both ends to edge of the surface if( !pC->IsClosed() ) RhinoExtendCrvOnSrf( *face, pC ); // Add to document CRhinoCurveObject* crv_obj = new CRhinoCurveObject(); crv_obj->SetCurve( pC ); context.m_doc.AddObject( crv_obj ); joined_curves[i] = 0; } } // Do not leak memory for( i = 0; i < offset_curves.Count(); i++ ) { if( offset_curves[i] ) { delete offset_curves[i]; offset_curves[i] = 0; } } } else { for( i = 0; i < offset_curves.Count(); i++) { ON_Curve* pC = offset_curves[i]; if( pC ) { // Extend both ends to edge of the surface if( !pC->IsClosed() ) RhinoExtendCrvOnSrf( *face, pC ); // Add to document CRhinoCurveObject* crv_obj = new CRhinoCurveObject(); crv_obj->SetCurve( pC ); context.m_doc.AddObject( crv_obj ); offset_curves[i] = 0; } } } } context.m_doc.Redraw(); return CRhinoCommand::success; } ``` -------------------------------------------------------------------------------- # Offsetting Meshes Source: https://developer.rhino3d.com/en/guides/rhinoscript/offsetting-meshes/ This guide demonstrates how to offset and solidify a mesh using RhinoScript. ## Problem RhinoScript can generate a closed mesh as a result of a base mesh and an offset. Rhino's *OffsetMesh* command does this task very well if you use the solidify option, but RhinoScript's `MeshOffset` function does not have this option. Let's take a look at creating a solid mesh with RhinoScript... ## Solution RhinoScript's `MeshOffset` function does not have a solidify option, but you can accomplish what you want by simply scripting Rhino's *OffsetMesh* command, instead of calling RhinoScript's `MeshOffset` function. The following example function will offset a mesh by scripting Rhino's OffsetMesh command... ```vbnet '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' Offset and solidify a mesh object '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Function OffsetMeshSolidify(strMesh, dblDistance) ' Declare local variables Dim arrSaved, strCommand ' Save any selected objects arrSaved = Rhino.SelectedObjects ' Unselect all objects Rhino.UnSelectAllObjects ' Select the mesh Call Rhino.SelectObject(strMesh) ' Script the OffsetMesh command strCommand = "_-OffsetMesh _CapMeshes=_Yes " & CStr(dblDistance) & " _Enter" Call Rhino.Command(strCommand, 0) ' Get the results of the command If Rhino.LastCommandResult = 0 Then OffsetMeshSolidify = Rhino.LastCreatedObjects()(0) Else OffsetMeshSolidify = Null End If ' Unselect all objects Rhino.UnSelectAllObjects ' If any objects were selected before calling ' this function, re-select them If IsArray(arrSaved) Then Rhino.SelectObjects(arrSaved) End Function ``` You can test the above function with the following code... ```vbnet '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' This procedure tests the OffsetMeshSolidify function '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Sub TestOffsetMeshSolidify() ' Declare local constants and variables Const rhMesh = 32 Dim strMesh, dblDistance, strOffset ' Select a mesh to offset strMesh = Rhino.GetObject("Select mesh to offset", rhMesh, True) If IsNull(strMesh) Then Exit Sub ' Get the offset distance dblDistance = Rhino.GetReal("Offset distance", 1.0) If IsNull(dblDistance) Then Exit Sub If (dblDistance = 0.0) Then Exit Sub ' Call our mesh offsetting function... strOffset = OffsetMeshSolidify(strMesh, dblDistance) End Sub ``` -------------------------------------------------------------------------------- # ON_SimpleArray Utilities Source: https://developer.rhino3d.com/en/samples/cpp/on-simplearray-utils/ Demonstrates how to sort and cull simple arrays. ```cpp void SortDoubles( ON_SimpleArray& arr, bool bIncreasing = true ) { if( arr.Count() > 1 ) { if( bIncreasing ) arr.QuickSort( &ON_CompareIncreasing ); else arr.QuickSort( &ON_CompareDecreasing ); } } void CullDoubles( ON_SimpleArray& arr, double tolerance = ON_ZERO_TOLERANCE ) { const int count = arr.Count(); if( count > 1 ) { arr.QuickSort( &ON_CompareIncreasing ); if( tolerance < ON_ZERO_TOLERANCE ) tolerance = ON_ZERO_TOLERANCE; double d = *arr.Last(); int i; for( i = count - 2; i >= 0; i-- ) { if( fabs(d - arr[i]) <= tolerance ) arr.Remove(i); else d = arr[i]; } arr.Shrink(); } } ``` -------------------------------------------------------------------------------- # Open a 3DM file Source: https://developer.rhino3d.com/en/guides/cpp/opening-a-3dm-file/ This brief guide demonstrates how to open a Rhino 3DM file from a plugin command using C/C++. ## Problem You would like to open a 3DM file, or an STL file, or any other file type that Rhino supports, from your C/C++ plugin. ## Solution As each type of file, support by Rhino for opening or importing, has a different set of options, it not possible to write a single, generic file open function and hope to support all formats. Thus, if you want to open or import a file from a plugin command, then simply script either Rhino's *Open* or *Import* command using `CRhinoApp::RunScript()`. ## Sample The following example command demonstrates how to open a Rhino 3DM file from a plugin command. You can use this same technique to open other support file types, such as STL, IGES, DWG, and others. ```cpp CRhinoCommand::result CCommandTest::RunCommand( const CRhinoCommandContext& context ) { // Prompt the user for the name of a 3dm file to open ON_wString filename; CWnd* pParentWnd = CWnd::FromHandle( RhinoApp().MainWnd() ); CRhinoGetFileDialog gf; gf.SetScriptMode( context.IsInteractive() ? FALSE : TRUE ); BOOL rc = gf.DisplayFileDialog( CRhinoGetFileDialog::open_rhino_only_dialog, filename, pParentWnd ); if( !rc ) return CRhinoCommand::cancel; // Verify the file name string filename = gf.FileName(); filename.TrimLeftAndRight(); if( filename.IsEmpty() ) return CRhinoCommand::nothing; // Verify the file if( !CRhinoFileUtilities::FileExists(filename) ) { RhinoApp().Print( L"File not found.\n" ); return CRhinoCommand::failure; } // Script Rhino's open command. Note, in case the file name // string contains spaces, we will want to surround the string // with double-quote characters so the command line parser // will deal with the string property. ON_wString script; script.Format( L"_-Open \"%s\"", filename ); RhinoApp().RunScript( script, 0 ); return CRhinoCommand::success; } ``` -------------------------------------------------------------------------------- # Opening 3DM Files Source: https://developer.rhino3d.com/en/samples/rhinoscript/opening-3dm-files/ Demonstrates how to open 3DM files using RhinoScript. ```vbnet Sub TestFileOpen() ' Local variable declarations Dim strFile ' Let the user pick a 3dm file. If the return value is null, ' then the user picked the "Cancel" button... strFile = Rhino.OpenFileName("Open", "Rhino 3D Models (*.3dm)|*.3dm|") If IsNull(strFile) Then Exit Sub ' To keep Rhino from displaying the dreaded ' "Save changes to " dialog, we can fool it ' into thinking that the document was never modified ' by doing the following. Call Rhino.DocumentModified(False) ' If the picked a file that has a space character in its name, ' or resides in a folder that has a space character, then we ' need to surround the file string in double-quotes so Rhino's ' command line parser will interpret the string correctly. strFile = Chr(34) & strFile & Chr(34) ' Now we can simply script Rhino's Open command to open the file. Call Rhino.Command("_-Open " & strFile, 0) End Sub ``` -------------------------------------------------------------------------------- # Optional Arguments Source: https://developer.rhino3d.com/en/guides/rhinoscript/optional-arguments/ This guide demonstrates how to implement optional arguments in VBScript. ## Overview It is not uncommon to want to make an argument of a VBScript function or subroutine optional. In VBScript, the `Optional` keyword, which allows some arguments to be left out in Visual Basic, is not implemented. This means that you must declare every argument that you want to use. What you can do is pass null values to your functions. For example: ```vbnet Function SomeArgs(one, two, three, four) SomeArgs = one & two & three & four End Function Call SomeArgs(1, 2, 3, Null) ``` ## Discussion To work around this limitation, you can use an array-based approach to simulate optional arguments. To see how to use the array-based approach for creating subroutines with optional arguments, consider the following example: ```vbnet Sub MySubroutine(arrArgs) ' Declare local variables Dim v1, v2, v3, v4 ' Initialize the local variables with default values v1 = 1 : v2 = 2 : v3 = 3 : v4 = 4 Select Case UBound(arrArgs) Case 0 v1 = arrArgs(0) Case 1 v1 = arrArgs(0) v2 = arrArgs(1) Case 2 v1 = arrArgs(0) v2 = arrArgs(1) v3 = arrArgs(2) Case 3 v1 = arrArgs(0) v2 = arrArgs(1) v3 = arrArgs(2) v4 = arrArgs(3) Case Else Exit Sub End Select Rhino.Print "v1 = " & CStr(v1) Rhino.Print "v2 = " & CStr(v2) Rhino.Print "v3 = " & CStr(v3) Rhino.Print "v4 = " & CStr(v4) End Sub ``` Notice in the subroutine declaration, only defined one argument has been defined: ```vbnet Sub MySubroutine(arrArgs) ``` The argument will be an array of values we would like to pass into the subroutine. The next few lines declare local variables and initializes them to default values (simple numbers, in this case). Next, use the `UBound()` function to determine the number of arguments passed. Then assign the array elements to the local variables: ```vbnet Select Case UBound(arrArgs) Case 0 v1 = arrArgs(0) Case 1 v1 = arrArgs(0) v2 = arrArgs(1) Case 2 v1 = arrArgs(0) v2 = arrArgs(1) v3 = arrArgs(2) Case 3 v1 = arrArgs(0) v2 = arrArgs(1) v3 = arrArgs(2) v4 = arrArgs(3) Case Else Exit Sub End Select ``` Since the array is zero-based, the first case branch assigns the first element to our v1 variable. As more arguments to the function are needed, one can easily add more case branches. To call this subroutine, create an array of the size based on the number of arguments you want to pass into the function, and then populate this array with the values you want to pass to the function: ```vbnet ' Create the array to pass to MySubroutine Dim arrArgs ' Call the subroutine with two arguments Redim arrArgs(1) arrArgs(0) = Value1 arrArgs(1) = Value2 Call MySubroutine(arrArgs) ``` Alternatively... ```vbnet ' Call MySubroutine with three arguments Call MySubroutine(Array(Value1, Value2, Value3)) ``` -------------------------------------------------------------------------------- # Options Pages Best Practices Source: https://developer.rhino3d.com/en/guides/rhinocommon/options_pages_best_practices/ Discusses the best practices for writing options or document properties pages. ## Overview One of the things your plug-in may want to do is add options or document property settings pages to Rhino. You can add pages by overriding the [PlugIn.OptionsDialogPages](https://developer.rhino3d.com/api/RhinoCommon/html/M_Rhino_PlugIns_PlugIn_OptionsDialogPages.htm) and/or [PlugIn.DocumentPropertiesDialogPages](https://developer.rhino3d.com/api/RhinoCommon/html/M_Rhino_PlugIns_PlugIn_DocumentPropertiesDialogPages.htm) methods and adding a custom [OptionsDialogPage](https://developer.rhino3d.com/api/RhinoCommon/html/T_Rhino_UI_OptionsDialogPage.htm) to the provided page list. You can create options pages in a plug-in using WinForms(Windows), WPF (Windows) or Eto (Windows and Mac). You can find samples that demonstrate how to create options pages in the [Developer Samples repository](https://github.com/mcneel/rhino-developer-samples) on GitHub. ## Details When writing options pages, it is important to remember that the page should initialize its content when activated, apply its changes when notified and optionally provide cancel support. #### Content Initialization The [OnActivate](https://developer.rhino3d.com/api/RhinoCommon/html/M_Rhino_UI_StackedDialogPage_OnActivate.htm) method is called with ```active == true``` when becoming the current, visible page which is the appropriate time to update the page contents. #### Applying Changes It is up to the page author to decide when to apply user changes to the document or runtime environment. If you wish to see the document or Rhino update while making changes, you may apply the changes in real time. It is important to save the original settings prior to making changes if your options page is going to support the cancel mechanism. Typically changes should be queued up and made in the [OnApply](https://developer.rhino3d.com/api/RhinoCommon/html/M_Rhino_UI_StackedDialogPage_OnApply.htm) override, canceling should be performed in the [OnCancel](https://developer.rhino3d.com/api/RhinoCommon/html/M_Rhino_UI_StackedDialogPage_OnCancel.htm) override. #### Rhino for Windows Behavior Rhino for Windows contains a single Options/Document Properties dialog box which can be displayed using the Options or DocumentProperties Rhino commands. This dialog box is modal in nature and will call the [PlugIn.OptionsDialogPages](https://developer.rhino3d.com/api/RhinoCommon/html/M_Rhino_PlugIns_PlugIn_OptionsDialogPages.htm) and [PlugIn.DocumentPropertiesDialogPages](https://developer.rhino3d.com/api/RhinoCommon/html/M_Rhino_PlugIns_PlugIn_DocumentPropertiesDialogPages.htm) methods each time it is displayed. It creates the [PageControl](https://developer.rhino3d.com/api/RhinoCommon/html/P_Rhino_UI_StackedDialogPage_PageControl.htm) the first time the page is made current. If your page is never made current, the page control is never created. Rhino will call [OnActivate](https://developer.rhino3d.com/api/RhinoCommon/html/M_Rhino_UI_StackedDialogPage_OnActivate.htm) with ```active == true``` when a page becomes the active page. When a page is no longer active, the [OnActivate](https://developer.rhino3d.com/api/RhinoCommon/html/M_Rhino_UI_StackedDialogPage_OnActivate.htm) method is called with ```active == false``` followed by a call to [OnApply](https://developer.rhino3d.com/api/RhinoCommon/html/M_Rhino_UI_StackedDialogPage_OnApply.htm). If the Rhino Options/Document Properties dialog is closed by cancelling, then [OnCancel](https://developer.rhino3d.com/api/RhinoCommon/html/M_Rhino_UI_StackedDialogPage_OnCancel.htm) is called for any page displayed while the modal dialog was visible. #### Rhino for Mac Behavior Rhino for Mac contains File/Settings (Document Properties) and Rhinoceros/Preferences (Options) modeless windows. The main things to consider here are the host windows are modeless and cancel is never called. Since the host winows are independent you will get slightly different behavior for Preferences (Options) panels than you will with File/Settings (Document Properties) panels. ##### Preferences Window (Options) The Preferences window will call [PlugIn.OptionsDialogPages](https://developer.rhino3d.com/api/RhinoCommon/html/M_Rhino_PlugIns_PlugIn_OptionsDialogPages.htm) the first time the window is displayed and will never call it again. The Preferences window is never really closed; it is only hidden or shown. The following is a description of the methods called and when they are called: * [PageControl](https://developer.rhino3d.com/api/RhinoCommon/html/P_Rhino_UI_StackedDialogPage_PageControl.htm) is referenced the first time the page is displayed and should return the one and only options page control. * [OnActivate](https://developer.rhino3d.com/api/RhinoCommon/html/M_Rhino_UI_StackedDialogPage_OnActivate.htm) with ```active == true``` is called when: * A page becomes the active page. * The Options window becomes the active window and a page is active. * [OnActivate](https://developer.rhino3d.com/api/RhinoCommon/html/M_Rhino_UI_StackedDialogPage_OnActivate.htm) with ```active == false``` is called when: * The active page changes and a page is no longer the active page. * The Options window is deactivated or hidden and a page is the active page. * [OnApply](https://developer.rhino3d.com/api/RhinoCommon/html/M_Rhino_UI_StackedDialogPage_OnApply.htm) is called on the active page when: * The active page changes and a page is no longer the active page. * The Options window is deactivated or hidden and a page is the active page. ##### File Settings Window (Document Properties) The File Settings window will call [PlugIn.DocumentPropertiesDialogPages](https://developer.rhino3d.com/api/RhinoCommon/html/M_Rhino_PlugIns_PlugIn_DocumentPropertiesDialogPages.htm) the first time the window is displayed and each time a new document is created. The Settings window keeps a list of pages associated with each open document and displays the active page assocated with the current document. The Settings window is never really closed; it is only hidden or shown. The following is a description of the methods called and when they are called: - [PageControl](https://developer.rhino3d.com/api/RhinoCommon/html/P_Rhino_UI_StackedDialogPage_PageControl.htm) is referenced the first time the page is displayed for a given document instance and should return the settings page control. It will also get called when a new document is opened or created and a page is the active page. - [OnActivate](https://developer.rhino3d.com/api/RhinoCommon/html/M_Rhino_UI_StackedDialogPage_OnActivate.htm) with ```active == true``` is called when: - A page becomes the active page. - The Settings window becomes the active window and a page is active. - [OnActivate](https://developer.rhino3d.com/api/RhinoCommon/html/M_Rhino_UI_StackedDialogPage_OnActivate.htm) with ```active == false``` is called when: - The active page changes and a page is no longer the active page. - The Settings window is deactivated or hidden and a page is the active page. - [OnApply](https://developer.rhino3d.com/api/RhinoCommon/html/M_Rhino_UI_StackedDialogPage_OnApply.htm) is called on the active page when: - The active page changes and a page is no longer the active page. - The Settings window is deactivated or hidden and a page is the active page. -------------------------------------------------------------------------------- # Orient On Surface Source: https://developer.rhino3d.com/en/samples/rhinocommon/orient-on-surface/ Demonstrates how to orient objects on a surface. ```cs partial class Examples { public static Rhino.Commands.Result OrientOnSrf(Rhino.RhinoDoc doc) { // Select objects to orient Rhino.Input.Custom.GetObject go = new Rhino.Input.Custom.GetObject(); go.SetCommandPrompt("Select objects to orient"); go.SubObjectSelect = false; go.GroupSelect = true; go.GetMultiple(1, 0); if (go.CommandResult() != Rhino.Commands.Result.Success) return go.CommandResult(); // Point to orient from Rhino.Input.Custom.GetPoint gp = new Rhino.Input.Custom.GetPoint(); gp.SetCommandPrompt("Point to orient from"); gp.Get(); if (gp.CommandResult() != Rhino.Commands.Result.Success) return gp.CommandResult(); // Define source plane Rhino.Display.RhinoView view = gp.View(); if (view == null) { view = doc.Views.ActiveView; if (view == null) return Rhino.Commands.Result.Failure; } Rhino.Geometry.Plane source_plane = view.ActiveViewport.ConstructionPlane(); source_plane.Origin = gp.Point(); // Surface to orient on Rhino.Input.Custom.GetObject gs = new Rhino.Input.Custom.GetObject(); gs.SetCommandPrompt("Surface to orient on"); gs.GeometryFilter = Rhino.DocObjects.ObjectType.Surface; gs.SubObjectSelect = true; gs.DeselectAllBeforePostSelect = false; gs.OneByOnePostSelect = true; gs.Get(); if (gs.CommandResult() != Rhino.Commands.Result.Success) return gs.CommandResult(); Rhino.DocObjects.ObjRef objref = gs.Object(0); // get selected surface object Rhino.DocObjects.RhinoObject obj = objref.Object(); if (obj == null) return Rhino.Commands.Result.Failure; // get selected surface (face) Rhino.Geometry.Surface surface = objref.Surface(); if (surface == null) return Rhino.Commands.Result.Failure; // Unselect surface obj.Select(false); // Point on surface to orient to gp.SetCommandPrompt("Point on surface to orient to"); gp.Constrain(surface, false); gp.Get(); if (gp.CommandResult() != Rhino.Commands.Result.Success) return gp.CommandResult(); // Do transformation Rhino.Commands.Result rc = Rhino.Commands.Result.Failure; double u, v; if (surface.ClosestPoint(gp.Point(), out u, out v)) { Rhino.Geometry.Plane target_plane; if (surface.FrameAt(u, v, out target_plane)) { // Build transformation Rhino.Geometry.Transform xform = Rhino.Geometry.Transform.PlaneToPlane(source_plane, target_plane); // Do the transformation. In this example, we will copy the original objects const bool delete_original = false; for (int i = 0; i < go.ObjectCount; i++) doc.Objects.Transform(go.Object(i), xform, delete_original); doc.Views.Redraw(); rc = Rhino.Commands.Result.Success; } } return rc; } } ``` ```python import Rhino import scriptcontext import System.Guid def OrientOnSrf(): # Select objects to orient go = Rhino.Input.Custom.GetObject() go.SetCommandPrompt("Select objects to orient") go.SubObjectSelect = False go.GroupSelect = True go.GetMultiple(1, 0) if go.CommandResult()!=Rhino.Commands.Result.Success: return go.CommandResult() # Point to orient from gp = Rhino.Input.Custom.GetPoint() gp.SetCommandPrompt("Point to orient from") gp.Get() if gp.CommandResult()!=Rhino.Commands.Result.Success: return gp.CommandResult() # Define source plane view = gp.View() if not view: view = doc.Views.ActiveView if not view: return Rhino.Commands.Result.Failure source_plane = view.ActiveViewport.ConstructionPlane() source_plane.Origin = gp.Point() # Surface to orient on gs = Rhino.Input.Custom.GetObject() gs.SetCommandPrompt("Surface to orient on") gs.GeometryFilter = Rhino.DocObjects.ObjectType.Surface gs.SubObjectSelect = True gs.DeselectAllBeforePostSelect = False gs.OneByOnePostSelect = True gs.Get() if gs.CommandResult()!=Rhino.Commands.Result.Success: return gs.CommandResult() objref = gs.Object(0) # get selected surface object obj = objref.Object() if not obj: return Rhino.Commands.Result.Failure # get selected surface (face) surface = objref.Surface() if not surface: return Rhino.Commands.Result.Failure # Unselect surface obj.Select(False) # Point on surface to orient to gp.SetCommandPrompt("Point on surface to orient to") gp.Constrain(surface, False) gp.Get() if gp.CommandResult()!=Rhino.Commands.Result.Success: return gp.CommandResult() # Do transformation rc = Rhino.Commands.Result.Failure getrc, u, v = surface.ClosestPoint(gp.Point()) if getrc: getrc, target_plane = surface.FrameAt(u,v) if getrc: # Build transformation xform = Rhino.Geometry.Transform.PlaneToPlane(source_plane, target_plane) # Do the transformation. In this example, we will copy the original objects delete_original = False for i in range(go.ObjectCount): rhobj = go.Object(i) scriptcontext.doc.Objects.Transform(rhobj, xform, delete_original) scriptcontext.doc.Views.Redraw() rc = Rhino.Commands.Result.Success return rc if __name__=="__main__": OrientOnSrf() ``` -------------------------------------------------------------------------------- # Orienting Objects on Surfaces Source: https://developer.rhino3d.com/en/guides/cpp/orienting-objects-on-surfaces/ This guide demonstrates how to orient objects on a surface using C/C++. ## Overview Rhino orients objects onto a surface by defining a rotation transformation. With the Rhino C/C++ SDK, you define transformations with the `ON_Xform` class. See *opennurbs_xform.h* for details. The transformation that is defined rotates objects from one plane to another. The source plane is defined from the active view's construction plane and a user-selected base point. The target plane is defined by the location the user picks on the surface. ## Sample The following sample code demonstrates how to orient objects on a surface. **NOTE**: This sample does not contain all of the options available in the Rhino command, nor does it show the objects transforming dynamically, but it is a good example on how to build the transformation. ```cpp CRhinoCommand::result CCommandTest::RunCommand( const CRhinoCommandContext& context ) { // Select objects to orient CRhinoGetObject go; go.SetCommandPrompt( L"Select objects to orient" ); go.EnableSubObjectSelect( FALSE ); go.EnableGroupSelect( TRUE ); go.GetObjects( 1, 0 ); if( go.CommandResult() != success ) return go.CommandResult(); // Point to orient from CRhinoGetPoint gp; gp.SetCommandPrompt( L"Point to orient from" ); gp.GetPoint(); if( gp.CommandResult() != success ) return gp.CommandResult(); // Define source plane CRhinoView* view = gp.View(); if( 0 == view ) { view = RhinoApp().ActiveView(); if( 0 == view ) return failure; } ON_Plane source_plane( view->Viewport().ConstructionPlane().m_plane ); source_plane.SetOrigin( gp.Point() ); // Surface to orient on CRhinoGetObject gs; gs.SetCommandPrompt( L"Surface to orient on" ); gs.SetGeometryFilter( CRhinoGetObject::surface_object ); gs.EnableSubObjectSelect( TRUE ); gs.EnableDeselectAllBeforePostSelect( false ); gs.EnableOneByOnePostSelect(); gs.GetObjects( 1, 1 ); if( gs.CommandResult() != success ) return gs.CommandResult(); const CRhinoObjRef& ref = gs.Object(0); // Get selected surface object const CRhinoObject* obj = ref.Object(); if( 0 == obj ) return failure; // Get selected surface (face) const ON_BrepFace* face = ref.Face(); if( 0 == face ) return failure; // Unselect surface obj->Select( false ); // Point on surface to orient to gp.SetCommandPrompt( L"Point on surface to orient to" ); gp.Constrain( *face ); gp.GetPoint(); if( gp.CommandResult() != success ) return gp.CommandResult(); // Do transformation CRhinoCommand::result rc = failure; double u = 0.0, v = 0.0; if( face->GetClosestPoint(gp.Point(), &u, &v) ) { ON_Plane target_plane; if( face->FrameAt(u, v, target_plane) ) { // If the face orientation is opposite // of natural surface orientation, then // flip the plane's zaxis. if( face->m_bRev ) { target_plane.yaxis.Reverse(); target_plane.zaxis.Reverse(); target_plane.UpdateEquation(); } // Build transformation ON_Xform xform; xform.Rotation( source_plane, target_plane ); // Do the transformation. In this example, // we will copy the original objects bool bDeleteOriginal = false; int i; for( i = 0; i < go.ObjectCount(); i++ ) context.m_doc.TransformObject( go.Object(i), xform, bDeleteOriginal ); context.m_doc.Redraw(); rc = success; } } return rc; } ``` -------------------------------------------------------------------------------- # Orthogonal Mode Source: https://developer.rhino3d.com/en/samples/rhinocommon/orthogonal-mode/ Demonstrates how to enable or disable orthogonal mode and its effects. ```cs partial class Examples { public static Result Ortho(RhinoDoc doc) { var gp = new GetPoint(); gp.SetCommandPrompt("Start of line"); gp.Get(); if (gp.CommandResult() != Result.Success) return gp.CommandResult(); var start_point = gp.Point(); var original_ortho = ModelAidSettings.Ortho; if (!original_ortho) ModelAidSettings.Ortho = true; gp.SetCommandPrompt("End of line"); gp.SetBasePoint(start_point, false); gp.DrawLineFromPoint(start_point, true); gp.Get(); if (gp.CommandResult() != Result.Success) return gp.CommandResult(); var end_point = gp.Point(); if (ModelAidSettings.Ortho != original_ortho) ModelAidSettings.Ortho = original_ortho; doc.Objects.AddLine(start_point, end_point); doc.Views.Redraw(); return Result.Success; } } ``` ```python from Rhino import * from Rhino.ApplicationSettings import * from Rhino.Commands import * from Rhino.Input.Custom import * from scriptcontext import doc def RunCommand(): gp = GetPoint() gp.SetCommandPrompt("Start of line") gp.Get() if gp.CommandResult() != Result.Success: return gp.CommandResult() start_point = gp.Point() original_ortho = ModelAidSettings.Ortho if not original_ortho: ModelAidSettings.Ortho = True gp.SetCommandPrompt("End of line") gp.SetBasePoint(start_point, False) gp.DrawLineFromPoint(start_point, True) gp.Get() if gp.CommandResult() != Result.Success: return gp.CommandResult() end_point = gp.Point() if ModelAidSettings.Ortho != original_ortho: ModelAidSettings.Ortho = original_ortho doc.Objects.AddLine(start_point, end_point) doc.Views.Redraw() return Result.Success if __name__ == "__main__": RunCommand() ``` -------------------------------------------------------------------------------- # Overlay Text Display Conduit Source: https://developer.rhino3d.com/en/samples/rhinocommon/overlay-text-display-conduit/ Demonstrates how to use a display conduit to draw overlay text. ```cs class CustomConduit : Rhino.Display.DisplayConduit { protected override void DrawForeground(Rhino.Display.DrawEventArgs e) { var bounds = e.Viewport.Bounds; var pt = new Rhino.Geometry.Point2d(bounds.Right - 100, bounds.Bottom - 30); e.Display.Draw2dText("Hello", System.Drawing.Color.Red, pt, false); } } partial class Examples { readonly static CustomConduit m_customconduit = new CustomConduit(); public static Rhino.Commands.Result DrawOverlay(RhinoDoc doc) { // toggle conduit on/off m_customconduit.Enabled = !m_conduit.Enabled; RhinoApp.WriteLine("Custom conduit enabled = {0}", m_customconduit.Enabled); doc.Views.Redraw(); return Rhino.Commands.Result.Success; } } ``` ```python import Rhino import System.Drawing import scriptcontext import rhinoscriptsyntax as rs # DisplayConduit subclass that overrides the DrawForeground function # e is an instance of Rhino.Display.DrawEventArgs class CustomConduit(Rhino.Display.DisplayConduit): def DrawForeground(self, e): color = System.Drawing.Color.Red bounds = e.Viewport.Bounds pt = Rhino.Geometry.Point2d(bounds.Right - 100, bounds.Bottom - 30) e.Display.Draw2dText("Hello", color, pt, False) def showafterscript(): # Create a custom conduit that can continue to draw after the # script has completed. The conduit is kept in the sticky # dictionary so we can get at it and turn it off in the future # # check to see if the conduit has been created and is in sticky conduit = None if "myconduit" in scriptcontext.sticky: conduit = scriptcontext.sticky["myconduit"] else: # create a conduit and place it in sticky conduit = CustomConduit() scriptcontext.sticky["myconduit"] = conduit # Toggle enabled state for conduit. Every time this script is # run, it will turn the conduit on and off conduit.Enabled = not conduit.Enabled if conduit.Enabled: print("conduit enabled") else: print("conduit disabled") scriptcontext.doc.Views.Redraw() def showinscript(): # create a custom conduit that only displays during the execution # of this script. Once the script has completed, the conduit is turned # off and display goes back to normal conduit = CustomConduit() conduit.Enabled = True scriptcontext.doc.Views.Redraw() rs.GetString("Pausing for user input") conduit.Enabled = False scriptcontext.doc.Views.Redraw() if __name__=="__main__": showinscript() #showafterscript() ``` -------------------------------------------------------------------------------- # Package Restore in Grasshopper Source: https://developer.rhino3d.com/en/guides/yak/package-restore-in-grasshopper/ How can Grasshopper use Yak to make your life easier? ## Overview For starters, this is less of a developer guide and more of a description of how this feature works, so that you, the developer, can better understand how your package and plug-in needs to be set up in order to leverage it. It can be frustrating to open a Grasshopper definition only to find that the required plug-ins aren't installed on the system. The package manager can help by streamlining the process of satisfying those dependencies. Since Rhino 6, the "Unrecognized Objects" dialog presents the user with an option to _download and install_ missing plug-ins. This feature is called Package Restore. ![Grasshopper package restore](https://developer.rhino3d.com/images/yak-gh-restore.gif) Package Restore uses the name, ID and version of the missing plug-ins to search the package server. If any packages match the search query then they will be installed and, if possible, loaded prior to opening the definition[^3]. ## Matching ### Naming Ideally, the name of the Grasshopper plug-in and the package will match. In case this isn't possible – due to either the constraints of the package naming scheme[^1] or the fact that there are multiple plug-ins in a package each with a different name – the correct package can also be identified by the plug-in ID. For each .gha file, the plug-in ID is extracted and added to the `manifest.yml` when you run `yak build`. ### Version numbers Package version numbers can either follow the [Semantic Versioning 2.0.0](https://semver.org) (SemVer) spec or they can be four-digits[^2], as per `System.Version`. See the [package server](../the-package-server) guide for more details on the allowed version number formats. The server allows both SemVer and four-digit because some Grasshopper plug-ins will specify their version number as a `string` in a class derived from `GH_AssemblyInfo` whereas others will rely on the `AssemblyVersionAttribute`. While restoring packages, if a package exists on the server that matches either the name or ID of the missing plug-in, but the exact version doesn't exist, the latest stable version will be installed. [^1]: Package names are pretty strict. They only allow letters, numbers, hyphens and underscores. [^2]: Support for four-digit (`System.Version`) version numbers was added in Yak 0.8. [^3]: Added to Grasshopper in December 2019. On-the-fly loading is only possible if another version of the Grasshopper library is not already installed and loaded. Otherwise, Rhino will need to be restarted to load the new version of the library. -------------------------------------------------------------------------------- # Padding Digits Source: https://developer.rhino3d.com/en/guides/rhinoscript/pad-digits/ This short guide demonstrate how to pad numbers with leading zeros in RhinoScript. ## Problem How do you pad digits with leading zeros? For example, if you have the number 24, how can you print it as “0024”? ## Solution The following utility function will pad an integer with a specified number of digits: ```vbnet Function PadDigits(val, digits) PadDigits = Right(String(digits,"0") & val, digits) End Function ``` You can use this function as such: ```vbnet For i = 0 To 25 Rhino.Print PadDigits(i, 4) Next ``` The output will be: ```vbs 0000 0001 0002 0003 0004 0005 0006 0007 0008 0009 0010 ... ``` -------------------------------------------------------------------------------- # Parentheses Error Source: https://developer.rhino3d.com/en/guides/rhinoscript/parentheses/ This guide discusses the Cannot use parentheses when calling a Sub error that occurs in RhinoScript. ## Problem Every now and then, you may get the error message "Cannot use parentheses when calling a Sub" when calling a function or method. This does not happen all the time. For example, the following code appears to work: ```vbnet Result = MyFunc(MyArg) MySub(MyArg) ``` ...but this code does not work: ```vbnet Result = MyOtherFunc(MyArg1, MyArg2) MyOtherSub(MyArg1, MyArg2) ``` ## Solution In VBScript, parentheses mean several different things: Evaluate a subexpression before the rest of the expression. For example: ```vbnet Average = (First + Last) / 2 ``` or... Dereference the index of an array. For example: ```vbnet Item = MyArray(Index) ``` or... Call a function or subroutine. For example: ```vbnet Limit = UBound(MyArray) ``` or... Pass an argument which would normally be `ByRef` as `ByVal`. For example... ```vbnet 'Arg1 is passed ByRef, Arg2 is passed ByVal. Result = MyFunction(Arg1, (Arg2)) ``` And, there are additional rules that apply when calling a function or subroutine... An argument list for a function call with an assignment to the returned value must be surrounded by parentheses. For example: ```vbnet Result = MyFunc(MyArg) ``` An argument list for a subroutine call, or a function call with no assignment, that uses the `Call` keyword must be surrounded by parentheses. For example: ```vbnet Call MySub(MyArg) ``` If the above two rules do not apply, then the list must not be surrounded by parentheses. Finally, there is the `ByRef` rule: arguments are passed `ByRef` when possible. But, if there are extra parentheses around a variable, then the variable is passed `ByVal`, not `ByRef`. From these rules, it should be clear why the statement `MySub(MyArg)` is legal but `MyOtherSub(MyArg1, MyArg2)` is not. The first case appears to be a subroutine call with parentheses around the argument list, but that would violate the rules. Then why does this work? In fact, it is a subroutine call with no parentheses around the argument list, but parentheses around the first argument. This passes the argument by value. The second case is a clear violation of rules, and there is no way to make it legal, so an error is given. ## Examples Here are some examples to what is legal and what is not in VBScript. Suppose `X` and `Y` are variables, `Func1` is a one argument procedure, and `Func2` is a two argument procedure. To pass `X` `ByRef` and `Y` `ByRef`: ```vbnet Func1 X Call Func1(X) Z = Func1(X) Func2 X, Y Call Func2(X, Y) Z = Func2(X, Y) ``` To pass `X` `ByVal` and `Y` `ByRef`: ```vbnet Func1(X) Call Func1((X)) Z = Func1((X)) Func2 (X), Y Func2 ((X)), Y Call Func2((X), Y) Z = Func2((X), Y) ``` The following will give syntax errors: ```vbnet Call Func1 X Z = Func1 X Func2(X, Y) Call Func2 X, Y Z = Func2 X, Y ``` -------------------------------------------------------------------------------- # Parsing Text Files Source: https://developer.rhino3d.com/en/guides/rhinoscript/parsing-text-files/ This guide discusses how to convert data read from a text file into its proper data type in RhinoScript. ## Problem A frequent workflow is using a text file - generated outside Rhino - to change a Rhino model. You may know how to read in a text file and parse it with VBScript, but what about parsing the text file and assigning the values as a variable? ## Solution Consider the following VBScript subroutine that reads a text file: ```vbnet Sub ReadTextFile Dim objFSO, objFile, strFileName, strLine Const ForReading = 1 strFileName = Rhino.OpenFileName("Open", "Text Files (*.txt)|*.txt|") If IsNull(strFileName) Then Exit Sub Set objFSO = CreateObject("Scripting.FileSystemObject") Set objFile = objFSO.OpenTextFile(strFileName, ForReading) While Not objFile.AtEndOfStream strLine = objFile.ReadLine Rhino.Print strLine Wend objFile.Close Set objFSO = Nothing End Sub ``` Note how every line read from the text file is assigned to the `strLine` variable before it is printed. Everything read from a text file using VBScript comes in as a string data type. If you want something that you have read to be an integer or a floating point number, then you need to convert the string to that data type. For example, if you have a text file: ``` 7 3.14159 Hello Rhino! ``` If you use VBScript to read this file, all three lines are read in as strings. If you want line one read as an integer and line read as a double, then you can use some of VBScript's data conversion functions to convert the string to the proper data type. For example: ```vbnet Dim nFirst, dblSecond, strThird nFirst = CInt(objFile.ReadLine) dblSecond = CDbl(objFile.ReadLine) strThird = objFile.ReadLine ``` **NOTE**: the above example does have the limitation that you have to know what kind of data is on each line. If you don't know what kind of data is on each line, then consider exporting a data type identifier along with the data from the program that generated the file. This way, when you read a line from VBScript, you will know what kind of data you have. VBScript's `VarType` function will return a value indicating the type of variable. The possible values are: | Type | | | | Value | | | | Description | |:--------|:-:|:-:|:-:|:-------:|:-:|:-:|:-:|:--------| | `vbEmpty` | | | | 0 | | | | Uninitialized (default) | | `vbNull` | | | | 1 | | | | Contains no valid data | | `vbInteger` | | | | 2 | | | | Integer subtype | | `vbLong` | | | | 3 | | | | Long subtype | | `vbSingle` | | | | 4 | | | | Single subtype | | `vbSingle` | | | | 5 | | | | Double subtype | | `vbCurrency` | | | | 6 | | | | Currency subtype | | `vbDate` | | | | 7 | | | | Date subtype | | `vbString` | | | | 8 | | | | String subtype | | `vbObject` | | | | 9 | | | | Object | | `vbError` | | | | 10 | | | | Error subtype | | `vbBoolean` | | | | 11 | | | | Boolean subtype | | `vbVariant` | | | | 12 | | | | Variant (used only for arrays of variants) | | `vbDataObject` | | | | 13 | | | | Data access object | | `vbDecimal` | | | | 14 | | | | Decimal subtype | | `vbByte` | | | | 17 | | | | Byte subtype | | `vbArray` | | | | 8192 | | | | Array | The text file above written to include the data's type identifier would look like this: ``` 2;7 5;3.14159 8;Hello Rhino! ``` Now each line in the text file contains both the data type and the data. Using VBScript's `Split` function, we can separate the data type from the data. Then, it is just a matter of testing for the types of data that our script supports and then performing the proper data conversion. For example: ```vbnet Dim strLine, arrLine, nType, vaValue strLine = objFile.ReadLine arrLine = Split(strLine, ";") nType = CInt(arrLine(0)) Select Case nType Case 2 vaValue = CInt(arrLine(1)) Case 3 vaValue = CLng(arrLine(1)) Case 4 vaValue = CSng(arrLine(1)) Case 5 vaValue = CDbl(arrLine(1)) Case 8 vaValue = arrLine(1) Case 11 vaValue = CBool(arrLine(1)) Case Else Rhino.Print "Unsupported data type." End Select ``` -------------------------------------------------------------------------------- # Periodic Curves & Surfaces Source: https://developer.rhino3d.com/en/guides/opennurbs/periodic-curves-and-surfaces/ This guide discusses periodic curves and surfaces and openNURBS toolkit. A periodic knot vector can be either uniform or non-uniform. A periodic degree d NURBS curve has (d-1) continuous derivatives at the start/end point. The differences between successive knot values are equal near the start and end of the spline; that is, the differences repeat themselves and hence the term “periodic”. Specifically, when -degree < i < degree, a periodic knot vector satisfies: ``` k[(degree-1)+i+1] - k[(degree-1)+i] = k[(cv_count-1)+i+1] - k[(cv_count)+i] ``` For example a cubic periodic knot vector looks like: ``` {a,b,c,d,e, ..., p+a,p+b,p+c,p+d,p+e} ``` with a < b < c < d < e and e < p+a. Chapter 12 of The NURBS Book has a few pages discussing periodic [NURBS](/guides/opennurbs/nurbs-geometry-overview/) (look in the index), but the discussion is limited. Chapter 14 of DeBoor's Practical Guide to Splines provides a few more details. ## Related topics - [NURBS Geometry Overview](/guides/opennurbs/nurbs-geometry-overview/) -------------------------------------------------------------------------------- # Perpendicular Vectors Source: https://developer.rhino3d.com/en/samples/rhinoscript/perpendicular-vectors/ Demonstrates how to calculate a vector that is perpendicular to another vector using RhinoScript. ```vbnet ' Description: ' Returns a 3-D vector that is perpendicular to another 3-D vector. ' Parameters: ' v - the 3-D vector to evaluate. ' Returns: ' A perpendicular 3-D vector if successful, Null otherwise. ' Remarks: ' The result is not a unitized 3-D vector. ' Function GetPerpendicularVector( v ) GetPerpendicularVector = Null If Not IsArray(v) Or UBound(v) <> 2 Then Exit Function If Rhino.IsVectorZero(v) Then Exit Function Dim i, j, k Dim a, b k = 2 If Abs(v(1)) > Abs(v(0)) Then If Abs(v(2)) > Abs(v(1)) Then ' |v(2)| > |v(1)| > |v(0)| i = 2 j = 1 k = 0 a = v(2) b = -v(1) ElseIf Abs(v(2)) >= Abs(v(0)) Then ' |v(1)| >= |v(2)| >= |v(0)| i = 1 j = 2 k = 0 a = v(1) b = -v(2) Else ' |v(1)| > |v(0)| > |v(2)| i = 1 j = 0 k = 2 a = v(1) b = -v(0) End If ElseIf Abs(v(2)) > Abs(v(0)) Then ' |v(2)| > |v(0)| >= |v(1)| i = 2 j = 0 k = 1 a = v(2) b = -v(0) ElseIf Abs(v(2)) > Abs(v(1)) Then ' |v(0)| >= |v(2)| > |v(1)| i = 0 j = 2 k = 1 a = v(0) b = -v(2) Else ' |v(0)| >= |v(1)| >= |v(2)| i = 0 j = 1 k = 2 a = v(0) b = -v(1) End If Dim rc(2) rc(i) = b rc(j) = a rc(k) = 0.0 GetPerpendicularVector = rc End Function ``` -------------------------------------------------------------------------------- # Persistent Settings Source: https://developer.rhino3d.com/en/guides/rhinoscript/persistent-settings/ This brief guide demonstrates how to use private variables for persistent settings in RhinoScript. ## Problem It can be annoying to enter the same custom parameter each time you run a script. ## Solution The following script demonstrates how to use a private variable to store a parameter during a Rhino session. ```vbnet ' How to script with persisting settings ' Jess Maertterer - 20.12.2004 Option Explicit Private dblLength If IsEmpty(dblLength) Then dblLength = 2.00 End If "https://developer.rhino3d.com/Sub Extend_Curve_Length_Persist Dim arrCurves, strCurve, dblResult arrCurves = Rhino.GetObjects("Select curves to extend", 4) If Not IsNull(arrCurves) Then dblResult = Rhino.GetReal("Length to extend", dblLength,0.00) If Not IsNull(dblResult) Then dblLength = dblResult For Each strCurve in arrCurves Rhino.ExtendCurveLength strCurve, 2, 2, dblLength Next End If End IfEnd Sub```If your script should remember the settings from the last session, then you have to use the RhinoScript methods `SaveSettings` and `GetSettings` to access a separate *.ini* file.--------------------------------------------------------------------------------# Pick Angle InteractivelySource: https://developer.rhino3d.com/en/samples/cpp/pick-angle-interactively/Demonstrates how to use the CRhinoGetAngle class to pick an angle.```cppCRhinoCommand::result CCommandTest::RunCommand(const CRhinoCommandContext& context){ // Prompt for base point CRhinoGetPoint gp; gp.SetCommandPrompt( L"Base point" ); gp.ConstrainToConstructionPlane( FALSE ); gp.GetPoint(); if( gp.CommandResult() != CRhinoCommand::success ) return gp.CommandResult(); // Get first picked point ON_3dPoint origin = gp.Point(); // Get view used during GetPoint CRhinoView* view = gp.View(); if( !view ) { // If scripted, get active view view = ::RhinoApp().ActiveView(); if( !view ) return CRhinoCommand::failure; } // Get view"s construction plane and move it to the picked point ON_Plane plane = view->Viewport().ConstructionPlane().m_plane; plane.SetOrigin( origin ); // Prompt for first reference point gp.SetCommandPrompt( L"First reference point" ); gp.SetBasePoint( origin ); gp.DrawLineFromPoint( origin, TRUE ); gp.Constrain( plane ); // Constrain picking to plane gp.GetPoint(); if( gp.CommandResult() != CRhinoCommand::success ) return gp.CommandResult(); // Get second picked point ON_3dPoint refpt = gp.Point(); // Prompt for angle CRhinoGetAngle ga; ga.SetCommandPrompt( L"Second reference point" ); ga.SetBasePoint( origin ); ga.SetBase( origin ); ga.SetReferencePoint( refpt ); ga.Constrain( plane ); // Constrain picking to plane ga.GetAngle(); if( ga.CommandResult() != CRhinoCommand::success ) return ga.CommandResult(); // Results double radians = ga.Angle(); double degrees = radians * (180.0/ON_PI); RhinoApp().Print( L"Angle = %f.\n", degrees ); return CRhinoCommand::success; } ``` -------------------------------------------------------------------------------- # Pick Point Source: https://developer.rhino3d.com/en/samples/rhinocommon/pick-point/ Demonstrates how to pick and select a point object. ```cs partial class Examples { public static Result PickPoint(RhinoDoc doc) { // this creates a point where the mouse is clicked. var gp = new GetPoint(); gp.SetCommandPrompt("Click for new point"); gp.Get(); if (gp.CommandResult() != Result.Success) return gp.CommandResult(); var point3d = gp.Point(); doc.Objects.AddPoint(point3d); doc.Views.Redraw(); // selects a point that already exists ObjRef obj_ref; var rc = RhinoGet.GetOneObject("Select point", false, ObjectType.Point, out obj_ref); if (rc != Result.Success) return rc; var point = obj_ref.Point(); RhinoApp.WriteLine("Point: x:{0}, y:{1}, z:{2}", point.Location.X, point.Location.Y, point.Location.Z); doc.Objects.UnselectAll(); // selects multiple points that already exist ObjRef[] obj_refs; rc = RhinoGet.GetMultipleObjects("Select point", false, ObjectType.Point, out obj_refs); if (rc != Result.Success) return rc; foreach (var o_ref in obj_refs) { point = o_ref.Point(); RhinoApp.WriteLine("Point: x:{0}, y:{1}, z:{2}", point.Location.X, point.Location.Y, point.Location.Z); } doc.Objects.UnselectAll(); // also selects a point that already exists. // Used when RhinoGet doesn't provide enough control var go = new GetObject(); go.SetCommandPrompt("Select point"); go.GeometryFilter = ObjectType.Point; go.GetMultiple(1, 0); if (go.CommandResult() != Result.Success) return go.CommandResult(); foreach (var o_ref in go.Objects()) { point = o_ref.Point(); if (point != null) RhinoApp.WriteLine("Point: x:{0}, y:{1}, z:{2}", point.Location.X, point.Location.Y, point.Location.Z); } doc.Views.Redraw(); return Result.Success; } } ``` ```python from Rhino import RhinoApp from Rhino.DocObjects import ObjectType from Rhino.Commands import Result from Rhino.Input import RhinoGet from Rhino.Input.Custom import GetPoint, GetObject from scriptcontext import doc import rhinoscriptsyntax as rs def RunCommand(): # creates a point where the mouse is clicked # using RhinoCommon gp = GetPoint() gp.SetCommandPrompt("Click for point") gp.Get() if gp.CommandResult() != Result.Success: return gp.CommandResult() point3d = gp.Point() doc.Objects.AddPoint(point3d) doc.Views.Redraw() # creates a point where the mouse is clicked # using the RhinoScript syntax point3d = rs.GetPoint("Click for point") if point3d == None: return Result.Nothing rs.AddPoint(point3d) doc.Objects.AddPoint(point3d) # selects a point that already exists # using RhinoCommon rc, obj_ref = RhinoGet.GetOneObject("Select point", False, ObjectType.Point) if rc != Result.Success: return rc point = obj_ref.Point() RhinoApp.WriteLine("Point: x:{0}, y:{1}, z:{2}", point.Location.X, point.Location.Y, point.Location.Z) doc.Objects.UnselectAll() # also selects a point that already exists # using RhinoCommon # Used when RhinoGet doesn't provide enough control go = GetObject() go.SetCommandPrompt("Select point") go.GeometryFilter = ObjectType.Point go.GetMultiple(1, 0) if go.CommandResult() != Result.Success: return go.CommandResult() for o_ref in go.Objects(): point = o_ref.Point() if point != None: RhinoApp.WriteLine("Point: x:{0}, y:{1}, z:{2}", point.Location.X, point.Location.Y, point.Location.Z) doc.Objects.UnselectAll() # selects a point that already exists # using RhinoScript syntax point_id = rs.GetObject("Select point", rs.filter.point) if point_id == None: return Result.Nothing print("point id: {0}".format(point_id)) rs.UnselectAllObjects() # selects multiple points that already exist rc, obj_refs = RhinoGet.GetMultipleObjects("Select point", False, ObjectType.Point) if rc != Result.Success: return rc for o_ref in obj_refs: point = o_ref.Point() RhinoApp.WriteLine("Point: x:{0}, y:{1}, z:{2}", point.Location.X, point.Location.Y, point.Location.Z) doc.Objects.UnselectAll() # selects multiple poins that already exists # using the RhinoScript syntax point_ids = rs.GetObjects("Select point", rs.filter.point) for p_id in point_ids: print("point id: {0}".format(p_id)) return Result.Success if __name__ == "__main__": RunCommand() ``` -------------------------------------------------------------------------------- # Pick Points Source: https://developer.rhino3d.com/en/samples/rhinocommon/pick-points/ Demonstrates how to pick and select point objects. ```cs partial class Examples { private static readonly List m_conduit_points = new List(); public static Rhino.Commands.Result PickPoints(RhinoDoc doc) { var conduit = new PointsConduit(m_conduit_points); conduit.Enabled = true; var gp = new Rhino.Input.Custom.GetPoint(); while (true) { gp.SetCommandPrompt("click location to create point. ( exit)"); gp.AcceptNothing(true); gp.Get(); if (gp.CommandResult() != Rhino.Commands.Result.Success) break; m_conduit_points.Add(new ConduitPoint(gp.Point())); doc.Views.Redraw(); } var gcp = new GetConduitPoint(m_conduit_points); while (true) { gcp.SetCommandPrompt("select conduit point. ( to exit)"); gcp.AcceptNothing(true); gcp.Get(true); doc.Views.Redraw(); if (gcp.CommandResult() != Rhino.Commands.Result.Success) break; } return Rhino.Commands.Result.Success; } } public class ConduitPoint { public ConduitPoint(Point3d point) { Color = System.Drawing.Color.White; Point = point; } public System.Drawing.Color Color { get; set; } public Point3d Point { get; set; } } public class GetConduitPoint : GetPoint { private readonly List m_conduit_points; public GetConduitPoint(List conduitPoints ) { m_conduit_points = conduitPoints; } protected override void OnMouseDown(GetPointMouseEventArgs e) { base.OnMouseDown(e); var picker = new PickContext(); picker.View = e.Viewport.ParentView; picker.PickStyle = PickStyle.PointPick; var xform = e.Viewport.GetPickTransform(e.WindowPoint); picker.SetPickTransform(xform); foreach (var cp in m_conduit_points) { double depth; double distance; if (picker.PickFrustumTest(cp.Point, out depth, out distance)) cp.Color = System.Drawing.Color.Red; else cp.Color = System.Drawing.Color.White; } } } class PointsConduit : Rhino.Display.DisplayConduit { private readonly List m_conduit_points; public PointsConduit(List conduitPoints ) { m_conduit_points = conduitPoints; } protected override void DrawForeground(Rhino.Display.DrawEventArgs e) { if (m_conduit_points != null) foreach (var cp in m_conduit_points) e.Display.DrawPoint(cp.Point, PointStyle.Simple, 3, cp.Color); } } ``` ```python # No Python sample available ``` -------------------------------------------------------------------------------- # Picking Brep Components Source: https://developer.rhino3d.com/en/guides/cpp/picking-brep-components/ This brief guide discusses picking components of a Brep using C/C++. ## Problem You would like to pick a brep component (e.g. face or edge) and then take action depending on what was picked. If you write an object picker as such: ```cpp CRhinoGetObject go; go.SetCommandPrompt( L"Select face or edge" ); go.SetGeometryFilter( CRhinoGetObject::surface_object | CRhinoGetObject::edge_object ); go.EnableSubObjectSelect( true ); go.GetObjects( 1, 1 ); ``` and then use it to try to pick the edge of a box, for example, only the faces show up on the "choose one object" menu. ## Solution You need to enable "choose one question" to get multiple subobject types on one brep to work. So, this should work: ```cpp CRhinoGetObject go; go.SetCommandPrompt( L"Select face or edge" ); go.SetGeometryFilter( CRhinoGetObject::surface_object | CRhinoGetObject::edge_object ); go.EnableSubObjectSelect( true ); go.EnableChooseOneQuestion( true ); // ADD THIS... go.GetObjects( 1, 1 ); ``` -------------------------------------------------------------------------------- # Picking Objects without CRhinoGetObject Source: https://developer.rhino3d.com/en/guides/cpp/picking-objects-without-crhinogetobject/ This guide demonstrates an alternate technique to picking objects without using CRhinoGetObject. ## Problem Imagine you have a 3D point, now you want to pick all the objects that are underneath it. ## Solution You can use `CRhinoPickContext` to build your own object picker. For more details on `CRhinoPickContext`, see *rhinoSdkPick.h*. With the `CRhinoPickContext` class, you can define the rules for picking. For example, you can specify a picking style (point, window, crossing). You can also specify a filter so you only pick the types of objects you want. The most important part is to define a pick chord, which starts on near clipping plane and ends on far clipping plane. After you have created the `CRhinoPickContext` object and filled out its parameters, call `CRhinoDoc::PickObjects`. This function goes through the list of eligible objects and intersects them with the pick frustum. If they hit the frustum in an acceptable manner, the object is added to a pick list passed in by the caller. Here is a simple example of a function that might work for you: ```cpp static int MyObjectPicker( CRhinoDoc& doc, CRhinoView* view, POINT point, CRhinoObjRefArray& pick_list ) { if( 0 == view ) return 0; CRhinoPickContext pick_context; pick_context.m_view = view; pick_context.m_pick_style = CRhinoPickContext::point_pick; CRhinoViewport& active_vp = view->ActiveViewport(); switch( active_vp.DisplayMode() ) { case ON::shaded_display: case ON::renderpreview_display: pick_context.m_pick_mode = CRhinoPickContext::shaded_pick; break; } int pick_count = 0; if( active_vp.GetPickXform(point.x, point.y, pick_context.m_pick_region.m_xform) ) { // adds objects to pick_list - does not change any status active_vp.VP().GetFrustumLine( point.x, point.y, pick_context.m_pick_line ); pick_context.UpdateClippingPlanes(); pick_count = doc.PickObjects( pick_context, pick_list ); } return pick_count; } ``` And, here is a sample of its use: ```cpp CRhinoCommand::result CCommandTest::RunCommand( const CRhinoCommandContext& context ) { CRhinoGetPoint gp; gp.GetPoint(); if( gp.CommandResult() != CRhinoCommand::success ) return gp.CommandResult(); CRhinoView* view = gp.View(); if( 0 == view ) return failure; ON_Xform w2s; view->ActiveViewport().VP().GetXform( ON::world_cs, ON::screen_cs, w2s ); ON_3dPoint pt3d = gp.Point(); pt3d.Transform( w2s ); POINT pt2d; pt2d.x = (int)pt3d.x; pt2d.y = (int)pt3d.y; CRhinoObjRefArray pick_list; int pick_count = MyObjectPicker( context.m_doc, view, pt2d, pick_list ); for( int i = 0; i < pick_count; i++ ) { const CRhinoObject* obj = pick_list[i].Object(); if( obj ) obj->Select( true, true, true ); } context.m_doc.Redraw(); return CRhinoCommand::success; } ``` -------------------------------------------------------------------------------- # Picking Point Objects Source: https://developer.rhino3d.com/en/guides/cpp/picking-point-objects/ This brief guide demonstrates how to use CRhinoGetObject to pick point objects using C/C++. ## How To If you need the user to define a 3D point location, you can use a `CRhinoGetPoint` object. But, if the points already exist as objects in Rhino, you will need to use a `CRhinoGetObject` object to pick them. Then, you can determine the 3D coordinates of that point object, for example: ```cpp CRhinoGetObject go; go.SetCommandPrompt( L"Select point" ); go.SetGeometryFilter( CRhinoGetObject::point_object ); CRhinoGet::result res = go.GetObjects( 1, 1 ); if( res == CRhinoGet::object ) { const CRhinoObjRef& objref = go.Object(0); const ON_Point* pt = objref.Point(); if( pt ) RhinoApp().Print(L"Point: %f,%f,%f", pt->point.x, pt->point.y, pt->point.z); } ``` If you needed access to the `CRhinoPointObject` object, you could do this: ```cpp CRhinoGetObject go; go.SetCommandPrompt( L"Select point" ); go.SetGeometryFilter( CRhinoGetObject::point_object ); CRhinoGet::result res = go.GetObjects( 1, 1 ); if( res == CRhinoGet::object ) { const CRhinoObjRef& objref = go.Object(0); const CRhinoPointObject* point_object = CRhinoPointObject::Cast( objref.Object() ); if( point_object ) { const ON_Point& pt = point_object->Point(); RhinoApp().Print(L"Point: %f,%f,%f", pt.point.x, pt.point.y, pt.point.z); } } ``` Here is how you can pick one or more point objects: ```cpp CRhinoGetObject go; go.SetCommandPrompt( L"Select points" ); go.SetGeometryFilter( CRhinoGetObject::point_object ); CRhinoGet::result res = go.GetObjects( 1, 0 ); if( res == CRhinoGet::object ) { int i; for( i = 0; i < go.ObjectCount(); i++ ) { const CRhinoObjRef& objref = go.Object(i); const ON_Point* point = objref.Point(); if( point ) RhinoApp().Print( L"Point %d: %f,%f,%f", i, point->point.x, point->point.y, point->point.z ); } } ``` -------------------------------------------------------------------------------- # Picking Surface Point Source: https://developer.rhino3d.com/en/guides/cpp/picking-surface-points/ This brief guide discusses how to pick points on a surface using C/C++. ## Problem You want to pick a point on the surface of an object. ## Solution There are a couple of ways to do this: - Use a `CRhinoGetObject` class. - Use a `CRhinoGetPoint` class. ## Using CRhinoGetObject When picking objects with a `CRhinoGetObject` object, the `CRhinoObjRef` returned by the object contains picking information, such as the location of the pick... ```cpp CRhinoCommand::result CCommandTestPick1::RunCommand( const CRhinoCommandContext& context ) { CRhinoGetObject go; go.SetCommandPrompt( L"Select a surface or a polysurface" ); go.SetGeometryFilter( CRhinoGetObject::surface_object | CRhinoGetObject::polysrf_object ); go.EnablePreSelect( FALSE ); go.GetObjects( 1, 1 ); if( go.CommandResult() != CRhinoCommand::success ) return go.CommandResult(); const CRhinoObjRef& ref = go.Object(0); // If the object was selected by picking a point on it, then // SelectionPoint() returns true and the point where the selection // occured. ON_3dPoint pt; if( ref.SelectionPoint(pt) ) { context.m_doc.AddPointObject( pt ); context.m_doc.Redraw(); } return CRhinoCommand::success; } ``` ## Using CRhinoGetPoint When picking points with a `CRhinoGetPoint` object, you can constrain the point picking to a surface... ```cpp CRhinoCommand::result CCommandTestPick2::RunCommand( const CRhinoCommandContext& context ) { CRhinoGetObject go; go.SetCommandPrompt( L"Select a surface" ); go.SetGeometryFilter( CRhinoGetObject::surface_object ); go.EnablePreSelect( FALSE ); go.GetObjects( 1, 1 ); if( go.CommandResult() != CRhinoCommand::success ) return go.CommandResult(); const CRhinoObjRef& ref = go.Object(0); const ON_BrepFace* face = ref.Face(); if( 0 == face ) return CRhinoCommand::failure; CRhinoGetPoint gp; gp.SetCommandPrompt( L"Select point on surface" ); gp.Constrain( *face ); gp.GetPoint(); if( gp.CommandResult() != CRhinoCommand::success ) return gp.CommandResult(); ON_3dPoint pt = gp.Point(); context.m_doc.AddPointObject( pt ); context.m_doc.Redraw(); return CRhinoCommand::success; } ``` -------------------------------------------------------------------------------- # Picking Text Dots Source: https://developer.rhino3d.com/en/guides/cpp/picking-text-dots/ This brief guide demonstrates how to interactively select Text Dot objects with C/C++ CRhinoGetObject. ## Problem Imagine you would like to allow the user to interactively select text dot objects from my plugin command. The first place you might look is the `CRhinoGetObject` class, for a text dot geometry filter. This does not exist. So, how to go about this? ## Solution If `CRhinoGetObject` does not have filters to select what you want, then simply derive a new class from `CRhinoGetObject` and override the `CustomGeometryFilter` virtual function. From within this function, simply return true if the object meets your selection criteria. ## Sample The following example code demonstrates how to derive a new class from `CRhinoGetObject` and override the `CustomGeometryFilter` virtual function in order to allow for selection of just text dot objects. That is, those objects whose geometry member is `ON_TextDot`... ```cpp class CGetTextDotObject : public CRhinoGetObject { bool CustomGeometryFilter( const CRhinoObject* object, const ON_Geometry* geometry, ON_COMPONENT_INDEX component_index ) const; }; bool CGetTextDotObject::CustomGeometryFilter( const CRhinoObject* object, const ON_Geometry* geometry, ON_COMPONENT_INDEX component_index ) const { bool rc = false; if( geometry ) { const ON_TextDot* p = ON_TextDot::Cast( geometry ); if( p ) rc = true; } return rc; } ``` The following example code demonstrates how you can use the new class... ```cpp CRhinoCommand::result CCommandTest::RunCommand( const CRhinoCommandContext& context ) { CGetTextDotObject go; go.SetCommandPrompt( L"Select text dots" ); go.GetObjects( 1, 0 ); if( go.CommandResult() != success ) return go.CommandResult(); const int object_count = go.ObjectCount(); int i; for( i = 0; i < object_count; i++ ) { const ON_TextDot* p = ON_TextDot::Cast( go.Object(i).Geometry() ); if( p ) { ON_wString sPoint; RhinoFormatPoint( p->m_point, sPoint ); RhinoApp().Print( L"TextDot%d: point = (%s), text = \"%s\"\n", i, sPoint, p->m_text ); } } return success; } ``` -------------------------------------------------------------------------------- # Planar Surface Source: https://developer.rhino3d.com/en/samples/rhinocommon/planar-surface/ Demonstrates how to create a planar surface from a rectangle. ```cs partial class Examples { public static Result PlanarSurface(RhinoDoc doc) { Point3d[] corners; var rc = Rhino.Input.RhinoGet.GetRectangle(out corners); if (rc != Result.Success) return rc; var plane = new Plane(corners[0], corners[1], corners[2]); var plane_surface = new PlaneSurface(plane, new Interval(0, corners[0].DistanceTo(corners[1])), new Interval(0, corners[1].DistanceTo(corners[2]))); doc.Objects.Add(plane_surface); doc.Views.Redraw(); return Result.Success; } } ``` ```python import Rhino; import rhinoscriptsyntax as rs def RunCommand(): rc, corners = Rhino.Input.RhinoGet.GetRectangle() if rc != Rhino.Commands.Result.Success: return rc plane = Rhino.Geometry.Plane(corners[0], corners[1], corners[2]) u_dir = rs.Distance(corners[0], corners[1]) v_dir = rs.Distance(corners[1], corners[2]) rs.AddPlaneSurface(plane, u_dir, v_dir) if __name__ == "__main__": RunCommand() ``` -------------------------------------------------------------------------------- # Platonic and Archimedean Solids Source: https://developer.rhino3d.com/en/samples/rhinoscript/platonic-and-archimedean-solids/ Demonstrates how to generate platonic and archimedean solids with RhinoScript. ```vbnet ''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' Define geometry for rhombitruncated icosidodecahedron ''''''''''''''''''''''''''''''''''''''''''''''''''''''' Sub RhombitruncatedIcosidodecahedron() ' Define the vertices Dim arrVertices(119) arrVertices(0) = Array( 0.519, 0.280, -0.560) arrVertices(1) = Array( 0.214, -0.733, 0.280) arrVertices(2) = Array( 0.280, -0.214, -0.733) arrVertices(3) = Array( 0.107, 0.107, 0.799) arrVertices(4) = Array( 0.280, 0.214, 0.733) arrVertices(5) = Array( 0.453, 0.107, -0.667) arrVertices(6) = Array( 0.733, -0.280, 0.214) arrVertices(7) = Array( 0.280, -0.214, 0.733) arrVertices(8) = Array(-0.280, -0.560, 0.519) arrVertices(9) = Array( 0.107, -0.107, 0.799) arrVertices(10) = Array(-0.560, -0.519, 0.280) arrVertices(11) = Array(-0.280, 0.560, -0.519) arrVertices(12) = Array( 0.214, 0.733, -0.280) arrVertices(13) = Array( 0.519, -0.280, 0.560) arrVertices(14) = Array(-0.667, 0.453, -0.107) arrVertices(15) = Array( 0.799, -0.107, 0.107) arrVertices(16) = Array(-0.107, -0.667, 0.453) arrVertices(17) = Array(-0.107, 0.107, 0.799) arrVertices(18) = Array( 0.107, 0.799, 0.107) arrVertices(19) = Array(-0.453, 0.107, -0.667) arrVertices(20) = Array(-0.280, -0.560, -0.519) arrVertices(21) = Array( 0.214, -0.733, -0.280) arrVertices(22) = Array( 0.346, 0.387, 0.626) arrVertices(23) = Array( 0.560, -0.519, -0.280) arrVertices(24) = Array(-0.107, -0.107, 0.799) arrVertices(25) = Array( 0.280, -0.560, 0.519) arrVertices(26) = Array(-0.107, -0.799, 0.107) arrVertices(27) = Array( 0.280, 0.560, 0.519) arrVertices(28) = Array(-0.799, -0.107, 0.107) arrVertices(29) = Array( 0.626, -0.346, -0.387) arrVertices(30) = Array(-0.667, -0.453, -0.107) arrVertices(31) = Array( 0.519, 0.280, 0.560) arrVertices(32) = Array( 0.560, -0.519, 0.280) arrVertices(33) = Array(-0.346, 0.387, 0.626) arrVertices(34) = Array(-0.346, 0.387, -0.626) arrVertices(35) = Array(-0.453, -0.107, -0.667) arrVertices(36) = Array(-0.560, 0.519, 0.280) arrVertices(37) = Array(-0.107, 0.799, -0.107) arrVertices(38) = Array(-0.519, 0.280, 0.560) arrVertices(39) = Array(-0.519, -0.280, -0.560) arrVertices(40) = Array(-0.453, 0.107, 0.667) arrVertices(41) = Array(-0.387, 0.626, 0.346) arrVertices(42) = Array(-0.346, -0.387, 0.626) arrVertices(43) = Array(-0.107, 0.667, 0.453) arrVertices(44) = Array(-0.733, 0.280, -0.214) arrVertices(45) = Array(-0.519, -0.280, 0.560) arrVertices(46) = Array(-0.107, -0.799, -0.107) arrVertices(47) = Array( 0.387, -0.626, -0.346) arrVertices(48) = Array( 0.733, 0.280, -0.214) arrVertices(49) = Array(-0.214, 0.733, -0.280) arrVertices(50) = Array(-0.107, 0.107, -0.799) arrVertices(51) = Array( 0.733, 0.280, 0.214) arrVertices(52) = Array( 0.667, 0.453, 0.107) arrVertices(53) = Array( 0.107, -0.799, -0.107) arrVertices(54) = Array( 0.667, -0.453, 0.107) arrVertices(55) = Array( 0.107, 0.799, -0.107) arrVertices(56) = Array(-0.346, -0.387, -0.626) arrVertices(57) = Array( 0.733, -0.280, -0.214) arrVertices(58) = Array(-0.280, -0.214, 0.733) arrVertices(59) = Array(-0.667, 0.453, 0.107) arrVertices(60) = Array( 0.346, -0.387, 0.626) arrVertices(61) = Array(-0.733, 0.280, 0.214) arrVertices(62) = Array( 0.214, 0.733, 0.280) arrVertices(63) = Array( 0.453, -0.107, -0.667) arrVertices(64) = Array( 0.107, -0.667, 0.453) arrVertices(65) = Array(-0.626, -0.346, 0.387) arrVertices(66) = Array(-0.667, -0.453, 0.107) arrVertices(67) = Array( 0.107, -0.107, -0.799) arrVertices(68) = Array(-0.626, 0.346, 0.387) arrVertices(69) = Array( 0.560, 0.519, 0.280) arrVertices(70) = Array(-0.214, 0.733, 0.280) arrVertices(71) = Array(-0.733, -0.280, 0.214) arrVertices(72) = Array(-0.107, 0.667, -0.453) arrVertices(73) = Array( 0.626, 0.346, -0.387) arrVertices(74) = Array( 0.667, 0.453, -0.107) arrVertices(75) = Array(-0.733, -0.280, -0.214) arrVertices(76) = Array( 0.626, -0.346, 0.387) arrVertices(77) = Array( 0.346, 0.387, -0.626) arrVertices(78) = Array(-0.214, -0.733, 0.280) arrVertices(79) = Array(-0.519, 0.280, -0.560) arrVertices(80) = Array(-0.626, 0.346, -0.387) arrVertices(81) = Array( 0.560, 0.519, -0.280) arrVertices(82) = Array(-0.387, 0.626, -0.346) arrVertices(83) = Array( 0.107, -0.799, 0.107) arrVertices(84) = Array( 0.453, -0.107, 0.667) arrVertices(85) = Array(-0.280, 0.560, 0.519) arrVertices(86) = Array( 0.799, -0.107, -0.107) arrVertices(87) = Array(-0.626, -0.346, -0.387) arrVertices(88) = Array( 0.667, -0.453, -0.107) arrVertices(89) = Array( 0.387, 0.626, -0.346) arrVertices(90) = Array(-0.799, 0.107, -0.107) arrVertices(91) = Array(-0.560, 0.519, -0.280) arrVertices(92) = Array(-0.107, -0.667, -0.453) arrVertices(93) = Array( 0.107, -0.667, -0.453) arrVertices(94) = Array( 0.280, 0.214, -0.733) arrVertices(95) = Array(-0.799, -0.107, -0.107) arrVertices(96) = Array( 0.387, 0.626, 0.346) arrVertices(97) = Array( 0.387, -0.626, 0.346) arrVertices(98) = Array( 0.799, 0.107, 0.107) arrVertices(99) = Array( 0.107, 0.667, -0.453) arrVertices(100) = Array(-0.280, 0.214, 0.733) arrVertices(101) = Array(-0.280, 0.214, -0.733) arrVertices(102) = Array(-0.214, -0.733, -0.280) arrVertices(103) = Array( 0.280, 0.560, -0.519) arrVertices(104) = Array( 0.107, 0.107, -0.799) arrVertices(105) = Array( 0.626, 0.346, 0.387) arrVertices(106) = Array(-0.560, -0.519, -0.280) arrVertices(107) = Array( 0.799, 0.107, -0.107) arrVertices(108) = Array(-0.280, -0.214, -0.733) arrVertices(109) = Array( 0.346, -0.387, -0.626) arrVertices(110) = Array(-0.387, -0.626, -0.346) arrVertices(111) = Array( 0.453, 0.107, 0.667) arrVertices(112) = Array( 0.280, -0.560, -0.519) arrVertices(113) = Array( 0.107, 0.667, 0.453) arrVertices(114) = Array(-0.799, 0.107, 0.107) arrVertices(115) = Array(-0.107, -0.107, -0.799) arrVertices(116) = Array(-0.107, 0.799, 0.107) arrVertices(117) = Array(-0.387, -0.626, 0.346) arrVertices(118) = Array( 0.519, -0.280, -0.560) arrVertices(119) = Array(-0.453, -0.107, 0.667) 'Define the faces Dim arrFaces(61) arrFaces(0) = Array(47,23,88,54,32,97,1,83,53,21) arrFaces(1) = Array(84,111,4,3,9,7) arrFaces(2) = Array(76,6,54,32) arrFaces(3) = Array(24,9,3,17) arrFaces(4) = Array(18,62,96,69,52,74,81,89,12,55) arrFaces(5) = Array(35,39,87,75,95,90,44,80,79,19) arrFaces(6) = Array(8,16,78,117) arrFaces(7) = Array(8,42,45,65,10,117) arrFaces(8) = Array(7,60,13,84) arrFaces(9) = Array(114,90,44,14,59,61) arrFaces(10) = Array(5,0,73,48,107,86,57,29,118,63) arrFaces(11) = Array(77,94,104,50,101,34,11,72,99,103) arrFaces(12) = Array(4,22,31,111) arrFaces(13) = Array(49,37,55,12,99,72) arrFaces(14) = Array(6,76,13,84,111,31,105,51,98,15) arrFaces(15) = Array(100,33,38,40) arrFaces(16) = Array(18,55,37,116) arrFaces(17) = Array(43,70,41,85) arrFaces(18) = Array(58,42,45,119) arrFaces(19) = Array(105,31,22,27,96,69) arrFaces(20) = Array(21,47,112,93) arrFaces(21) = Array(105,51,52,69) arrFaces(22) = Array(28,71,65,45,119,40,38,68,61,114) arrFaces(23) = Array(15,86,107,98) arrFaces(24) = Array(68,61,59,36) arrFaces(25) = Array(93,92,102,46,53,21) arrFaces(26) = Array(114,28,95,90) arrFaces(27) = Array(65,71,66,10) arrFaces(28) = Array(28,95,75,30,66,71) arrFaces(29) = Array(113,62,96,27) arrFaces(30) = Array(74,81,73,48) arrFaces(31) = Array(89,103,77,0,73,81) arrFaces(32) = Array(76,13,60,25,97,32) arrFaces(33) = Array(14,91,80,44) arrFaces(34) = Array(16,8,42,58,24,9,7,60,25,64) arrFaces(35) = Array(23,29,118,109,112,47) arrFaces(36) = Array(30,106,87,75) arrFaces(37) = Array(43,85,33,100,17,3,4,22,27,113) arrFaces(38) = Array(82,49,37,116,70,41,36,59,14,91) arrFaces(39) = Array(0,5,94,77) arrFaces(40) = Array(93,112,109,2,67,115,108,56,20,92) arrFaces(41) = Array(74,52,51,98,107,48) arrFaces(42) = Array(79,19,101,34) arrFaces(43) = Array(25,64,1,97) arrFaces(44) = Array(12,89,103,99) arrFaces(45) = Array(39,35,108,56) arrFaces(46) = Array(19,101,50,115,108,35) arrFaces(47) = Array(64,1,83,26,78,16) arrFaces(48) = Array(104,50,115,67) arrFaces(49) = Array(110,102,46,26,78,117,10,66,30,106) arrFaces(50) = Array(113,62,18,116,70,43) arrFaces(51) = Array(118,63,2,109) arrFaces(52) = Array(5,94,104,67,2,63) arrFaces(53) = Array(100,17,24,58,119,40) arrFaces(54) = Array(49,82,11,72) arrFaces(55) = Array(91,80,79,34,11,82) arrFaces(56) = Array(83,53,46,26) arrFaces(57) = Array(102,110,20,92) arrFaces(58) = Array(106,87,39,56,20,110) arrFaces(59) = Array(85,41,36,68,38,33) arrFaces(60) = Array(88,23,29,57) arrFaces(61) = Array(6,54,88,57,86,15) ' Create the solid CreateSolid arrVertices, arrFaces End Sub ''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' Define geometry for truncated tetrahedron ''''''''''''''''''''''''''''''''''''''''''''''''''''''' Sub TruncatedTetrahedron() ' Define the vertices Dim arrVertices(11) arrVertices(0) = Array(-0.347, 0.000,-0.735) arrVertices(1) = Array(-0.693,-0.347,-0.245) arrVertices(2) = Array( 0.347, 0.693, 0.245) arrVertices(3) = Array( 0.000, 0.347, 0.735) arrVertices(4) = Array( 0.693, 0.347,-0.245) arrVertices(5) = Array(-0.347,-0.693, 0.245) arrVertices(6) = Array( 0.347, 0.000,-0.735) arrVertices(7) = Array( 0.347,-0.693, 0.245) arrVertices(8) = Array( 0.693,-0.347,-0.245) arrVertices(9) = Array( 0.000,-0.347, 0.735) arrVertices(10) = Array(-0.347, 0.693, 0.245) arrVertices(11) = Array(-0.693, 0.347,-0.245) 'Define the faces Dim arrFaces(7) arrFaces(0) = Array(9,5,7) arrFaces(1) = Array(6,4,8) arrFaces(2) = Array(10,11,0,6,4,2) arrFaces(3) = Array(4,2,3,9,7,8) arrFaces(4) = Array(1,11,0) arrFaces(5) = Array(10,3,2) arrFaces(6) = Array(6,0,1,5,7,8) arrFaces(7) = Array(5,1,11,10,3,9) ' Create the solid CreateSolid arrVertices, arrFaces End Sub ''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' General purpose subroutine to create solids ''''''''''''''''''''''''''''''''''''''''''''''''''''''' Sub CreateSolid(arrVertices, arrFaces) Dim arrSurfaces() ReDim arrSurfaces(UBound(arrFaces)) Rhino.EnableRedraw False ' Create each face Dim i, j, arrFace, arrPoints(), strPolyline, arrPlanarSrf For i = 0 To UBound(arrFaces) arrFace = arrFaces(i) ReDim arrPoints(UBound(arrFace) + 1) For j = 0 To UBound(arrFace) arrPoints(j) = arrVertices(arrFace(j)) Next arrPoints(UBound(arrFace) + 1) = arrPoints(0) strPolyline = Rhino.AddPolyline(arrPoints) arrPlanarSrf = Rhino.AddPlanarSrf(Array(strPolyline)) arrSurfaces(i) = arrPlanarSrf(0) Rhino.DeleteObject strPolyline Next ' Join all of the faces Rhino.SelectObjects arrSurfaces Rhino.Command "_-Join" Rhino.UnselectAllObjects Rhino.EnableRedraw True End Sub ``` -------------------------------------------------------------------------------- # Plugin Loading Source: https://developer.rhino3d.com/en/guides/cpp/plugin-loading/ This guide discusses how Rhino loads C/C++ plugins. ## Loading Rhino plugins are loaded twice. The first time as follows: ```cpp hModule = ::LoadLibraryEx( lpFileName, 0, DONT_RESOLVE_DLL_REFERENCES | LOAD_WITH_ALTERED_SEARCH_PATH ); ``` By using the `DONT_RESOLVE_DLL_REFERENCES` flag, the system does not call the plugin's `DllMain` for process and thread initialization and termination. Also, the system does not load additional executable modules that are referenced by the specified module. This allows Rhino to quickly verify the Rhino SDK version and that the proper plugin exports are available. The `LOAD_WITH_ALTERED_SEARCH_PATH` flag is used so `LoadLibraryEx` looks for dependent DLLs in the directory specified by `lpFileName`, not by *Rhino.exe*. The plugin module is freed as follows: ```cpp ::FreeLibrary( hModule ); ``` If the above was successful, the plugin is loaded for a final time as follows: ```cpp hModule = ::LoadLibraryEx( lpFileName, 0, LOAD_WITH_ALTERED_SEARCH_PATH ); ``` Again, the altered search path flag is used. -------------------------------------------------------------------------------- # Plugin Search Order Source: https://developer.rhino3d.com/en/guides/cpp/plugin-search-order/ This guide discusses the order in which Rhino searches and loads plugins. ## Overview Rhino plugins are Windows Dynamic Link Libraries, or DLLs. As such, Rhino uses Windows to load your plugin. Rhino attempts to load your plugin, and any dependent DLLs, in the following manner: 1. Alternate Search Order - uses `LoadLibraryEx` with the `LOAD_WITH_ALTERED_SEARCH_PATH` flag. 1. Standard Search Order - uses `LoadLibrary`. **NOTE**: starting with Windows XP and later, the dynamic-link library (DLL) search order used by the system depends on the setting of the `HKLM\System\CurrentControlSet\Control\Session Manager\SafeDllSearchMode` value. For Windows Server 2003: The default value is 1. Windows XP: The default value is 0. ## Alternate Search Order The `LoadLibraryEx` function supports an alternate search order if the call specifies `LOAD_WITH_ALTERED_SEARCH_PATH` and the `lpFileName` parameter specifies a path. If `SafeDllSearchMode` is 1, the alternate search order is as follows: 1. The directory specified by `lpFileName`. 1. The system directory. Use the `GetSystemDirectory` function to get the path of this directory. 1. The 16-bit system directory. There is no function that obtains the path of this directory, but it is searched. 1. The Windows directory. Use the `GetWindowsDirectory` function to get the path of this directory. 1. The current directory. 1. The directories that are listed in the `PATH` environment variable. If `SafeDllSearchMode` is 0, the alternate search order is as follows: 1. The directory specified by `lpFileName`. 1. The current directory. 1. The system directory. Use the `GetSystemDirectory` function to get the path of this directory. 1. The 16-bit system directory. There is no function that obtains the path of this directory, but it is searched. 1. The Windows directory. Use the `GetWindowsDirectory` function to get the path of this directory. 1. The directories that are listed in the `PATH` environment variable. ## Standard Search Order If `SafeDllSearchMode` is 1, the search order is as follows: 1. The directory from which the application loaded. 1. The system directory. Use the `GetSystemDirectory` function to get the path of this directory. 1. The 16-bit system directory. There is no function that obtains the path of this directory, but it is searched. 1. The Windows directory. Use the `GetWindowsDirectory` function to get the path of this directory. 1. The current directory. 1. The directories that are listed in the `PATH` environment variable. If `SafeDllSearchMode` is 0, the search order is as follows: 1. The directory from which the application loaded. 1. The current directory. 1. The system directory. Use the `GetSystemDirectory` function to get the path of this directory. 1. The 16-bit system directory. There is no function that obtains the path of this directory, but it is searched. 1. The Windows directory. Use the `GetWindowsDirectory` function to get the path of this directory. 1. The directories that are listed in the `PATH` environment variable. Note, Windows 2000 does not support the `SafeDllSearchMode` value. T he search order for Windows 2000 is as follows: 1. The directory from which the application loaded. 1. The current directory. 1. The system directory. Use the `GetSystemDirectory` function to get the path of this directory. 1. The 16-bit system directory. There is no function that obtains the path of this directory, but it is searched. 1. The Windows directory. Use the `GetWindowsDirectory` function to get the path of this directory. 1. The directories that are listed in the `PATH` environment variable. ## Related Topics - [Plugin Loading](/guides/cpp/plugin-loading) -------------------------------------------------------------------------------- # Plugin User Data Source: https://developer.rhino3d.com/en/guides/rhinocommon/plugin-user-data/ This guide gives an overview of user data and how to use it with RhinoCommon. There are two basic ways plugins can store information in Rhino .3dm files: 1. [Document User Data](#document-user-data) 1. [Object User Data](#object-user-data) For example, a rendering plugin might save a scene descriptions as document user data and use object user data to attach rendering material information to individual surfaces. ## Document User Data To save document user data your plugin must override three PlugIn base class functions: ```cs PlugIn.ShouldCallWriteDocument PlugIn.WriteDocument PlugIn.ReadDocument ``` SDK references for these functions can be found [here](/api/RhinoCommon/html/Methods_T_Rhino_PlugIns_PlugIn.htm). When Rhino writes a .3dm file, it goes through all the plugins that are currently loaded. First Rhino calls `ShouldCallWriteDocument()` to see if the plugin wants to save document user data. If `ShouldWriteDocument()` returns true, Rhino saves information that identifies the plugin and then calls `WriteDocument()` when it is time for the plugin to save its “document” user data. When Rhino reads a .3dm file and it encounters document user data, it uses the plugin identification information to load the plugin and then calls the plugin's `ReadDocument()` to read the plugin's “document” user data. ## Object User Data Object user data can be attached to things like layers, materials, geometry objects, and object attributes. In fact object user data can be attached to any class derived from CommonObject. This user data is stored in a linked list on CommonObject and can be copied, transformed and saved along with the parent object. For example, you could attach object user data to a mesh. When the mesh is copied the object user data would be copied and attached to the copy. When the mesh is transformed, the transformation would be recored by the object user data. When the mesh is saved in a .3dm file, the object user data will be saved too. There are three forms of object user data: 1. User strings 1. UserDictionary 1. Custom UserData It is *recommended to typically use the User Strings or the UserDictionary on an object* since the data is automatically serialized for you and it is easily shared between plugins and scripts. In the case that you want to write your own private custom user data, you would derive a class from `Rhino.DocObjects.Custom.UserData`. Here's a sample: ```cs using System; using Rhino; using System.Runtime.InteropServices; namespace examples_cs { // You must define a Guid attribute for your user data derived class // in order to support serialization. Every custom user data class // needs a custom Guid [Guid("DAAA9791-01DB-4F5F-B89B-4AE46767C783")] public class PhysicalData : Rhino.DocObjects.Custom.UserData { public int Weight{ get; set; } public double Density {get; set;} // Your UserData class must have a public parameterless constructor public PhysicalData(){} public PhysicalData(int weight, double density) { Weight = weight; Density = density; } public override string Description { get { return "Physical Properties"; } } public override string ToString() { return String.Format("weight={0}, density={1}", Weight, Density); } protected override void OnDuplicate(Rhino.DocObjects.Custom.UserData source) { PhysicalData src = source as PhysicalData; if (src != null) { Weight = src.Weight; Density = src.Density; } } // return true if you have information to save public override bool ShouldWrite { get { if (Weight > 0 && Density > 0) return true; return false; } } protected override bool Read(Rhino.FileIO.BinaryArchiveReader archive) { Rhino.Collections.ArchivableDictionary dict = archive.ReadDictionary(); if (dict.ContainsKey("Weight") && dict.ContainsKey("Density")) { Weight = (int)dict["Weight"]; Density = (double)dict["Density"]; } return true; } protected override bool Write(Rhino.FileIO.BinaryArchiveWriter archive) { // you can implement File IO however you want... but the dictionary class makes // issues like versioning in the 3dm file a bit easier. If you didn't want to use // the dictionary for writing, your code would look something like. // // archive.Write3dmChunkVersion(1, 0); // archive.WriteInt(Weight); // archive.WriteDouble(Density); var dict = new Rhino.Collections.ArchivableDictionary(1, "Physical"); dict.Set("Weight", Weight); dict.Set("Density", Density); archive.WriteDictionary(dict); return true; } } [Guid("ca9a110e-3969-49ec-9d59-a7c2ee0b85bd")] public class ex_userdataCommand : Rhino.Commands.Command { public override string EnglishName { get { return "cs_userdataCommand"; } } protected override Rhino.Commands.Result RunCommand(RhinoDoc doc, Rhino.Commands.RunMode mode) { Rhino.DocObjects.ObjRef objref; var rc = Rhino.Input.RhinoGet.GetOneObject("Select Object", false, Rhino.DocObjects.ObjectType.AnyObject, out objref); if (rc != Rhino.Commands.Result.Success) return rc; // See if user data of my custom type is attached to the geomtry var ud = objref.Geometry().UserData.Find(typeof(PhysicalData)) as PhysicalData; if (ud == null) { // No user data found; create one and add it int weight = 0; rc = Rhino.Input.RhinoGet.GetInteger("Weight", false, ref weight); if (rc != Rhino.Commands.Result.Success) return rc; ud = new PhysicalData(weight, 12.34); objref.Geometry().UserData.Add(ud); } else { RhinoApp.WriteLine("{0} = {1}", ud.Description, ud); } return Rhino.Commands.Result.Success; } } } ``` ## Related topics - [Rhino.PlugIns.PlugIn Methods (API Reference)](/api/RhinoCommon/html/Methods_T_Rhino_PlugIns_PlugIn.htm) - [RhinoCommon object plugin user data](/samples/rhinocommon/user-data/) -------------------------------------------------------------------------------- # Polar Arrays Source: https://developer.rhino3d.com/en/samples/rhinoscript/polar-arrays/ Demonstrates how to create polar arrays of objects using RhinoScript. ```vbnet Option Explicit Sub MyArrayPolar Dim arrObjects, arrCenter, nCount Dim dAngle, arrAxis, arrXform, i arrObjects = Rhino.GetObjects("Select objects to array") If IsNull(arrObjects) Then Exit Sub arrCenter = Rhino.GetPoint("Center of polar array") If IsNull(arrCenter) Then Exit Sub nCount = Rhino.GetInteger("Number of items",,2) If IsNull(nCount) Then Exit Sub dAngle = 360.0 / nCount Rhino.EnableRedraw False For i = 1 To nCount - 1 arrAxis = Array(0,0,1) ' world z-axis arrXform = Rhino.XformRotation(dAngle * i, arrAxis, arrCenter) Rhino.TransformObjects arrObjects, arrXform, True Next Rhino.EnableRedraw True End Sub ``` -------------------------------------------------------------------------------- # Positioning Objects on a Surface Source: https://developer.rhino3d.com/en/samples/rhinoscript/positioning-objects-on-a-surface/ Demonstrates how to positioning objects on a surface using RhinoScript. ```vbnet ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' PositionOnSrf.rvb -- February 2009 ' If this code works, it was written by Dale Fugier. ' If not, I don't know who wrote it. ' Works with Rhino 4.0. ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Option Explicit Sub PositionOnSrf Dim objs, srf, box, p0, p1, pt, arr, i objs = Rhino.GetObjects("Select objects to position on surface") If IsNull(objs) Then Exit Sub srf = Rhino.GetObject("Select surface", 8) If IsNull(srf) Then Exit Sub Rhino.EnableRedraw False For i = 0 To UBound(objs) box = Rhino.BoundingBox(objs(i)) If IsArray(box) Then p0 = box(0) p1 = box(2) pt = Array((p1(0)+p0(0))/2,(p1(1)+p0(1))/2,(p1(2)+p0(2))/2) arr = Rhino.ProjectPointToSurface(pt, srf, Array(0,0,1)) If IsArray(arr) Then Rhino.MoveObject objs(i), pt, arr(0) End If End If Next Rhino.EnableRedraw True End Sub ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Rhino.AddStartupScript Rhino.LastLoadedScriptFile Rhino.AddAlias "PositionOnSrf", "_NoEcho _-RunScript (PositionOnSrf)" ``` -------------------------------------------------------------------------------- # Pre and Post-Pick Objects Source: https://developer.rhino3d.com/en/samples/rhinocommon/pre-and-post-pick-objects/ Demonstrates how to customize Rhino's pre and post picking of objects behavior. ```cs partial class Examples { public static Result PrePostPick(RhinoDoc doc) { var go = new Rhino.Input.Custom.GetObject(); go.SetCommandPrompt("Select objects"); go.EnablePreSelect(true, true); go.EnablePostSelect(true); go.GetMultiple(0, 0); if (go.CommandResult() != Result.Success) return go.CommandResult(); var selected_objects = go.Objects().ToList(); if (go.ObjectsWerePreselected) { go.EnablePreSelect(false, true); go.DeselectAllBeforePostSelect = false; go.EnableUnselectObjectsOnExit(false); go.GetMultiple(0, 0); if (go.CommandResult() == Result.Success) { foreach (var obj in go.Objects()) { selected_objects.Add(obj); // The normal behavior of commands is that when they finish, // objects that were pre-selected remain selected and objects // that were post-selected will not be selected. Because we // potentially could have both, we'll try to do something // consistent and make sure post-selected objects also stay selected obj.Object().Select(true); } } } return selected_objects.Count > 0 ? Result.Success : Result.Nothing; } } ``` ```python from Rhino import * from Rhino.Commands import * from Rhino.Input.Custom import * def RunCommand(): go = GetObject() go.SetCommandPrompt("Select objects") go.EnablePreSelect(True, True) go.EnablePostSelect(True) go.GetMultiple(0, 0) if go.CommandResult() != Result.Success: return go.CommandResult() selected_objects = [] for obj in go.Objects(): selected_objects.Add(obj) if go.ObjectsWerePreselected: go.EnablePreSelect(False, True) go.DeselectAllBeforePostSelect = False go.EnableUnselectObjectsOnExit(False) go.GetMultiple(0, 0) if go.CommandResult() == Result.Success: for obj in go.Objects(): selected_objects.Add(obj) # The normal behavior of commands is that when they finish, # objects that were pre-selected remain selected and objects # that were post-selected will not be selected. Because we # potentially could have both, we'll try to do something # consistent and make sure post-selected objects also stay selected obj.Object().Select(True) return Result.Success if selected_objects.Count > 0 else Result.Nothing if __name__ == "__main__": RunCommand() ``` -------------------------------------------------------------------------------- # Pre- and Post-Picking Objects Source: https://developer.rhino3d.com/en/guides/cpp/pre-and-post-picking-objects/ This brief guide demonstrates how to both pre-pick and post-pick objects using C/C++. ## Overview The normal operation for commands that manipulate geometric objects is to allow the user to either pre-pick or post-pick the objects. Occasionally, though, it might be necessary for commands to want to allow for both pre-picked and post-picked objects. That is, after it has been determined that objects were pre-picked, allow the user to continue to pos-pick more objects. ## Sample The following code sample demonstrates this capability... ```cpp CRhinoCommand::result CCommandTest::RunCommand( const CRhinoCommandContext& context ) { bool bObjectsWerePreSelected = false; bool bObjectsWerePostSelected = false; ON_SimpleArray obj_list; // Select some objects. Allow for pre-selected objects CRhinoGetObject go; go.SetCommandPrompt( L"Select objects" ); go.EnablePreSelect( TRUE ); go.GetObjects( 0, 0 ); if( go.CommandResult() != CRhinoCommand::success ) return go.CommandResult(); // Add the selected objects to our list int i; for( i = 0; i < go.ObjectCount(); i++ ) { const CRhinoObjRef& objref = go.Object(i); const CRhinoObject* obj = objref.Object(); if( obj ) obj_list.Append( obj ); } // Determine of the bjects were pre-selected // or post-selected. if( go.ObjectsWerePreSelected() ) bObjectsWerePreSelected = true; else bObjectsWerePostSelected = true; // If objects were pre-selected, then select // more objects. But, ignore pre-selected ones. if( bObjectsWerePreSelected ) { go.EnablePreSelect( FALSE ); go.EnableDeselectAllBeforePostSelect( FALSE ); go.GetObjects( 0, 0 ); if( go.CommandResult() != CRhinoCommand::success ) return go.CommandResult(); for( i = 0; i < go.ObjectCount(); i++ ) { const CRhinoObjRef& objref = go.Object(i); const CRhinoObject* obj = objref.Object(); if( obj ) obj_list.Append( obj ); } bObjectsWerePostSelected = true; } if( obj_list.Count() == 0 ) return CRhinoCommand::nothing; // // TODO: do something with the object list here // // The normal behavior of commands is that when they finish, // objects that were pre-selected remain selected and objects // that were post-selected will not be selected. Because we // potentially could have both, we'll try to do something // consistent. if( bObjectsWerePreSelected && bObjectsWerePreSelected ) { for( i = 0; i < obj_list.Count(); i++ ) { const CRhinoObject* obj = obj_list[i]; if( obj && obj->IsSelected() ) obj->Select( false ); } context.m_doc.Redraw(); } return CRhinoCommand::success; } ``` -------------------------------------------------------------------------------- # Print Instance Definition Names Source: https://developer.rhino3d.com/en/samples/cpp/print-instance-definition-names/ Demonstrates how to print the names of all instance definitions in the document. ```cpp CRhinoCommand::result CCommandTest::RunCommand( const CRhinoCommandContext& context ) { const CRhinoInstanceDefinitionTable& idef_table = context.m_doc.m_instance_definition_table; int idef_count = idef_table.InstanceDefinitionCount(); if (idef_count == 0) { RhinoApp().Print("No instance definitions found.\n"); return CRhinoCommand::nothing; } int num_printed = 0; for (int i = 0; i < idef_count; i++) { const CRhinoInstanceDefinition* idef = idef_table[i]; if (idef != 0 && idef->IsDeleted() == false) { ON_wString idef_name = idef->Name(); RhinoApp().Print(L"Instance definition %d = %s\n", num_printed, idef_name); num_printed += 1; } } return CRhinoCommand::success; } ``` -------------------------------------------------------------------------------- # Print Instance Definition Tree Source: https://developer.rhino3d.com/en/samples/cpp/print-instance-definition-tree/ Demonstrates how to print the names of all instance definitions, including objects and sub-instances, in a tree-style format. ```cpp CRhinoCommand::result CCommandTest::RunCommand( const CRhinoCommandContext& context ) { const CRhinoInstanceDefinitionTable& idef_table = context.m_doc.m_instance_definition_table; int idef_count = idef_table.InstanceDefinitionCount(); if (idef_count == 0) { RhinoApp().Print("No instance definitions found.\n"); return CRhinoCommand::nothing; } ON_wString writer; ON_TextLog dump( writer ); dump.SetIndentSize( 4 ); for (int i = 0; i < idef_count; i++) DumpInstanceDefinition( idef_table[i], dump, true ); RhinoApp().Print( L"%s\n", writer ); return CRhinoCommand::success; } void CCommandTest::DumpInstanceDefinition( const CRhinoInstanceDefinition* idef, ON_TextLog& dump, bool bRoot ) { if( idef && ! idef->IsDeleted() ) { ON_wString node; if( bRoot ) node = L"\u2500"; else node = L"\u2514"; dump.Print(L"%s Instance definition %d = %s\n", node, idef->Index(), idef->Name() ); const int idef_object_count = idef->ObjectCount(); if( idef_object_count ) { dump.PushIndent(); for( int i = 0; i < idef->ObjectCount(); i++ ) { const CRhinoObject* obj = idef->Object( i ); if( obj ) { const CRhinoInstanceObject* iref = CRhinoInstanceObject::Cast( obj ); if( iref ) DumpInstanceDefinition( iref->InstanceDefinition(), dump, false ); // Recursive... else dump.Print(L"\u2514 Object %d = %s\n", i, obj->ShortDescription(false) ); } } dump.PopIndent(); } } } ``` -------------------------------------------------------------------------------- # Print Surface Control Points Source: https://developer.rhino3d.com/en/samples/rhinoscript/print-surface-control-points/ Demonstrates how to print the location of a surface's control points using RhinoScript. ```vbnet Option Explicit Sub PrintSurfacePoints Dim strSurface strSurface = Rhino.GetObject("Select surface", 8) If IsNull(strSurface) Then Exit Sub Dim arrPoints arrPoints = Rhino.SurfacePoints(strSurface) If Not IsArray(arrPoints) Then Exit Sub Dim arrCount arrCount = Rhino.SurfacePointCount(strSurface) Dim u, v Dim ulast : ulast = arrCount(0) Dim vlast : vlast = arrCount(1) Dim i : i = 0 For u = 0 To ulast - 1 For v = 0 To vlast - 1 Rhino.Print "CV[" & CStr(u) & "," & CStr(v) & "] = " _ & Rhino.Pt2Str(arrPoints(i), 3) i = i + 1 Next Next End Sub ``` -------------------------------------------------------------------------------- # Printing a Layer's Full Path Source: https://developer.rhino3d.com/en/guides/cpp/printing-layer-full-path/ This brief guide demonstrates now to obtain a layer's full path using C/C++. ## Problem You could like to print a layer's full path. That is, if a layer "MyLayer"”" is nested, I would like to print out the nesting like this: `"GreatGrandParent / GrandParent / Parent / MyLayer"` ## Solution The following sample function ought to do the trick: ```cpp static ON_wString RhinoFullLayerPath( CRhinoDoc& doc, const CRhinoLayer& layer ) { ON_wString layer_path; CRhinoLayerNode layer_node; layer_node.Create(layer.m_layer_index, 2, 0, true ); if( layer_node.m_parent_count > 0 ) { int i, layer_index = -1; for( i = layer_node.m_parent_count - 1; i >= 0; i-- ) { layer_index = layer_node.m_parent_list[i]; layer_path += doc.m_layer_table[layer_index].LayerName(); layer_path += L" / "; } } layer_path += layer.LayerName(); return layer_path; } ``` You can use the function like this... ```cpp CRhinoCommand::result CCommandTest::RunCommand( const CRhinoCommandContext& context ) { ON_wString s = RhinoFullLayerPath( context.m_doc, context.m_doc.m_layer_table.CurrentLayer() ); RhinoApp().Print( L"%s\n", s.Array() ); return CRhinoCommand::success; ``` -------------------------------------------------------------------------------- # Project Curves onto Breps Source: https://developer.rhino3d.com/en/guides/cpp/project-curves-onto-breps/ This guide demonstrates how to project a curve onto a brep using C/C++. ## Problem You want to project curves onto a brep, but you do not function any C/C++ function to do this. Is there a solution for this? ## Solution It is true that the Rhino C/C++ SDK does not currently have a function that will project a curve onto a brep. But, using some of the existing functions, you can write your own function without too much effort. To project a curve onto a brep, you need to do the following: 1. Extrude the curve through the brep using the `RhinoExtrudeCurveStraight()` function. 1. Intersect the two breps using `RhinoIntersectBreps()` function. 1. The results of the brep intersection will be the projected curves. ## Sample The following sample code demonstrates how one might write such a function... ```cpp /* Description: Projects a curve onto a surface or polysurface Parameters: brep - [in] The brep to project the curve onto. curve - [in] The curve to project. dir - [in] The direction of the projection. tol - [in] The intersection tolerance. output_curves - [out] The output curves. NOTE, the caller is responsible for destroying these curves. Returns: true if successful. false if unsuccessful. */ bool ProjectCurveToBrep( const ON_Brep& brep, const ON_Curve& curve, const ON_3dVector& dir, double tolerance, ON_SimpleArray& output_curves ) { ON_3dVector n = dir; if( !n.Unitize() ) return false; ON_BoundingBox bbox = brep.BoundingBox(); bbox.Union( curve.BoundingBox() ); ON_Surface* pExtrusion = RhinoExtrudeCurveStraight( &curve, dir, bbox.Diagonal().Length() ); if( 0 == pExtrusion ) return false; ON_Brep* pBrep = ON_Brep::New(); pBrep->Create( pExtrusion ); BOOL rc = RhinoIntersectBreps( *pBrep, brep, tolerance, output_curves ); delete pBrep; // Don't leak... return ( rc ) ? true : false; } ``` -------------------------------------------------------------------------------- # Project Points to Breps Source: https://developer.rhino3d.com/en/samples/rhinocommon/project-points-to-breps/ Demonstrates how to project points to Brep objects. ```cs partial class Examples { public static Result ProjectPointsToBreps(RhinoDoc doc) { var gs = new GetObject(); gs.SetCommandPrompt("select surface"); gs.GeometryFilter = ObjectType.Surface | ObjectType.PolysrfFilter; gs.DisablePreSelect(); gs.SubObjectSelect = false; gs.Get(); if (gs.CommandResult() != Result.Success) return gs.CommandResult(); var brep = gs.Object(0).Brep(); if (brep == null) return Result.Failure; var points = Intersection.ProjectPointsToBreps( new List {brep}, // brep on which to project new List {new Point3d(0, 0, 0), new Point3d(3,0,3), new Point3d(-2,0,-2)}, // some random points to project new Vector3d(0, 1, 0), // project on Y axis doc.ModelAbsoluteTolerance); if (points != null && points.Length > 0) { foreach (var point in points) { doc.Objects.AddPoint(point); } } doc.Views.Redraw(); return Result.Success; } } ``` ```python import rhinoscriptsyntax as rs from scriptcontext import * from Rhino.Geometry import * def RunCommand(): srfid = rs.GetObject("select surface", rs.filter.surface | rs.filter.polysurface) if not srfid: return brep = rs.coercebrep(srfid) if not brep: return pts = Intersect.Intersection.ProjectPointsToBreps( [brep], # brep on which to project [Point3d(0, 0, 0), Point3d(3,0,3), Point3d(-2,0,-2)], # points to project Vector3d(0, 1, 0), # project on Y axis doc.ModelAbsoluteTolerance) if pts != None and pts.Length > 0: for pt in pts: doc.Objects.AddPoint(pt) doc.Views.Redraw() if __name__ == "__main__": RunCommand() ``` -------------------------------------------------------------------------------- # Project Points to Mesh Source: https://developer.rhino3d.com/en/samples/rhinocommon/project-points-to-mesh/ Demonstrates how to project points to a mesh. ```cs partial class Examples { public static Result ProjectPointsToMeshesEx(RhinoDoc doc) { ObjRef obj_ref; var rc = RhinoGet.GetOneObject("mesh", false, ObjectType.Mesh, out obj_ref); if (rc != Result.Success) return rc; var mesh = obj_ref.Mesh(); ObjRef[] obj_ref_pts; rc = RhinoGet.GetMultipleObjects("points", false, ObjectType.Point, out obj_ref_pts); if (rc != Result.Success) return rc; var points = new List(); foreach (var obj_ref_pt in obj_ref_pts) { var pt = obj_ref_pt.Point().Location; points.Add(pt); } int[] indices; var prj_points = Intersection.ProjectPointsToMeshesEx(new[] {mesh}, points, new Vector3d(0, 1, 0), 0, out indices); foreach (var prj_pt in prj_points) doc.Objects.AddPoint(prj_pt); doc.Views.Redraw(); return Result.Success; } } ``` ```python from System.Collections.Generic import * from Rhino import * from Rhino.Commands import * from Rhino.Geometry import * from Rhino.Geometry.Intersect import * from Rhino.Input import * from Rhino.DocObjects import * from scriptcontext import doc def RunCommand(): rc, obj_ref = RhinoGet.GetOneObject("mesh", False, ObjectType.Mesh) if rc != Result.Success: return rc mesh = obj_ref.Mesh() rc, obj_ref_pts = RhinoGet.GetMultipleObjects("points", False, ObjectType.Point) if rc != Result.Success: return rc points = [] for obj_ref_pt in obj_ref_pts: pt = obj_ref_pt.Point().Location points.append(pt) prj_points, indices = Intersection.ProjectPointsToMeshesEx({mesh}, points, Vector3d(0, 1, 0), 0) for prj_pt in prj_points: doc.Objects.AddPoint(prj_pt) doc.Views.Redraw() return Result.Success if __name__ == "__main__": RunCommand() ``` -------------------------------------------------------------------------------- # Projecting Points to Breps Source: https://developer.rhino3d.com/en/guides/cpp/projecting-points-to-breps/ This brief guide demonstrates how to project points onto Brep objects using C/C++. ## Problem You would like to project a 2D point (x,y) onto a brep object in order to acquire the z-coordinate. ## Solution Use the `RhinoProjectPointsToBreps` function. In order to use this function you will need to provide the following: 1. An array of one or more Brep objects. 1. An array of one or more points to project. 1. A projection direction (vector). ## Sample The following sample code demonstrates how you can use this function... ```cpp CRhinoCommand::result CCommandFoobarCpp::RunCommand( const CRhinoCommandContext& context ) { CRhinoGetObject go; go.SetCommandPrompt(L"Select surface or polysurface"); go.SetGeometryFilter( CRhinoGetObject::surface_object | CRhinoGetObject::polysrf_object ); go.GetObjects(1, 1); if (go.CommandResult() != CRhinoCommand::success) return go.CommandResult(); const ON_Brep* brep = go.Object(0).Brep(); if (0 == brep) return CRhinoCommand::failure; ON_3dPoint point(0.0, 0.0, 0.0); // some point on the world x-y plane // Prepare input to RhinoProjectPointsToBreps ON_SimpleArray Breps; Breps.Append(brep); ON_3dPointArray Points; Points.Append(point); ON_3dVector ProjDir(0.0, 0.0, 1.0); // world z-axis ON_3dPointArray OutPoints; ON_SimpleArray Indices; bool rc = RhinoProjectPointsToBreps( Breps, Points, ProjDir, OutPoints, Indices, context.m_doc.AbsoluteTolerance() ); if (rc == true) { for (int i = 0; i < OutPoints.Count(); i++) { ON_3dPoint pt = OutPoints[i]; context.m_doc.AddPointObject(pt); } context.m_doc.Redraw(); } return CRhinoCommand::success; } ``` -------------------------------------------------------------------------------- # Pull Curve to Surface Source: https://developer.rhino3d.com/en/samples/cpp/pull-curve-to-surface/ Demonstrates how to use ON_Surface::Pullback() to pull a curve object to a surface object. ```cpp CRhinoCommand::result CCommandTestSdk::RunCommand( const CRhinoCommandContext& context ) { CRhinoGetObject gc; gc.SetCommandPrompt( L"Select a curve on a surface" ); gc.SetGeometryFilter( CRhinoGetObject::curve_object ); gc.GetObjects( 1, 1 ); if( gc.CommandResult() != CRhinoCommand::success ) return gc.CommandResult(); const ON_Curve* pC3d = gc.Object(0).Curve(); if( !pC3d ) return CRhinoCommand::failure; CRhinoGetObject gs; gs.SetCommandPrompt(L"Select the surface" ); gs.EnableHighlight(); gs.SetGeometryFilter( CRhinoGetObject::surface_object ); gs.EnableDeselectAllBeforePostSelect( false ); gs.EnablePreSelect( FALSE ); gs.GetObjects( 1, 1 ); if( gs.CommandResult() != CRhinoCommand::success ) return gs.CommandResult(); const ON_Surface* pS = gs.Object(0).Surface(); if( !pS ) return CRhinoCommand::failure; ON_Curve* pC2d = pS->Pullback( *pC3d, context.m_doc.AbsoluteTolerance() ); if( !pC2d ) { RhinoApp().Print( L"Unable to pull curve to surface.\n" ); return CRhinoCommand::failure; } // At this point we now have a 2D curve to do with what we want. // In this case, we will just add it to the document. pC2d->ChangeDimension(3); if( pC2d->IsValid() ) { CRhinoCurveObject* crv_obj = new CRhinoCurveObject(); crv_obj->SetCurve( pC2d ); if( !context.m_doc.AddObject(crv_obj) ) { delete crv_obj; return CRhinoCommand::failure; } } context.m_doc.Redraw(); return CRhinoCommand::success; } ``` -------------------------------------------------------------------------------- # Python Basic Syntax Source: https://developer.rhino3d.com/en/guides/rhinopython/python-statements/ This guide presents an overview of Python syntax. ## Overview Many scripting and programming languages, such as JScript, C#, and C++, make no attempt to match the code that is run with the actual physical lines typed into the text editor. This is because they not recognize the end of a line of code until it sees the termination character (in these cases, the semicolon). Thus, the actual physical lines of type taken up by the code are irrelevant. Unlike other languages, Python does not use an end of line character. Most of the time a simple Enter will do. Yet, Python is very particular about indentation, spaces and lines in certain cases. This document is to help understand Python formatting. It is important to understand how Python interprets: 2. End of Statement 1. Names and Capitalization 2. Comments 3. Block Structures 4. Tabs and Spaces For the official very detailed documentation on Python Syntax, see the [Style Guide for Python Code](https://www.python.org/dev/peps/pep-0008/) ## End of Statements To end a statement in Python, you do not have to type in a semicolon or other special character; you simply press Enter. For example, this code will generate a syntax error: ```python message = 'Hello World!' ``` This will not: ```python message = 'Hello World!' ``` In general, the lack of a required statement termination character simplifies script writing in Python. There is, however, one complication: To enhance readability, it is recommended that you limit the length of any single line of code to 79 characters. What happens, then, if you have a line of code that contains 100 characters? Although it might seem like the obvious solution, you cannot split a statement into multiple lines simply by entering a carriage return. For example, the following code snippet returns a run-time error in Python because a statement was split by using Enter. ```python message= 'This message will generate an error because it was split by using the enter button on your keyboard' ``` You cannot split a statement into multiple lines in Python by pressing Enter. Instead, use the backslash (`\`) to indicate that a statement is continued on the next line. In the revised version of the script, a blank space and an underscore indicate that the statement that was started on line 1 is continued on line 2. To make it more apparent that line 2 is a continuation of line 1, line 2 is also indented four spaces. (This was done for the sake of readability, but you do not have to indent continued lines. ) ```python message\ =\ 'This \ back slash \ acts \ like \ enter' print\ message ``` ```python message\ =\ '''triple quotes will span multiple lines without errors''' print\ message ``` Line continuation is automatic when the split comes while a statement is inside parenthesis ( ( ), brackets ( [ ) or braces ( { ). This is convenient, but can also lead to errors if there is no closing Parenthesis, bracket or brace. Python would interpret the rest of the script as one statement in that case. Python uses single quotes (') double quotes (") and triple quotes (""") to denote literal strings. Only the triple quoted strings (""") also will automatically continue across the end of line statement. Sometimes, more than one statement may be put on a single line. In Python a semicolon (;) can be used to separate multiple statements on the same line. For instance three statements can be written: ```python y = 3; x = 5; print(x+y) ``` To the Python interpreter, this would be the same set of statements: ```python y = 3 x = 5 print(x+y) ``` -------------------------------------------------------------------------------- # Python Scripting in Rhino Source: https://developer.rhino3d.com/en/guides/rhinopython/python-script-introduction/ This guide provides an overview of the scripting with Python in Rhino. ## Introduction The Python language is a comprehensive programming language for both Windows, Macintosh and Grasshopper platforms. Python is meant to be a easy-to-learn language. Python is a great language for people that are not programmers and whom are just learning. A key characteristic of python is that there is very few extra characters allowing the code to be shorter and potentially easier to read. For more information on the history and development of Python, see [Python (programming language) on Wikipedia](https://en.wikipedia.org/wiki/Python_(programming_language)). Python is [open source](https://opensource.com/resources/what-open-source) and managed by the [Python Software Foundation](https://www.python.org/psf/) We have added additional libraries to Python in the form of RhinoScriptSyntax and RhinoCommon. These libraries allow Python to be aware of Rhino and Grasshopper: * Geometry * Commands * Document objects * Application methods Rhinoscript syntax is the easiest to use and learn library. To make these methods easy-to-use, all RhinoScriptSyntax methods return simple Python variables or Python List-based data structures. Thus, once you are familiar with Python, you will be able to use any and all functions in the RhinoScriptSyntax methods library. RhinoCommon is an extensive, low level .NET library of the Rhino SDK. It is meant for more experienced programmers that would like to most extensive access to Rhino and its classes. This document introduces the built-in Script editor in Rhino and using Python with RhinoSciptSyntax to automate Rhino. ## Getting help The information in this help file pertains to the usage of the RhinoScriptSyntax libraries. Additional support for Python in Rhino can be obtained from the following resources. [McNeel Developer Documentation](/guides/rhinopython) [Rhino Scripting Forum](https://discourse.mcneel.com/c/scripting) --- -------------------------------------------------------------------------------- # Quadratic Solver Source: https://developer.rhino3d.com/en/guides/rhinoscript/quadratic-solver/ This brief guide demonstrates how to solve quadratic equations in RhinoScript. ## Problem If you are trying to solve quadratic equations like: $${-b \pm \sqrt{b^2 - 4ac} \over 2a }$$ the results may seem incorrect at times. ## Solution Most likely, the problem that is that there are floating point rounding errors. Being that you only get 15 decimal places of accuracy, you can use them all up if you are dealing with small numbers. The following algorithm should produce more accurate results: ```vbnet Function QuadraticSolver(a, b, c) Dim d, s0, s1 d = b * b - 4 * a * c If d < 0 Then ' No real solution QuadraticSolver = Null Else s0 = (-b - Sqr(d)) / (2 * a) s1 = (-b + Sqr(d)) / (2 * a) If Abs(s0) < Abs(s1) Then s0 = s1 s1 = c / (a * s0) QuadraticSolver = Array(s0,s1) End If End Function ``` -------------------------------------------------------------------------------- # Query Flamingo Plant Objects Source: https://developer.rhino3d.com/en/samples/rhinoscript/query-flamingo-plant-object/ Demonstrates how query a plant object using RhinoScript. ```vbnet Option Explicit Call Main() Sub Main() Dim objPlugIn, strObject, plant On Error Resume Next Set objPlugIn = Rhino.GetPluginObject("8008880f-8d13-4b2d-92b0-727e12878a4c") If Err Then MsgBox Err.Description Exit Sub End If strObject = Rhino.GetObject("Select object") If Not IsNull(strObject) And Not strObject = "" Then Set plant = objPlugIn.PlantFromObject(strObject) If IsNull(plant) Then Rhino.Print("Object is not a plant") Else Rhino.Print("Plant assignment: " & plant.FileName & " XML: " & plant.Xml) End If Set plant = Nothing End If Set objPlugIn = Nothing End Sub ``` -------------------------------------------------------------------------------- # Query Plant Wireframe Source: https://developer.rhino3d.com/en/samples/rhinoscript/query-plant-wireframe/ Demonstrates how to generate a wire-frame representation of a Flamingo 2.0 plant using RhinoScript. ```vbnet Option Explicit Call Main() Sub Main() Dim objPlugIn, plant, lines, line, i, count On Error Resume Next Set objPlugIn = Rhino.GetPluginObject("8008880f-8d13-4b2d-92b0-727e12878a4c") If Err Then MsgBox Err.Description Exit Sub End If plant = objPlugIn.ModalFlamingo2PlantBrowser("", "", "") If Not IsNull(plant) Then lines = objPlugIn.GetFlamingo2PlantWireframe(plant(0), plant(1), plant(2)) If Not IsNull(lines) Then count = UBound(lines) If count > 0 Then For i = 0 to count Rhino.Print("Line " & (i + 1) & " of " & (count + 1)) Rhino.AddLine Array(lines(i,0), lines(i,1), lines(i,2)), Array(lines(i,3), lines(i,4), lines(i,5)) Next Rhino.Redraw() End If End If End If Set objPlugIn = Nothing End Sub ``` -------------------------------------------------------------------------------- # Quick Sort Key Value Pairs Source: https://developer.rhino3d.com/en/guides/rhinoscript/quick-sort-key-value-pair/ This guide demonstrates how to sort an array of key-value pairs in RhinoScript. ## Overview The .NET Framework's `SortedList` class provides a hash table with automatically sorted key-value pairs. The available methods and properties for `SortedList` are very similar to the ones available in [ArrayList](/guides/rhinoscript/sorting-vbs-arrays-with-net). The following sample code creates a `SortedList` and populates it with some key-value pairs: ```vbnet Set SortedList = CreateObject("System.Collections.Sortedlist") SortedList.Add "First", "Hello" SortedList.Add "Second", "," SortedList.Add "Third", "Rhino" SortedList.Add "Fourth", "!" For i = 0 To SortedList.Count - 1 Rhino.Print SortedList.GetKey(i) & vbTab & SortedList.GetByIndex(i) Next ``` **NOTE**: `SortedList` only sorts the list by keys. It is not possible to sort the list by values. ## Quick Sort VBScript and RhinoScript do not expose procedures for sorting multiple arrays. The .NET framework does, but only a single Key-Value pair. The algorithm outlined on below can be easily extended to work for any number of value arrays. It is a standard implementation of the [QuickSort](http://en.wikipedia.org/wiki/Quicksort) algorithm. QuickSort works through a [Divide and Conquer](http://en.wikipedia.org/wiki/Divide_and_conquer_algorithm) approach and it's one of the fastest sorting algorithms available. However, this implementation uses the recursive approach which may result in stack overflow errors on large datasets. QuickSort works best on randomized arrays, if the array is already almost sorted the solution will take more steps. This implementation comes as a collection of three procedures, but it can be easily packaged into one. First, the big one. This is the actual recursive algorithm: ```vbnet Sub QuickSort(ByRef A(), ByRef B(), ByVal min, ByVal max) Dim i : i = min Dim k : k = max If (max - min) >= 1 Then Dim pivot : pivot = A(min) While (k > i) While (A(i) <= pivot And i <= max And k > i) i = i+1 Wend While (A(k) > pivot And k >= min And k >= i) k = k-1 Wend If (k > i) Then Call SwapElements(A, B, i, k) Wend Call SwapElements(A, B, min, k) Call QuickSort(A, B, min, k-1) Call QuickSort(A, B, k+1, max) End If End Sub ``` It depends on the `SwapElement()` subroutine, which could have been written inline, but since it is called twice, it is placed in a a separate subroutine: ```vbnet Sub SwapElements(ByRef A(), ByRef B(), ByVal i0, ByVal i1) Dim loc_A : loc_A = A(i0) Dim loc_B : loc_B = B(i0) A(i0) = A(i1) B(i0) = B(i1) A(i1) = loc_A B(i1) = loc_B End Sub ``` Finally, there's a wrapper procedure that makes calling this function slightly easier: ```vbnet Function SortByKey(ByRef Keys(), ByRef Values()) Call QuickSort(Keys, Values, 0, Ubound(Keys)) End Function ``` ## Related Topics - [Sorting VBS Arrays with .NET](/guides/rhinoscript/sorting-vbs-arrays-with-net) - [Quicksort on Wikipedia](http://en.wikipedia.org/wiki/Quicksort) - [Divide and conquer algorithms on Wikipedia](https://en.wikipedia.org/wiki/Divide_and_conquer_algorithms) -------------------------------------------------------------------------------- # Random Surface Points Source: https://developer.rhino3d.com/en/samples/rhinoscript/random-surface-points/ Generate random points on a surface using RhinoScript. ```vbnet Sub RandomSurfacePoints() Dim srf, num, cnt Dim dom_u, dom_v, uv(1), pt srf = Rhino.GetObject("Select surface", 8, True, True) If IsNull(srf) Then Exit Sub num = Rhino.GetInteger("Number of points", 10, 1) If IsNull(num) Then Exit Sub Call Rhino.EnableRedraw(False) Randomize dom_u = Rhino.SurfaceDomain(srf, 0) dom_v = Rhino.SurfaceDomain(srf, 1) cnt = 0 While (cnt < num) uv(0) = Rnd() * (dom_u(1) - dom_u(0)) + dom_u(0) uv(1) = Rnd() * (dom_v(1) - dom_v(0)) + dom_v(0) pt = Rhino.EvaluateSurface(srf, uv) If (Rhino.IsPointOnSurface(srf, pt) = True) Then Call Rhino.AddPoint(pt) cnt = cnt + 1 End If Wend Call Rhino.EnableRedraw(True) End Sub ``` -------------------------------------------------------------------------------- # RDK Automatic UI Source: https://developer.rhino3d.com/en/guides/cpp/rdk-raw-auto-ui/ This document describes how to use the RDK's automatic UI classes in C/C++.

UNDER CONSTRUCTION

This guide has yet to be written. Please check back soon for updates. -------------------------------------------------------------------------------- # RDK Current Environment Source: https://developer.rhino3d.com/en/guides/cpp/rdk-current-environment-classes/ This document describes how to use the RDK current environment class in C/C++. ### Introduction The _Current Environment_ is a document-resident environment which has been selected for use during rendering. There are currently three such environments which are identified by _usage_. These are: * 360 backdrop environment. This is the environment that appears to surround the scene. * Custom reflective environment. This is an environment that can be used to override the environment that appears in reflections. If this is not enabled, the backdrop environment is used. * Custom skylight environment. This is an environment that can be used to override the environment used for skylighting. If this is not enabled, the backdrop environment is used. When accessing these environments using the SDK, you sometimes need to specify the _purpose_ of the access. These purposes are: * _Simple_. This is used to directly get and set the environment instance id. * _Render_. This is used by renderers. It uses special logic which defers to other environments when one is not available. ![Environment](https://developer.rhino3d.com/images/rdk-environment.jpg) Photo by John Croudy. ### The Document Current Environment The _RDK Document Current Environment_ is actually a set of _three_ document-resident current environments which affect renderings and the viewports. If you have a Rhino document, you can read and write that document's current environments through the document's [IRhRdkCurrentEnvironment](/api/cpp/class_i_rh_rdk_current_environment.html) interface. Any changes you make will appear in the Rendering UI and will also be stored in the 3dm file. Getting a current environment from a document always returns a const reference. To write to the current environment, you must begin a batch of write operations and afterwards end the batch. This is done using the RDK's standard BeginChange / EndChange system. The following is an example of how to access and change the document's current 'custom reflective' environment: ```cpp static class CCurrentEnvironmentExampleCommand : public CRhinoTestCommand { protected: virtual UUID CommandUUID() override { static const UUID uuid = your_uuid_here; return uuid; } virtual const wchar_t* EnglishCommandName() override { return RHSTR_LIT(L"MyCurrentEnvCmd"); } virtual CRhinoCommand::result RunCommand(const CRhinoCommandContext& context) override; } theCurrentEnvironmentExampleCommand; CRhinoCommand::result CCurrentEnvironmentExampleCommand::RunCommand(const CRhinoCommandContext& context) { auto* pDoc = context.Document(); if (nullptr == pDoc) return failure; // We will test using the custom reflective environment. const auto usage = IRhRdkCurrentEnvironment::Usage::Reflection; const auto purpose = IRhRdkCurrentEnvironment::Purpose::Simple; const auto& ce = pDoc->CurrentEnvironment(); ON_wString sUuid; auto uuid = ce.Get(usage, purpose); ON_UuidToString(uuid, sUuid); const auto* wszUuid = static_cast(sUuid); RhinoApp().Print(L"Custom refl. env before: %s [%s]\n", ce.On(usage) ? L"on" : L"off", wszUuid); // Create a new basic environment. auto* pEnvironment = ::RhRdkNewBasicEnvironment(pDoc); if (nullptr != pEnvironment) { // Attach it to the document. auto& write_contents = pDoc->Contents().BeginChange(RhRdkChangeContext::Program); write_contents.Attach(*pEnvironment); write_contents.EndChange(); // Turn on the environment and set it as the new basic environment. auto& write_ce = ce.BeginChange(RhRdkChangeContext::Program); write_ce.SetOn(usage, true); write_ce.Set(usage, pEnvironment->InstanceId()); write_ce.EndChange(); } uuid = ce.Get(usage, purpose); ON_UuidToString(uuid, sUuid); wszUuid = static_cast(sUuid); RhinoApp().Print(L"Custom refl. env after: %s [%s]\n", ce.On(usage) ? L"on" : L"off", wszUuid); return success; } ``` -------------------------------------------------------------------------------- # RDK Decals Source: https://developer.rhino3d.com/en/guides/cpp/rdk-decal-classes/ This document describes how to use the RDK decal classes in C/C++. ### IRhRdkDecal
_IRhRdkDecal_ is an abstract decal interface. It provides access to all the properties of a Decal. ### CRhRdkObjectDataAccess _CRhRdkObjectDataAccess_ is a wrapper around an object or layer which makes it easy to get RDK-specific information from that object or layer. When the wrapped object is a Rhino object, it can be used for accessing the object's decals using the following methods: * `AddDecal()` Adds a new decal to the object. * `RemoveDecal()` Remove a particular decal from the object. * `RemoveAllDecals()` Removes all decals from the object. * `NewDecalIterator()` Obtains an iterator for accessing the object's decals. A particularly important property of decals is the _decal CRC_. This value identifies a decal by its state. Multiple decals which would be exactly the same would have the same CRC and are culled from the system. If you store this value with the intention of using it to find the decal again later, you must update your stored value whenever the decal state changes. The following is an example of how to access decals already on an object. It uses NewDecalIterator() to get IRhRdkDecal and lists decal information on every object in the document. To keep the example short it does not list _every_ piece of information available from IRhRdkDecal but should be enough to get you started: ```cpp using OI = CRhinoObjectIterator; int objCount = 1; CRhinoObjectIterator it(*pDoc, OI::undeleted_objects, OI::active_and_reference_objects); const auto* pObject = it.First(); while (nullptr != pObject) { CRhRdkObjectDataAccess da(pObject); auto* pDI = da.NewDecalIterator(); if (nullptr != pDI) { const auto* pDecal = pDI->NextDecal(); while (nullptr != pDecal) { const auto m = pDecal->Mapping(); const wchar_t* wszMapping = L""; switch (m) { case IRhRdkDecal::mapUV: wszMapping = L"UV"; break; case IRhRdkDecal::mapPlanar: wszMapping = L"Planar"; break; case IRhRdkDecal::mapCylindrical: wszMapping = L"Cylindrical"; break; case IRhRdkDecal::mapSpherical: wszMapping = L"Spherical"; break; } const auto pt = pDecal->Origin(); RhinoApp().Print(L"Object %u: Decal %08X, mapping: %s, origin: (%.1f, %.1f, %.1f)", objCount, pDecal->CRC(), wszMapping, pt.x, pt.y, pt.z); if ((IRhRdkDecal::mapSpherical == m) || (IRhRdkDecal::mapCylindrical == m)) { RhinoApp().Print(L", Radius: %.1f", pDecal->Radius()); if (IRhRdkDecal::mapCylindrical == m) { RhinoApp().Print(L", Height: %.1f", pDecal->Height()); } } RhinoApp().Print(L"\n"); pDecal = pDI->NextDecal(); } delete pDI; } pObject = it.Next(); objCount++; } ``` -------------------------------------------------------------------------------- # RDK Dithering Source: https://developer.rhino3d.com/en/guides/cpp/rdk-dithering-classes/ This document describes how to use the RDK dithering class in C/C++. ### Introduction Image _dithering_ is a process in which some form of noise is added to an image in order to reduce color banding and other artifacts. This is commonly used when a display device is unable to display the full range of colors. The image below shows an extreme example. A photo was converted to use only two colors. On the left, no dithering was used and a lot of detail has been lost. On the right, Floyd-Steinberg dithering was used and the amount of detail is much improved. Although modern displays don't usually require any dithering, there are cases where it can make a subtle difference to the final quality of an image. For this reason, the RDK provides two kinds of dithering, _Simple Noise_ and _Floyd Steinberg_. ![Dithering](https://developer.rhino3d.com/images/rdk-dithering.png) Photo by John Croudy. ### The Document Dithering Settings The _RDK Document Dithering settings_ is a document-resident dithering object which affects renderings. If you have a Rhino document, you can read and write that document's dithering settings through the document's [IRhRdkDithering](/api/cpp/class_i_rh_rdk_dithering.html) interface. Any changes you make will appear in the Dithering and Color Adjustment section of the Rendering panel and will also be stored in the 3dm file. Getting the dithering settings from a document always returns a const reference. To write to the settings, you must begin a batch of write operations and afterwards end the batch. This is done using the RDK's standard BeginChange / EndChange system. The following is an example of how to access and change the document dithering settings: ```cpp static class CDitheringExampleCommand : public CRhinoTestCommand { protected: virtual UUID CommandUUID() override { static const UUID uuid = your_uuid_here; return uuid; } virtual const wchar_t* EnglishCommandName() override { return RHSTR_LIT(L"DitheringExample"); } virtual CRhinoCommand::result RunCommand(const CRhinoCommandContext& context) override; } theDitheringExampleCommand; using DM = IRhRdkDithering::Methods; static const wchar_t* StringFromDitheringMethod(DM dm) { switch (dm) { case DM::FloydSteinberg: return L"Floyd-Steinberg"; break; case DM::SimpleNoise: return L"Simple noise"; break; } return L"None"; } CRhinoCommand::result CDitheringExampleCommand::RunCommand(const CRhinoCommandContext& context) { auto* pDoc = context.Document(); if (nullptr == pDoc) return failure; const auto& dither = pDoc->Dithering(); RhinoApp().Print(L"Dithering before: %s\n", StringFromDitheringMethod(dither.Method())); auto& write_dither = dither.BeginChange(RhRdkChangeContext::Program); write_dither.SetMethod(DM::FloydSteinberg); write_dither.EndChange(); RhinoApp().Print(L"Dithering after: %s\n", StringFromDitheringMethod(dither.Method())); return success; } ``` -------------------------------------------------------------------------------- # RDK Document Contents Source: https://developer.rhino3d.com/en/guides/cpp/rdk-contents-classes/ This document describes how to use the RDK document contents class in C/C++. The _RDK Document Contents_ is an object that enables the plug-in developer to perform certain operations on document-resident [Render Contents](/guides/cpp/rdk-render-content/) (AKA 'Contents'). It is particularly useful for attaching contents to a document and finding contents by their instance ids. If you have a Rhino document, you can read and modify that document's contents through the document's [IRhRdkContents](/api/cpp/class_i_rh_rdk_contents.html) interface. Any changes you make will appear in the relevant editor and will also be stored in the 3dm file. Getting the contents from a document always returns a const reference. To write to the contents, you must begin a batch of write operations and afterwards end the batch. This is done using the RDK's standard BeginChange / EndChange system. The following is an example of how to access the document contents: ```cpp static class CTestContents : public CRhinoTestCommand { protected: virtual UUID CommandUUID() override { static const UUID uuid = your_uuid_here; return uuid; } virtual const wchar_t* EnglishCommandName() override { return RHSTR_LIT(L"ContentsExample"); } virtual CRhinoCommand::result RunCommand(const CRhinoCommandContext& context) override; } theContentsExampleCommand; CRhinoCommand::result CTestContents::RunCommand(const CRhinoCommandContext& context) { auto* pDoc = context.Document(); if (nullptr == pDoc) return failure; const auto& contents = pDoc->Contents(); // Create a new basic material. ON_Material mat; auto* pMaterial = ::RhRdkNewBasicMaterial(mat, pDoc); if (nullptr == pMaterial) return failure; // Attach it to the document. auto& write_contents = contents.BeginChange(RhRdkChangeContext::Program); write_contents.Attach(*pMaterial); write_contents.EndChange(); // Find the material in the document. const auto* pFound = contents.Find(pMaterial->InstanceId()); ASSERT((nullptr != pFound) && (pFound->InstanceId() == pMaterial->InstanceId())); return success; } ``` -------------------------------------------------------------------------------- # RDK Ground Plane Source: https://developer.rhino3d.com/en/guides/cpp/rdk-ground-plane-classes/ This document describes how to use the RDK ground plane class in C/C++. ### Introduction When rendering real-world scenes, it is very often the case that the rendering will include a large area of ground or flooring. This could be modeled by using a plane, but this is inconvenient because the ground tends to stretch as far as the eye can see. To circumvent this problem, the RDK provides a set of _ground plane_ services to make it easier to add a ground or floor to your scene. A ground plane has an _altitude_, which is usually the same as its position along the z-axis. It can also have a material assigned to it, which will appear in renderings and in the viewport, much as materials assigned to objects do. If the _auto-altitude_ option is enabled, the ground plane will adjust itself to sit below the objects in the scene. ![Road](https://developer.rhino3d.com/images/rdk-ground-plane-road.jpg) Interstate 15, Nevada, USA ~ Photo by John Croudy. ### The Document Ground Plane The _RDK Document Ground Plane_ is a document-resident ground plane which affects viewports and renderings. If you have a Rhino document, you can read and write that document's ground plane through the document's [IRhRdkGroundPlane](/api/cpp/class_i_rh_rdk_ground_plane.html) interface. Any changes you make will appear in the main ground plane UI and will also be stored in the 3dm file. Getting the ground plane from a document always returns a const reference. To write to the ground plane, you must begin a batch of write operations and afterwards end the batch. This is done using the RDK's standard BeginChange / EndChange system. The following is an example of how to access and change the document ground plane: ```cpp static class CGroundPlaneExampleCommand: public CRhinoTestCommand { protected: virtual UUID CommandUUID() override { static const UUID uuid = { your_uuid_here } }; return uuid; } virtual const wchar_t* EnglishCommandName() override { return RHSTR_LIT(L"MyGroundPlaneCmd"); } virtual CRhinoCommand::result RunCommand(const CRhinoCommandContext& context) override; } theGroundPlaneExampleCommand; CRhinoCommand::result CGroundPlaneExampleCommand::RunCommand(const CRhinoCommandContext& context) { auto* pDoc = context.Document(); if (nullptr == pDoc) return failure; // Get the document ground plane. const auto& gp = pDoc->GroundPlane(); RhinoApp().Print(L"Ground plane altitude before: %.1f\n", gp.Altitude()); // Begin a change bracket on the ground plane. auto& write_gp = gp.BeginChange(RhRdkChangeContext::Program); // Set the ground plane's altitude manually. write_gp.SetAutoAltitude(false); write_gp.SetAltitude(10.0); // Create a new custom material. ON_Material mat; mat.SetDiffuse(ON_Color(185, 14, 14)); auto* pMaterial = ::RhRdkNewBasicMaterial(mat, pDoc); if (nullptr != pMaterial) { auto& write_contents = pDoc->Contents().BeginChange(RhRdkChangeContext::Program); write_contents.Attach(*pMaterial); write_contents.EndChange(); write_gp.SetOn(true); write_gp.SetShadowOnly(false); write_gp.SetMaterialInstanceId(pMaterial->InstanceId()); } // End the ground plane change bracket. write_gp.EndChange(); RhinoApp().Print(L"Ground plane altitude after: %.1f\n", gp.Altitude()); return success; } ``` -------------------------------------------------------------------------------- # RDK Linear Workflow Source: https://developer.rhino3d.com/en/guides/cpp/rdk-linear-workflow-classes/ This document describes how to use the RDK linear workflow class in C/C++. ### Introduction Many consumer digital cameras output JPEG files which use the sRGB color space. This color space incorporates gamma correction which makes it possible to directly display the images on a computer monitor without even knowing that gamma correction exists. This means that many available photographic textures have already been gamma-corrected. Working with these images can give unsatisfactory results when intermediate processing and rendering works in a linear fashion. This problem can be avoided by using a _Linear Workflow_. This means that the gamma correction is removed from the images before they are used for rendering, and reapplied afterwards for display. ### The Document Linear Workflow The _RDK Document Linear Workflow_ is a document-resident linear workflow object which affects renderings and viewports. If you have a Rhino document, you can read and write that document's linear workflow settings through the document's [IRhRdkLinearWorkflow](/api/cpp/class_i_rh_rdk_linear_workflow.html) interface. Any changes you make will appear in the Rendering UI and will also be stored in the 3dm file. Getting the linear workflow from a document always returns a const reference. To write to the linear workflow, you must begin a batch of write operations and afterwards end the batch. This is done using the RDK's standard BeginChange / EndChange system. The following is an example of how to access and change the document linear workflow settings: ```cpp static class CLinearWorkflowExampleCommand : public CRhinoTestCommand { protected: virtual UUID CommandUUID() override { static const UUID uuid = your_uuid_here; return uuid; } virtual const wchar_t* EnglishCommandName() override { return RHSTR_LIT(L"LinearWorkflowExample"); } virtual CRhinoCommand::result RunCommand(const CRhinoCommandContext& context) override; } theLinearWorkflowExampleCommand; CRhinoCommand::result CLinearWorkflowExampleCommand::RunCommand(const CRhinoCommandContext& context) { auto* pDoc = context.Document(); if (nullptr == pDoc) return failure; const auto& lw = pDoc->LinearWorkflow(); RhinoApp().Print(L"LW gamma before: %.1f\n", lw.PreProcessGamma()); auto& write_lw = lw.BeginChange(RhRdkChangeContext::Program); write_lw.SetPreProcessGamma(3.5f); write_lw.EndChange(); RhinoApp().Print(L"LW gamma after: %.1f\n", lw.PreProcessGamma()); return success; } ``` -------------------------------------------------------------------------------- # RDK Post Effect Classes Source: https://developer.rhino3d.com/en/guides/cpp/rdk-post-effects/ This document describes how to use the RDK's post effect classes in C/C++. This is preliminary documentation. The functionality described below is not yet available. ### Introduction Starting with Rhino 7, the RDK includes a new post effect system which allows plug-in developers to create their own post effects. Although this was possible prior to Rhino 7, earlier versions required the post effects to apply changes to the final RGBA bitmap pixels. This meant that the post effects could only work on low dynamic range (8-bit) color components. Furthermore, all processing had to be done on the CPU which could be very slow for some effects. The new _post effect pipeline_ allows post effects to process the original high dynamic range channel data and also allows this to be done on the _GPU_ if so desired. CPU post effects are, of course, still supported. ### The Post Effect pipeline The new post effect system operates as a pipeline. The post effects selected by the user are run one after the other. The process begins with the rendered image which exists as a set of channels in the frame buffer. As each effect runs, it processes the output of the previous one. Each post effect can operate on any of the available channels, such as RGBA, distance-from-camera, etc. ### Post Effects A post effect is an object that runs in the Render Window to process the rendered frame buffer image and create a modified version of it. The RDK comes with several built-in post effects such as Fog and Depth-of-field. After rendering, the user can choose which post effects to apply and in what order to apply them. Every post effect has a _type_ which determines when in the pipeline it is run. There are three of these types; _Early_, _Tone Mapping_ and _Late_ and the pipeline runs them in that order. Early post effects can operate on the original high dynamic range image. Tone mapping post effects perform _tone mapping_ on the high dynamic range image to produce a low dynamic range image. The pipeline then runs a process which clamps the values in the RGBA channels to the range 0..1. Finally, the late post effects process this low dynamic range image to produce a final image which the pipeline converts to an 8-bit image. It is this final image which is displayed to the user in the Render Frame. ### Writing a Post Effect To write a post effect, a developer needs to create a subclass of one of the post effect base classes. Which base class you use depends on the type of your post effect. If your post effect is intended to process the high dynamic range image, it must be derived from `CRhRdkNewEarlyPostEffectPlugIn`. If it is a tone mapper, it must be derived from `CRhRdkNewToneMappingPostEffectPlugIn`. If it operates on the low dynamic range image, it must be derived from `CRhRdkNewLatePostEffectPlugIn`. The following is an example of an early post effect. All post effects follow this general style. ``` class CExamplePostEffect : public CRhRdkEarlyPostEffect { public: CExamplePostEffect() { InternalResetToFactoryDefaults(); } static UUID Ident(void); virtual UUID Id(void) const override { return Ident(); } virtual ON_wString LocalName(void) const override; virtual unsigned int BitFlags(void) const override { uf_ExecuteForProductionRendering | uf_ExecuteForRealtimeRendering; } virtual void RequiredChannels(OUT ON_SimpleArray& aChan) const override; virtual bool Execute(IRhRdkNewPostEffectPipeline& pipeline, const ON_4iRect& rect) const override; virtual ExecuteWhileRenderingOptions GetExecuteWhileRenderingOption(void) const override { return ExecuteWhileRenderingOptions::Always; } virtual bool GetParameter(const wchar_t* wszName, OUT CRhRdkVariant& vValue) const override; virtual bool SetParameter(const wchar_t* wszName, const CRhRdkVariant& vValue) override; virtual bool ReadState(const IState& state) override; virtual bool WriteState(IState& state) const override; virtual void AddUISections(IRhRdkPostEffectUI& ui) override; virtual void ResetToFactoryDefaults(void) override { InternalResetToFactoryDefaults(); } virtual bool DisplayHelp(void) const override { return false; } virtual bool CanDisplayHelp(void) const override { return false; } private: void InternalResetToFactoryDefaults(void); }; ``` Each post effect has a unique id and a localized name which are returned by the `Id()` and `LocalName()` methods respectively. Next you specify a set of usage flags by implementing the `BitFlags()` method. This tells the RDK certain facts about the post effect, including under which circumstances the post effect should be executed, which is done by calling the `Execute()` method. The implementation of `Execute()` will be described in detail later. The pipeline executes some post effects while rendering is proceeding. To let it know if your post effect can support this, you implement `GetExecuteWhileRenderingOption()`. This can be one of the following: * 'Never' means the post effect does not support execution while rendering. * 'Always' means the post effect supports execution while rendering. * 'UseDelay' means the post effect supports execution while rendering, but only after a delay the first time. Which of these you return depends on how time-consuming your post effect is and whether or not it requires the final rendered image to work properly. For example, the Fog effect works on single pixels and does not need the surrounding pixels in order to work. It is also quite fast. Therefore, it returns `ExecuteWhileRenderingOptions::Always`. On the other hand, the Watermark post effect needs to know how long the rendering took to complete, so it returns `ExecuteWhileRenderingOptions::Never` as it should only be run at the end, after rendering finishes. #### Channels Your post effect will, of course, need to use existing channels to get its source data. You should implement `RequiredChannels()` to tell the RDK which channels it is planning to use. This information is needed if the user chooses 'Automatic' in the Render Channels section of the Rendering panel: ``` void CExamplePostEffect::RequiredChannels(OUT ON_SimpleArray& aChan) const { CRhRdkEarlyPostEffect::RequiredChannels(aChan); // Be sure to call the base class. aChan.Append(IRhRdkRenderWindow::chanRGBA); ... } ``` #### Parameters If your post effect has options or _parameters_ that the user can set, it will have some private members for these values. It must also implement the following methods: * `GetParameter()` This is called by the user interface when it needs to display a value. * `SetParameter()` This is called by the user interface when the user changes a value. * `ReadState()` This is called when the post effect is read in during document loading. * `WriteState()` This is called when the post effect is written out during document saving. * `AddUISections()` This is called by the framework when the user interface is created. It allows the post effect to add sections (AKA roll-ups) to the user interface. * `ResetToFactoryDefaults()` This resets the settings to their initial default values. #### Help Finally, your post effect can optionally provide a help page. If it does, it should implement `CanDisplayHelp()` to return _true_, and it must also implement `DisplayHelp()` to actually display the help page. Built-in post effects do this by opening a page from the Rhino documentation in the user's web browser. Once you have defined your post effect class, you must create a factory for it and register the factory with the RDK: #### Registration ``` class CExamplePostEffectPlugInFactory : public CRhRdkNewPostEffectPlugInFactory { public: virtual IRhRdkNewPostEffectPlugIn* NewPostEffectPlugIn(void) const override { return new CExamplePostEffect; } virtual UUID PlugInId(void) const final override; // Return your render plug-in's id. }; ``` Registration of the factory is done in your override of `CRhRdkRenderPlugIn::RegisterExtensions()` as follows: ``` void CMyRdkPlugIn::RegisterExtensions(void) { ... AddExtension(new CExamplePostEffectPlugInFactory); ... } ``` #### Implementation The bulk of your post effect's implementation will be in the `Execute()` method. In this example, we will assume you are writing a CPU-based post effect. All post effects read channel data (pixels), do some calculations, and create new channel data. Very often, this will involve reading RGBA data, processing it, and writing RGBA data. The `Execute()` method has a parameter of type `IRhRdkNewPostEffectPipeline&` and another of type `const ON_4iRect&`. The post effect queries the pipeline for existing channels (the input), asks it to create new channels (the output) and then iterates over pixels within the specified rectangle to create the output form the input. Sometimes the rectangle will be for the whole rendering, and sometimes (during rendering) it will be small areas of the rendering. You don't need to worry about this; you just need to process that exact rectangle of pixels. In order to get input channels to process, the post effect must call `GetChannel()` specifying the channel identifier. Note that post effects are only allowed to access the color component channels (red, green, blue and alpha) by asking for the RGBA composite channel. Requests for the individual components (chanRed etc) are not allowed. So, to get the RGBA channel, the post effect does the following: ``` // Get the RGBA channel. const auto* pRGBA = pepl.GetChannelForRead(RW::chanRGBA, 0); if (nullptr == pRGBA) return false; ``` At this point you decide if you want to run on the CPU or the GPU and obtain the correct interface, as follows. We are running on the CPU, so we write: ``` const auto* pRGBA_CPU = pRGBA->CPU(); if (nullptr == pRGBA_CPU) return false; ``` Having got the input channel, we now need to get a new output channel: ``` // Create a new RGBA channel. auto* pNewRGBA = pepl.GetChannelForWrite(RW::chanRGBA, 0); if (nullptr == pNewRGBA) return false; ``` We also need to get the CPU interface: ``` auto* pNewRGBA_CPU = pNewRGBA->CPU(); if (nullptr == pNewRGBA_CPU) return false; ``` Now we are ready to enter the main pixel loop. Inside the loop, you read the input pixels by calling `pRGBA_CPU->GetValue()` or `pRGBA_CPU->GetValueEx()`. The latter is preferred because it's faster than the former which was only retained for backward compatibility. You write the output pixels by calling `pNewRGBA_CPU->SetValue()`. The iteration is best done by iterating over y and then x. You should include a call to `IRhRdkNewPostEffectPipeline::ReportProgress()` in the _y_ loop. ``` // Iterate over all the pixels in the area. for (int y = rect.top; y < rect.bottom; y++) { for (int x = rect.left; x < rect.right; x++) { float in[4]; if (pRGBA_CPU->GetValueEx(x, y, in)) { float out[4]; // Do calculations and create 'out' from 'in'. pNewRGBA_CPU->SetValue(x, y, ComponentOrder::RGBA, out); } } // Report progress and abort if requested to. if (!pipeline.ReportProgress(y)) break; } ``` Finally, the new channel must be committed. This causes the pipeline to replace the current version of the channel with your new version. If you don't do this, you changes will be discarded. ``` // Commit the changes. pipeline.Commit(pNewRGBA); ``` Your changes are now in the pipeline and the next post effect to run will use your pixel data as its input, assuming it's also working on RGBA data. #### CPU vs GPU channels. As mentioned above, post effects can run on the GPU instead of the CPU. By calling for the correct interface (`CPU()` or `GPU()`) you can transparently get access to the data you need. The pipeline takes care of managing the data and converting / moving it from CPU memory to GPU memory as needed. If several post effects run on the GPU, the data will be moved to the GPU and it will stay there while those post effects run. It will only be moved back to main CPU memory if a post effect calls `CPU()`. -------------------------------------------------------------------------------- # RDK Render Content Source: https://developer.rhino3d.com/en/guides/cpp/rdk-render-content/ This document describes how to use the RDK render content class in C/C++. ![Material Environment and Texture](https://developer.rhino3d.com/images/rdk-met.png) ### Introduction Probably the most important object in the Rhino RDK is the Render Content (AKA ‘Content’) object. This object is an abstraction that represents one of three possible ways of describing something that is going to be drawn (rendered) on the screen. There are three kinds of contents: _Materials_, _Environments_ and _Textures_. A material describes how a surface will be rendered. It has properties such as color, glossiness, etc. An environment describes how the surroundings of a model affect its appearance. A texture describes the texture of a surface. In addition to color and glossiness, all real-world surfaces have a detailed appearance such as a wood grain. This appearance is described by RDK textures. Objects and layers in the document can have a material assigned to them. This material is usually (but not always) associated with an RDK Material. The Ground Plane can also have such a material assigned to it. As you will see later, render contents are actually a tree hierarchy. The assigned RDK material is always the top-level material which means it has no parent. It can, however, have children. ### Classes The base class for contents is _CRhRdkContent_. This is an abstraction that controls the features common to all contents. For example, all contents have a unique identifier called the instance id which identifies each instance of CRhRdkContent. They also have a unique type id which defines their type and an instance name which is the user-defined name that appears on the screen. They can also have any number of children which means that a content is usually considered as a hierarchy because these children can also have children to any depth. The hierarchy is therefore a tree structure. This flexibility allows the user to create very complex materials if so desired. All these properties and more are stored in and managed by the base class called CRhRdkContent. This base class has a single immediate subclass called _CRhRdkCoreContent_ which provides default implementations of some of the more complicated functions, mostly to do with the user interface. Derived from this are the three main _content kinds_; _CRhRdkMaterial_, _CRhRdkEnvironment_ and _CRhRdkTexture_. It is from these latter three objects that a plug-in should derive its own specialized materials, environments and textures. To do this, it is necessary to choose a base class from one of those three and write a subclass that overrides or implements all the necessary virtual functions, implement a factory class that knows how to create an instance of the class, and register the factory with the RDK. Once this is done, your content will start to appear in the Rhino user interface, specifically in the Material Editor if the content is a material (or the Environment Editor or Texture Palette if it is an environment or texture). You can choose to use the default user interface provided by CRhRdkCoreContent or you can create a custom user interface of your own design. You can also avoid having to create a user interface at all by using the RDK's built-in _Automatic UI_ system. Code examples showing how to do this can be found in the [SampleRdkMarmalade](https://github.com/mcneel/rhino-developer-samples/tree/6/cpp/SampleRdkMarmalade) sample. ### Lifetime and ownership There are two flavors of content in the RDK -- temporary and persistent. It is very important to understand the distinction between a temporary content instance and a persistent content instance, and the fact that a temporary instance (and all its children) can become persistent. Temporary contents get created and deleted very often during the normal operation of the RDK. In fact, just about anything the user clicks on might result in a temporary content being created and deleted again. They are created by the content browser, the preview rendering, and so on. They are 'free floating' and are owned by whomever created them. They do not appear in the modeless UI, they do not get saved in the 3dm file, and they can freely be deleted again after use. Contrast this with persistent contents which are attached to a document. They are always owned by the RDK, appear in the modeless UI and get saved in the 3dm file. You should never store pointers to persistent contents; you should only store their instance ids and look them up again using CRhRdkDocument::FindContentInstance(). They can be deleted only after detaching them from the document. The preferred way to access document-resident contents is by using the [document contents interface](/guides/cpp/rdk-contents-classes). This is an example code sequence showing the main stages in the lifetime of a content. ```cpp // Create a new content owned by you. auto* pContent = new CMyContent; // Initialize the content. pContent->Initialize(); // Attach the content to a document. The content is now owned by // the document and it will appear in the various user interfaces. doc.Contents().Attach(*pContent); // Detach the content from the document. The content will disappear // from any user interfaces and it is once again owned by you. doc.Contents().Detach(*pContent); // Uninitialize the content to prepare it for deletion. pContent->Uninitialize(); // Delete the content. This is possible because the content is owned by you. delete pContent; ``` You can also create a free-floating content that is not attached to a document. This content will not appear in any modeless UIs, but it is possible to edit it in a modal UI by calling the `Edit()` method. ```cpp auto* pContent = new CMyMaterial; // Initialize the content. pContent->Initialize(); // Edit the content in the Modal Editor. If the user clicks OK, this returns an edited version // of the content (the original is unaltered) and you own this object as well as the original one. auto* pEdited = pContent->Edit(); if (nullptr != pEdited) // Returns null if the editor is canceled. { // Uninitialize the edited content to prepare it for deletion. pEdited->Uninitialize(); // Delete the edited content. This is possible because the content is owned by you. delete pEdited; } // Uninitialize the original content to prepare it for deletion. pContent->Uninitialize(); // Delete the original content. This is possible because the content is owned by you. delete pContent; ``` In an ideal world, the call to `delete` would both uninitialize and delete the content. But since `Uninitialize()` is a virtual function, this is not possible. So every call to `delete` must be preceded by a call to `Uninitialize()`. Remember that you can only do this if you own the object. Contents that are owned by a document (attached) cannot be deleted by the client without first being detached. The most important thing to understand is that if you want to delete a content, you must know who owns it and act accordingly. The RDK tries to enforce good practice by using constness. For example, the function `CRhRdkDocument::FindContentInstance()` returns a const pointer to the content. This has two implications: 1. This content is not owned by you and you can’t delete it. 2. This content can’t be modified; it’s read-only. See below under Modifying Contents for information on how a const content can be modified. | Who owns it? | Procedure for deletion | |:-------------------------|:------------------------| | You | Uninitialize it. Delete it. | | A document | Detach it from the document. Uninitialize it. Delete it. | | Unknown | You have no right to delete it. Doing so will most likely crash Rhino. ### Modifying Contents If a content is owned by you, for example immediately after you create it, then it is generally non-const and you can modify it by calling any non-const method such as `SetParameter()`. But if the content is const, for example after being found in a document, then you have to open it for modification. This is done by calling `BeginChange()` which begins a change bracket. BeginChange() takes a parameter called the change context. This is one of the values of the enum `RhRdkChangeContext`. The most common one used by plug-ins is `RhRdkChangeContext::UI` which means the change is being done by the user inside some user interface. BeginChange() also returns a non-const reference to the same content, allowing you to call any non-const method such as `SetParameter()`. It is important to call the `Changed()` method on the content if you detect that a change to the parameter has occurred. If this is not done, the user interface will not update. More than one parameter can be changed inside the change bracket. When all the desired changes have been made, you must call `EndChange()`. Calls to BeginChange() and EndChange() can be nested but there must always be exactly one call to EndChange() for every call to BeginChange(). The final call to EndChange() closes the content and sends the events necessary to update the user interface. ### Creating your own specialized material The most commonly specialized render content is probably the material. When creating your own render content, most of the steps that apply to materials also apply to the other two kinds. Rhino is commonly used for designing things. Whether these things are phones, boats, jewelry or dinosaurs makes very little difference to the bulk of the plug-in developer's work. But where it does make a difference is in the creation of the specialized materials and textures. If your plug-in is intended for designing cars for example, it will need materials and textures that describe what a car looks like. In the real world, the look of a car is heavily influenced by the kind of paint used on the car's body. In this discussion, we will assume you are writing a render plug-in for designing cars and we will show how to create a car paint material. The first thing we notice when looking at a painted surface is likely to be the color of the paint. After that, we notice that the paint is either gloss or matte. In the case of car paint, there is also sometimes a glittery texture and often a candied appearance. Sometimes the color of the surface changes depending on the viewing angle. These properties of the car paint material will be defined by _fields_ in the material. We will create a simple car paint material with the following fields: * Paint color * Gloss amount * Glitter color * Glitter size * Glitter amount This will allow our car paint to have a basic paint color and some glitter. Other more advanced features such as candy color could be added but this will be left as an exercise for the reader (as will writing the actual car paint shader). As mentioned earlier, every render content has the ability to host any number of fields which describe the properties of the content. It is possible to ignore the fields and just provide one's own data members, but this is more difficult and requires more work than using fields, because when fields are used, a lot of the functionality is provided automatically by the RDK. So using fields is highly recommended for all kinds of content. First let's define the class for our new car paint material. Because it is a material, it should be derived from `CRhRdkMaterial`. The first thing to do is implement some important virtual functions: ```cpp class CCarPaintMaterial : public CRhRdkMaterial { protected: virtual UUID TypeId(void) const override { return _unique_type_id_ } virtual UUID PlugInId(void) const override { return _unique_plug_in_id_; } virtual UUID RenderEngineId(void) const override { return _unique_render_engine_id_; } virtual ON_wString InternalName(void) const override { return L"car-paint-material"; } virtual ON_wString TypeName(void) const override { return L"Car Paint"; } virtual ON_wString TypeDescription(void) const override { return L"Demo car paint material"; } }; ``` Then override the `BitFlags()` method and switch on the fields option. This tells the RDK that the material uses fields and allows a lot of automation to take place by default. At the same time we remove the _Texture Summary_ option which would cause our UI to display a panel containing a texture list. Since this material has no child textures, this would not be useful here. ```cpp class CCarPaintMaterial : public CRhRdkMaterial { ... virtual unsigned int BitFlags(void) const override; }; unsigned int CCarPaintMaterial::BitFlags(void) const { auto flags = __super::BitFlags(); // Get the default flags. flags &= ~bfTextureSummary; // Remove the Texture Summary option. flags |= bfFields; // Add the fields option. return flags; } ``` Recall that `CRhRdkMaterial` is derived from `CRhRdkContent`. This base class contains a field collection which is accessible by calling the `Fields()` method to obtain a reference to the `CRhRdkContentFields` object containing the fields. Each field is implemented by `CRhRdkContentField` and they must be defined and initialized. They are best defined as members of the class and initialized in the constructor. Let's add the constructor and field members for the paint properties: ```cpp class CCarPaintMaterial : public CRhRdkMaterial { public: CCarPaintMaterial(); ... private: CRhRdkContentField m_PaintColor; CRhRdkContentField m_GlossAmount; CRhRdkContentField m_GlitterColor; CRhRdkContentField m_GlitterSize; CRhRdkContentField m_GlitterAmount; }; ``` Notice that this says nothing about the _type_ of the fields. Some will need to be colors and others may be floats or doubles. This is defined when the fields are initialized in the material constructor. Let's do that now: ```cpp CCarPaintMaterial::CCarPaintMaterial() : m_PaintColor (*this, L"paint-color", L"Paint Color", L"Paint Color"), m_GlossAmount (*this, L"gloss-amount", L"Gloss Amount", L"Gloss Amount"), m_GlitterColor (*this, L"glitter-color", L"Glitter Color", L"Glitter Color"), m_GlitterSize (*this, L"glitter-size", L"Glitter Size", L"Glitter Size"), m_GlitterAmount(*this, L"glitter-amount", L"Glitter Amount", L"Glitter Amount") { m_PaintColor = CRhRdkColor(28, 122, 230); m_GlitterColor = CRhRdkColor(80, 200, 250); m_GlossAmount = 1.0; m_GlossAmount.SetLimits(0.0, 1.0); m_GlitterSize = 0.5; m_GlitterSize.SetLimits(0.0, 20.0); m_GlitterAmount = 0.5; m_GlitterAmount.SetLimits(0.0, 1.0); } ``` In the example above, each field is first constructed and then initialized. The constructor of `CRhRdkContentField` is quite involved and has many parameters for expert users, but we just set the most important ones here. Every field must have, at the very least, a reference back to the content that owns it, an internal name, a localized name and an English name. If your plug-in does not support localization, you can just repeat the English name as shown above. After construction, the fields are initialized by assigning a value of the required type. This initializes the field to its type and default value. For example, setting the field to be a color is done by assigning a `CRhRdkColor` to it. Fields can have many types from simple POD types like `int` to more elaborate types such as `ON_Xform`. The default values will be changed when the user edits them, but they will be restored whenever `ResetParametersToDefaults()` is called. If the material is attached to a document, the fields' values will be stored when the document is saved, referenced by the internal name. For this reason, once a field's type and internal name have been set, they should never be changed. The English and localized names are free to change if so desired. The localized name will be displayed to the user if you choose to use the automatic UI system. The constructor above also sets some minimum and maximum limits on each field which prevents the user from setting them to nonsensical values. It is important to understand a few things about this method of using fields. First, the field objects are owned by your material because they are embedded in it. They are constructed and destructed at the same time as your material. These are known as _static fields_. There is another kind of field called a _dynamic field_, the use of which is an advanced topic which will be covered in a different article. #### Fast material copying When a material is copied the default implementation of `MakeCopy()` has to assume that the properties are stored in some user-defined way, so it must convert the whole material to XML, copy the XML and convert it back to properties in the copy. This can be very slow. Because we are exclusively using fields to store our material's properties, we can take advantage of an optimization provided by the RDK. If you override `MakeCopy()` and have it call `FastMakeCopy()`, you can make the copying much faster. The default implementation of `FastMakeCopy()` does the bulk of the work by simply copying the fields. ```cpp class CCarPaintMaterial : public CRhRdkMaterial { ... virtual CRhRdkContent* MakeCopy(CopyMethods m) const override { return FastMakeCopy(m); } }; ``` #### Getting and setting material parameters The content user interface displays and allows editing of content parameters (i.e., properties). Although your material could provide methods for getting and setting each parameter, it is not necessary to do this. As long as the UI knows the internal names of the material's fields, it need call only two methods: `GetParameter()` and `SetParameter()`. These methods work with the `CRhRdkVariant` class to get or set parameters by name. When using the field system, this name _is_ the field's internal name. The default implementations of these methods usually do everything you need, but it is possible to override and specialize them if necessary. #### The material simulation All render contents must provide a method for other renderers, including the standard Rhino display, to represent them visually. This method is called _simulation_. Without simulation, the rendered mode in Rhino would not update to reflect your custom definitions. In addition, simulation makes it possible for users to render objects with your content objects attached without having your plug-in installed. Furthermore, when the RDK displays a material in the Material Editor, it first shows a simple rendition of the material preview using OpenGL. After that it renders the material preview using the current render engine. The OpenGL rendition uses the simulation of the material. This is essentially an `ON_Material` that has been set up to look as much like your material as possible. For complex materials, it can be difficult to produce a good simulation, but the most important properties to get right are the diffuse color and the glossiness. So we must implement an override of `CRhRdkMaterial::SimulateMaterial()` as follows: ```cpp class CCarPaintMaterial : public CRhRdkMaterial { ... virtual void SimulateMaterial(ON_Material& matOut, CRhRdkTexture::TextureGeneration tg, int iSimulatedTextureSize, const CRhinoObject* pObject) const override; }; void CCarPaintMaterial::SimulateMaterial(ON_Material& matOut, CRhRdkTexture::TextureGeneration tg, int iSimulatedTextureSize, const CRhinoObject* pObject) const { __super::SimulateMaterial(matOut, tg, iSimulatedTextureSize, pObject); const auto col = m_PaintColor.Value().AsOnColor(); matOut.SetDiffuse(col); const auto gloss = m_GlossAmount.Value().AsDouble(); matOut.SetShine(gloss * ON_Material::MaxShine); } ``` In the above example, only the output material `matOut` is used. The other parameters are for more advanced use and will not be covered here. #### The material shader Most render engines have custom binary definitions for their materials, environments and textures. Usually these will be instances of classes with filled in “parameter blocks” initialized from the data in a content object. Sometimes they will simply be strings pointing to files or locations in a library. These objects are termed _shaders_ in the RDK. When an object needs to be rendered by your render engine, you will need to access these objects. As part of your content object implementation you should override `CRhRdkContent::GetShader()` to return a pointer to this object. The reason the return value is `void*` is because the RDK does not know anything about the shader; it's an internal render-engine object, but the material interface must have a method for getting it. The render engine will cast the returned pointer to the correct type before use. Since you are both the provider and client of this function, how you implement it is up to you. The data is private and its type and allocation details are also completely up to you. However, you must check the incoming render engine id to ensure that you can render shaders of that type. Usually you will call `IsCompatible()` passing the id and only proceed if it returns `true`. ```cpp class CCarPaintMaterial : public CRhRdkMaterial { ... virtual void* GetShader(const UUID& uuidRenderEngine, void* pvData) const override; }; void* CCarPaintMaterial::GetShader(const UUID& uuidRenderEngine, void* pvData) const { if (!IsCompatible(uuidRenderEngine)) return nullptr; void* pShader = nullptr; // TODO: Get a pointer to the shader used to render this material. return pShader; } ``` #### The material factory The RDK uses the factory pattern to allow render plug-ins to provide custom render content. This means that in order to have our new car paint material show up in the Material Editor's list of available materials, we must create a factory object that knows how to create an instance of the material. Because the factory produces materials, it must be derived from `CRhRdkMaterialFactory`. It must also implement the `NewMaterial()` method to create the material instance: ```cpp class CCarPaintMaterialFactory : public CRhRdkMaterialFactory { protected: virtual CRhRdkMaterial* NewMaterial(void) const override { return new CCarPaintMaterial; } }; ``` Finally, we register the factory with the RDK in our override of `CRhRdkRenderPlugIn::RegisterExtensions()`: ```cpp void CCarDesignerPlugIn::RegisterExtensions(void) const { ... AddExtension(new CCarPaintMaterialFactory); ... } ``` ### Creating the user interface The above code is enough to get us a car paint material that the user can choose and create in the editor. But this is not very useful because we haven't provided a user interface, so the only things the user can edit are the material's name and notes. In order to edit the actual material properties, we need to supply a UI. This can be done (on Windows, at least) by creating an MFC dialog with an IDD and a resource in the usual way. This can be a lot of work. Sometimes it's enough to just be able to see and edit the parameters, for example when prototyping, and sometimes even for the final product. We will do this for our car paint material by using the RDK's Automatic UI system. In order to create a UI for your material, you must override `CRhRdkCoreContent::AddUISections()`. This is true in almost all cases, except when you want to create a completely customized user interface. To use the automatic UI, you must call `AddAutomaticUISection()` in your override. This will get you an automatic UI section (AKA roll-up) in the user interface, but it will be blank because it is necessary to tell the RDK which fields you want to display. The automatic UI uses an object called a _param block_ which is accessed through the `IRhRdkParamBlock` interface. This param block contains the information about what parameters to display in the user interface and it accepts the changes made by the user. It can be thought of as a conduit that enables the transfer of the parameters between the render content and the user interface. When using fields, it's easy to get all of this functionality. You just have to override `AddAutoParameters()` and have the fields add themselves to the param block. When the user changes a value in the UI, it is necessary for your material to accept to change and modify the relevent field. All you need do is override `GetAutoParameters()` and have the fields load themselves from the param block. You can also sort the parameters according to either their internal (field) name or their display name. This is commonly done in `AddAutoParameters()` just after adding them. The following code will get us a working automatic UI for the entire car paint material: ```cpp class CCarPaintMaterial : public CRhRdkMaterial { ... virtual void AddUISections(IRhRdkExpandableContentUI& ui) override; virtual void AddAutoParameters(IRhRdkParamBlock& paramBlock, int id) const override; virtual void GetAutoParameters(const IRhRdkParamBlock& paramBlock, int id) override; }; void CCarPaintMaterial::AddUISections(IRhRdkExpandableContentUI& ui) { const wchar_t* wsz = L"Car paint settings"; AddAutomaticUISection(ui, wsz, wsz, 0); __super::AddUISections(ui); } void CCarPaintMaterial::AddAutoParameters(IRhRdkParamBlock& paramBlock, int id) const { Fields().AddValuesToParamBlock(paramBlock, id); paramBlock.Sort(IRhRdkParamBlock::SortBy::DisplayName); } void CCarPaintMaterial::GetAutoParameters(const IRhRdkParamBlock& paramBlock, int id) { Fields().GetValuesFromParamBlock(paramBlock, id); } ``` #### Using multiple automatic sections Our car paint material only has a few fields, but some customized materials will have many more. If you don't want all the fields to appear in the same UI section, you can have the automatic UI distribute the fields between multiple sections. To do this, you first decide which fields should appear together and give them the same identifier. This is one of the constructor parameters, `sectionId` which defaults to zero. In this example we will put the color fields on one section and the other fields on a different section. The color fields are given the id _1_ and the others _2_. The `sectionId` parameter appears after two other 'filter' parameters that are not relevant to this example, so we will fill them in as the default value, `CRhRdkContentField::Filter::All`: ```cpp static const int idColor = 1; static const int idOther = 2; static const auto f = CRhRdkContentField::Filter::All; CCarPaintMaterial::CCarPaintMaterial() : m_PaintColor (*this, L"paint-color", L"Paint Color", L"Paint Color" , f, f, idColor), m_GlossAmount (*this, L"gloss-amount", L"Gloss Amount", L"Gloss Amount" , f, f, idOther), m_GlitterColor (*this, L"glitter-color", L"Glitter Color", L"Glitter Color" , f, f, idColor), m_GlitterSize (*this, L"glitter-size", L"Glitter Size", L"Glitter Size" , f, f, idOther), m_GlitterAmount(*this, L"glitter-amount", L"Glitter Amount", L"Glitter Amount" , f, f, idOther) ... ``` Now that the fields have been separated by this id, we just have to add a second section to the UI and specify which id appears on which section: ```cpp void CCarPaintMaterial::AddUISections(IRhRdkExpandableContentUI& ui) { const wchar_t* wsz1 = L"Car paint color settings"; AddAutomaticUISection(ui, wsz1, wsz1, idColor); const wchar_t* wsz2 = L"Car paint other settings"; AddAutomaticUISection(ui, wsz2, wsz2, idOther); __super::AddUISections(ui); } ``` #### Extra requirements It is possible to enhance the automatic UI a little more by making use of _extra requirements_. These are essentially a set of special options that can applied to the parameters so their display can be tweaked. Let's demonstrate this by changing one of the parameters, _Glitter Amount_ to show its value as a percentage. Any float or double value that has a nominal range of 0 to 1 can be displayed as 0% to 100% by adding the `RDK_NUMBER_EDIT_TWEAKS` extra requirement and then specifying its `RDK_PERCENTILE` option. The requirement is specified when parameters are added to the param block. ```cpp void CCarPaintMaterial::AddAutoParameters(IRhRdkParamBlock& paramBlock, int id) const { Fields().AddValuesToParamBlock(paramBlock, id, RDK_NUMBER_EDIT_TWEAKS); ... } ``` After this is done, all the parameters will support this extra requirement, but it won't have any effect until you specify an option within the scope of that requirement. In this case, the `RDK_PERCENTILE` option must be specified. To do this, your material needs to override `GetExtraRequirementParameter()` as follows: ```cpp class CCarPaintMaterial : public CRhRdkMaterial { ... virtual bool GetExtraRequirementParameter(const wchar_t* wszParamName, const wchar_t* wszExtraReqName, CRhRdkVariant& vValueOut) const override; }; bool CCarPaintMaterial::GetExtraRequirementParameter( const wchar_t* wszParamName, const wchar_t* wszExtraReqName, CRhRdkVariant& vValueOut) const { if (__super::GetExtraRequirementParameter(wszParamName, wszExtraReqName, vValueOut)) return true; if (0 == _wcsicmp(L"glitter-amount", wszParamName)) { if (0 == _wcsicmp(RDK_PERCENTILE, wszExtraReqName)) { vValueOut = true; return true; } } return false; } ``` This is how the user interface appears after the above changes have been made. It is possible to have as many fields as you need split among as many sections as you need, limited only by available computer resources. ![CarPaint2](https://developer.rhino3d.com/images/car-paint-example2.png) ### Summary This article introduced render contents and covered the most important aspects of using them in your render plug-in. It explained ownership, modification, copying, and showed how to create a customized material and a simple user interface for it. -------------------------------------------------------------------------------- # RDK Rendering Source: https://developer.rhino3d.com/en/guides/cpp/rdk-rendering-classes/ This document describes how to use the RDK's rendering classes in C/C++. ### Introduction Even before the RDK existed, Rhino had a rendering pipeline and a _Render_ command which would allow the current render engine (e.g., Rhino Render or Flamingo) to render the scene. The RDK builds on and enhances this system to provide the following features: * An extensible Render Window UI. * A frame buffer with both built-in and customizable channels. * An extensible post effect system. * Exposure and color adjustment controls. * Asynchronous rendering. In addition to the built-in functionality, plug-in developers have the ability to add their own UI panes, custom channels, and post effects. The asynchronous rendering feature frees users from being locked out of Rhino while rendering proceeds and actually allows multiple renders to run at the same time using different render engines, if so desired. ### The many faces of a render window. The term _render window_ can be a source of confusion, because there are several different objects in the RDK that could be called by that name: * The physical render window that the user sees on the screen. * The data and structures that lie behind the `IRhRdkRenderWindow` interface. * The `RenderWindow()` SDK method (on `CRhinoSdkRender` and `CRhRdkSdkRender`). * The RenderWindow command. In order to avoid confusion, the physical render window will be called the _render frame_ in this article. Elsewhere, the phrase 'render window' will mean the `IRhRdkRenderWindow` interface. If the method is mentioned, it will be written in this form: `RenderWindow()` including the parentheses. The command will be referred to as the RenderWindow _command_. ### Getting started Let's start at the top and follow the rendering process from the moment the user presses the Render button until the render frame is closed. You must first create a subclass of `CRhRdkSdkRender`: ```cpp class CExampleSdkRender : public CRhRdkSdkRender { public: CExampleSdkRender(const CRhinoCommandContext& context, CRhinoRenderPlugIn& plugIn, bool bPreview, const ON_wString& sCaption, UINT uIconId); virtual CRhinoSdkRender::RenderReturnCodes Render(const ON_2iSize& sizeImage) override; virtual CRhinoSdkRender::RenderReturnCodes RenderWindow(CRhinoView* pView, const LPRECT pRect, bool bInPopupWindow) override; ... }; ``` The first thing you'll notice are the `Render()` and `RenderWindow()` methods. These will be called in response to the _Render_ and _RenderWindow_ commands respectively. In the following discussion, when we refer to the Render command or the `Render()` method, we are also referring to the RenderWindow command and the `RenderWindow()` method, depending on which one the user chose. In the constructor of CExampleSdkRender you do your basic initialization. This may include adding channels to the render window. ```cpp CExampleSdkRender::CExampleSdkRender(const CRhinoCommandContext& context, CRhinoRenderPlugIn& plugIn, const ON_wString& sCaption, UINT uIconId, bool bPreview) : CRhRdkSdkRender(context, plugIn, sCaption, uIconId) { // The RDK only adds the red, green, blue and alpha channels by default, // but it provides several other built-in channels. Let's add some. // First, get the render window. auto& rw = GetRenderWindow(); // If this render window is being reused, remove any non-fixed channels // that were added last time. rw.ClearChannels(); // Add a channel for use as a z-buffer. rw.AddChannel(IRhRdkRenderWindow::chanDistanceFromCamera, sizeof(float)); // Add channels for normals (X, Y, Z). rw.AddChannel(IRhRdkRenderWindow::chanNormalX, sizeof(float)); rw.AddChannel(IRhRdkRenderWindow::chanNormalY, sizeof(float)); rw.AddChannel(IRhRdkRenderWindow::chanNormalZ, sizeof(float)); } ``` In the code example above, getting the render window for the first time will cause it to be created. This also causes the creation of a _render session_ which is an object that the RDK uses to keep track of rendering progress for each render window. As rendering proceeds, the render session goes through a set of states, defined by the `IRhRdkRenderSession::Status` enum. This includes states such as _Initializing_, _Rendering_ and _Completed_, among others. As long as the user sees a render frame on the screen, the render session and render window objects will exist and be associated with the render frame. When the user closes the render frame, the render session will become _disposed_. In this state, the session is waiting in a list to be deleted at the end of the command. This system prevents problems caused by deleting the session while the plug-in may still be using it, perhaps from a worker thread. At the end of the command, all disposed sessions will be deleted. ### The rendering process Being a Rhino render engine, your plug-in must include a class derived from CRhinoRenderPlugIn. When the user runs the Render command, if yours is the current render engine, Rhino will call your plug-in's `Render()` method. Your implementation of this method must instantiate your CExampleSdkRender object on the stack as a local variable and call its `Render()` method, passing the desired image size. This size can be obtained by calling the base sdkRender's `RenderSize()` method. This will return the size according to the user's settings in the Rendering panel: ```cpp CRhinoCommand::result CExampleRhinoPlugIn::Render(const CRhinoCommandContext& context, bool bPreview) { const auto* pDoc = context.Document(); if (nullptr == pDoc) return CRhinoCommand::failure; // If you need to check for a valid license, do that first. if (!CheckLicense()) return CRhinoCommand::failure; // Instantiate your SDK Render object. CExampleSdkRender sdkRender(context, *this, L"Example", IDI_EXAMPLE, bPreview); // Get the size of the image to render. const auto size = sdkRender.RenderSize(*pDoc, true); // Do the rendering. const auto result = sdkRender.Render(size); if (CRhinoSdkRender::render_ok == result) return CRhinoCommand::success; return CRhinoCommand::failure; ``` `CExampleSdkRender::Render()` can do some more 'heavy' initialization that was not done in the constructor, such as creating render meshes or opening a progress window. After that, it calls `CRhRdkSdkRender::Render()` to do the actual rendering: ```cpp CRhinoSdkRender::RenderReturnCodes CExampleSdkRender::Render(const ON_2iSize& sizeImage) { auto* pView = RhinoApp().ActiveView(); // This is old and wrong. Ask Andy. if (nullptr == pView) return CRhinoSdkRender::render_no_active_view; // Force render meshes to be created on the main thread. const auto& vp = pView->ActiveViewport().VP(); auto* pIterator = NewRenderMeshIterator(vp, true, false); pIterator->EnsureRenderMeshesCreated(); // You can now use this iterator to get all of the meshes in the scene. // While the iterator is alive, all meshes are guaranteed to be available // which means you don't need to copy them during the rendering process. CRhRdkRenderMesh rm; pIterator->Reset(); while (pIterator->Next(rm)) { // TODO: Use the mesh. This might be the point at which you create // your acceleration structure or, if you are writing a renderer // that uses its own mesh representation, you might do the // conversion here. One thing to remember - the // IRhRdkSdkRenderMeshIterator::Next function is not, at this time, // thread safe, so please don't pass the iterator's pointer into // multiple render threads and use it to query the mesh list. // In any case, it's not really optimized for in-render access. } // Once everything is set up, do the actual rendering. // By keeping the iterator alive, we ensure the meshes // don't get deleted. const auto result = __super::Render(sizeImage); // After rendering, delete the iterator and the render meshes. delete pIterator; return result; } ``` The call to `__super::Render(sizeImage)` above does the actual rendering using the Rhino render pipeline. This works by calling into virtual functions on your CExampleSdkRender object at various stages of the process. When Rhino calls the various functions on the SDK Render object, the RDK gets in between and forwards some of the calls directly to you; other calls it processes itself. The calls that are directly forwarded are the original render pipeline functions that have been in Rhino since the beginning: * `NeedToProcessLightTable()` * `AddLightToScene()` * `NeedToProcessGeometryTable()` * `IgnoreRhinoObject()` * `AddRenderMeshToScene()` * `RenderSceneWithNoMeshes()` * `RenderPreCreateWindow()` * `StartRendering()` * `RenderEnterModalLoop()` * `RenderContinueModal()` * `RenderExitModalLoop()` The first override to be called is `NeedToProcessLightTable()`. This generally returns true unless you have implemented a light cache or other optimization. Then, for each light in the scene, Rhino will call your override of `AddLightToScene()` in which you will set up your light structures for the rendering process to use. Next, Rhino calls `NeedToProcessGeometryTable()`. If you return true, Rhino will call `AddRenderMeshToScene()` for each object. If your renderer needs to set up any structures, it can do that now. If not, it does not need to override that function, but be aware that if you want rendering to proceed, you must override `RenderSceneWithNoMeshes()` (which is called next) to return true. Note that if Rhino encounters an object that is not meshable (e.g., a point or curve), it will call `IgnoreRhinoObject()`. If your renderer knows how to render objects without meshes, you can return false. Otherwise it's a good idea to return true so that the object is skipped. After lights and meshes have been processed, Rhino creates the render frame on the screen, but just before it does, it calls `RenderPreCreateWindow()`. This is probably not useful but was kept for backward compatibility. Now Rhino creates the render frame on the screen and calls `StartRendering()`. In this override, you should create one or more worker threads to do the actual rendering. Because Rhino itself is not aware of the asynchronous option (it's an RDK concept), it calls `RenderEnterModalLoop()` to ask if you want to go into a loop while waiting for rendering to finish. Unless there has been some kind of error, you must always return true (even in the asynchronous case), otherwise Rhino will abort. After that, Rhino will enter a loop calling `RenderContinueModal()` until it returns false. This is the point where synchronous and asynchronous renderers use different logic. Synchronous renderers will return true until rendering finishes, but asynchronous renderers will return false because they don't want to go into a modal loop, they want the pipeline to exit leaving the render frame open on the screen so that rendering can proceed in the background. Finally, Rhino will call `RenderExitModalLoop()` and generally your override will return true in order for Rhino to return a successful result code. ### Rendering, pausing, resuming and canceling In the synchronous case, rendering proceeds in one or more worker threads while Rhino sits in the modal loop pumping messages and keeping the UI alive. During this time, the user can use the menu and tool bars in the render frame. The RDK calls the `SupportsPause()` method on your sdkRender object to determine if the pause button should be enabled on the screen. If you return true, you will be expected to also implement `PauseRendering()` and `ResumeRendering()`. These functions can be as simple as setting and clearing a flag which the render threads check periodically. Pausing is most useful when the renderer is asynchronous because the user can continue using Rhino while rendering is paused and continue rendering later. If the user chooses the _Stop_ option, `StopRendering()` will be called on your sdkRender object. Your override should take whatever action is necessary to immediately stop rendering. It should tell any render threads to abort and wait for them to exit before returning. If a thread continues after this function returns, Rhino may crash because it assumes that the client is no longer using any of the rendering objects or meshes. ### Asynchronous rendering We touched on the differences between synchronous and asynchronous rendering above, but now we will examine them in more detail. One of the main differences with asynchronous rendering is the existence of an object called an _async render context_. This is represented by the `IRhRdkAsyncRenderContext` interface. It is an object that takes over the role of the sdkRender object after that object goes off the stack. Recall that as soon as asynchronous rendering begins, the renderer asks Rhino to _not_ continue the modal loop. This causes Rhino to exit the render pipeline and the sdkRender object goes off the stack and is deleted. The async render context persists, owned by the associated render session and enables communication between the RDK and the render engine, allowing requests such as pause, resume and stop to pass to the renderer in the absence of the sdkRender object. The async render context should be created in your sdkRender constructor, before any access to the render session or render window. It is the call to `SetAsyncRenderContext()` that establishes the render session as being asynchronous rather than synchronous. ```cpp CExampleSdkRender::CExampleSdkRender(const CRhinoCommandContext& context, CRhinoRenderPlugIn& plugIn, const ON_wString& sCaption, UINT uIconId, bool bPreview) : CRhRdkSdkRender(context, plugIn, sCaption, id) { // It is critical that the render context is created first, before any calls to GetRenderWindow(). SetAsyncRenderContext(new CExampleAsyncRenderContext(...)); // Get the render window. auto& rw = GetRenderWindow(); // Note: GetRenderWindow() uses the async render context set above. ... } ``` The implementation of `IRhRdkAsyncRenderContext` is usually fairly simple. An example is shown below. You just have to handle a few functions for pausing, resuming and stopping rendering. This Windows-centric example assumes you have a single thread and you have stored its handle in `m_hRenderThread`. ```cpp class CExampleAsyncRenderContext : public IRhRdkAsyncRenderContext { public: CExampleAsyncRenderContext(...); virtual ~CExampleAsyncRenderContext(); public: // Implement IRhRdkAsyncRenderContext. virtual void StopRendering(void) override; virtual bool SupportsPause(void) const override { return true; } virtual void PauseRendering(void) override { m_bPause = true; } virtual void ResumeRendering(void) override { m_bPause = false; } virtual void OnQuietRenderFinished(const IRhRdkRenderSession&) override { } // Not currently used. virtual void DeleteThis(void) override { delete this; } virtual void* EVF(const wchar_t*, void*) override { return nullptr; } private: HANDLE m_hRenderThread = NULL; bool m_bPause = false; bool m_bCancel = false; }; void CExampleAsyncRenderContext::StopRendering(void) { // Because the cancel flag is in this object, the render thread(s) // will need access to the object so they can check the flag. // If rendering is in progress, cancel it and wait for it to stop. if (NULL != m_hRenderThread) { m_bCancel = true; ::WaitForSingleObject(m_hRenderThread, INFINITE); ::CloseHandle(m_hRenderThread); m_hRenderThread = NULL; } } ``` When rendering ends, the render frame remains on the screen. The user can choose to save the rendering, view the different channels, apply exposure or post-effects, or close the render frame. The render session associated with this render frame is now in one of the following states: * _Completed_ ~ Rendering has completed successfully. * _Canceled_ ~ Rendering was canceled by the user. * _Aborted_ ~ Rendering was aborted. Happens when an async render aborts because the document is closed. * _Failed_ ~ Rendering failed (but not because it was canceled). The user can also choose to clone the render frame. What this means under the hood is that the render session will be cloned and a new render frame will be opened for that session. This allows the user to compare the renderings while viewing different channels or using different exposures or post-effects. Eventually, the user will close the render frame (or close Rhino, which will, of course, close all render frames). When the render frame is closed, the render session goes into the 'Disposed' state, ready to be deleted at the end of the next command. If the user closes the render frame while rendering is underway, `StopRendering()` will be called before the render frame is closed. ### Adding a custom tab to the render frame _This section only applies to the Windows OS._ Custom tabs can be added to the render frame by creating a subclass of `CRhRdkRenderFrameTabFactory` and registering it with the RDK in your override of `CRhRdkRenderPlugIn::RegisterExtensions()`. The tab factory implements NewTab() to create an instance of the tab. Once this is done, all render frames will include your custom tab if yours is the current render engine. ```cpp class CExampleRenderFrameTabFactory : public CRhRdkRenderFrameTabFactory { public: virtual CRhinoUiDockBarTab* NewTab(IRhRdkRenderWindow& rw) const override; virtual UUID PlugInId(void) const override { return your_plug_in_id_here; } virtual UUID RenderEngineId(void) const override { return your_render_engine_id_here; } virtual UUID TabId(void) const override { return your_tab_id_here; }; }; ``` In `NewTab()`, your factory must create an object that is derived from `CRhinoUiDockBarTab`. This object is a wrapper around a window which it manages: ```cpp class CExampleRenderWindowTab : public CRhinoUiDockBarTab, public ITabbedDockBarEventWatcher { public: CExampleRenderWindowTab(IRhRdkRenderWindow& rw) : m_RW(rw) { } // Return a caption for the tab. virtual ON_wString Caption(void) const override { return L"Example"; } // Create your window here. virtual bool CreateWnd(void) override; // Move your window to a position in client coords. virtual void MoveWnd(const ON_4iRect& rect) override; // Show or hide your window (uState is SH_SHOW or SW_HIDE). virtual void ShowWnd(UINT uState) override; // Destroy your window handle. virtual void DestroyWnd(void) override; // Return true if your window handle is valid, else false. virtual bool IsCreated(void) const override; // Return an icon for the tab. virtual HICON Icon(const ON_2iSize& sizeInPixels) const override; virtual void DeleteThis(void) override { delete this; } virtual UUID TabId(void) const override { return your_tab_id_here; } // Display help for your dialog. virtual void DoHelp(void) const override { } // Called when the current tab changes and when the user docks or undocks the dock bar. virtual void SwitchDockBar() override { } virtual ITabbedDockBarEventWatcher* TabbedDockBarEventWatcher(void) const override { // It's easiest to inherit this object from ITabbedDockBarEventWatcher // instead of creating a separate object. return const_cast(this); } protected: // Implement ITabbedDockBarEventWatcher. virtual bool OnDockContextStartDrag(bool bStart) override { return false; } // TODO: virtual void OnToggleDocking(bool bStart) override { } // TODO: virtual void OnStartTracking(bool bDoneTracking) override { } // TODO: virtual void OnDockBarPositionChanged(DWORD dwNewLocation) override { } // TODO: virtual void OnShowDockBar(ShowEventArgs args) override { } // TODO: virtual void OnShowTab(const class CRhinoUiPanel& panel, bool bShowTab, const ON_UUID& uuidDockBar) override { } // TODO: private: IRhRdkRenderWindow& m_RW; }; CRhinoUiDockBarTab* CExampleRenderFrameTabFactory::NewTab(IRhRdkRenderWindow& rw) const { return new CExampleRenderWindowTab(rw); } ``` -------------------------------------------------------------------------------- # RDK Safe Frame Source: https://developer.rhino3d.com/en/guides/cpp/rdk-safe-frame-classes/ This document describes how to use the RDK safe frame class in C/C++. ### Introduction A _Safe Frame_ is a guide to help ensure that the most important elements of a scene will appear inside a certain region of the rendered image. The name comes from movie and TV production where a camera operator sees one or more rectangles in the camera's viewfinder which shows limits inside which an actor or prop is guaranteed to be visible on all viewer's screens. ![SafeFrame](https://developer.rhino3d.com/images/rdk-safeframe.jpg) ### The Document Safe Frame The _RDK Document Safe Frame_ is a document-resident safe frame which can be displayed in viewports. If you have a Rhino document, you can read and write that document's safe frame through the document's [IRhRdkSafeFrame](/api/cpp/class_i_rh_rdk_safe_frame.html) interface. Any changes you make will appear in the Safe Frame UI and will also be stored in the 3dm file. Getting the safe frame from a document always returns a const reference. To write to the safe frame, you must begin a batch of write operations and afterwards end the batch. This is done using the RDK's standard BeginChange / EndChange system. The following is an example of how to access and change the document safe frame: ```cpp static class CSafeFrameExampleCommand : public CRhinoTestCommand { protected: virtual UUID CommandUUID() override { static const UUID uuid = your_uuid_here; return uuid; } virtual const wchar_t* EnglishCommandName() override { return RHSTR_LIT(L"MySafeFrameCmd"); } virtual CRhinoCommand::result RunCommand(const CRhinoCommandContext& context) override; } theSafeFrameExampleCommand; CRhinoCommand::result CSafeFrameExampleCommand::RunCommand(const CRhinoCommandContext& context) { auto* pDoc = context.Document(); if (nullptr == pDoc) return failure; const auto& sf = pDoc->SafeFrame(); RhinoApp().Print(L"Safe frame before: %s\n", sf.On() ? L"on" : L"off"); auto& write_sf = sf.BeginChange(RhRdkChangeContext::Program); write_sf.SetOn(false); write_sf.EndChange(); RhinoApp().Print(L"Safe frame after: %s\n", sf.On() ? L"on" : L"off"); return success; } ``` -------------------------------------------------------------------------------- # RDK Skylight Source: https://developer.rhino3d.com/en/guides/cpp/rdk-skylight-classes/ This document describes how to use the RDK skylight class in C/C++. ### Introduction The _skylight_ is a feature that allows a scene to be rendered realistically, as if the objects in the scene were in a real environment under a real sky. When the skylight is used, the objects in the scene are lit not only by the scene's lights (or the sun), but also by the environment. The image below shows a comparison of a scene rendered with the skylight disabled (left) and enabled (right). Notice the subtly different coloring and the softer more diffuse shadows in the sky-lit image. The disadvantage of the skylight is that it is very CPU-intensive and renderings are much slower when it is enabled. ![Road](https://developer.rhino3d.com/images/rdk-skylight.jpg) ### The Document Skylight The _RDK Document Skylight_ is a document-resident skylight which affects viewports and renderings. If you have a Rhino document, you can read and write that document's skylight through the document's [IRhRdkSkylight](/api/cpp/class_i_rh_rdk_skylight.html) interface. Any changes you make will appear in the Lighting section of the Rendering panel and will also be stored in the 3dm file. Getting the skylight from a document always returns a const reference. To write to the skylight, you must begin a batch of write operations and afterwards end the batch. This is done using the RDK's standard BeginChange / EndChange system. The following is an example of how to access and change the document skylight: ```cpp static class CSkylightExampleCommand : public CRhinoTestCommand { protected: virtual UUID CommandUUID() override { static const UUID uuid = your_uuid_here; return uuid; } virtual const wchar_t* EnglishCommandName() override { return RHSTR_LIT(L"MySkylightCmd"); } virtual CRhinoCommand::result RunCommand(const CRhinoCommandContext& context) override; } theSkylightExampleCommand; CRhinoCommand::result CSkylightExampleCommand::RunCommand(const CRhinoCommandContext& context) { auto* pDoc = context.Document(); if (nullptr == pDoc) return failure; const auto& sl = pDoc->Skylight(); RhinoApp().Print(L"Skylight before: %s\n", sl.On() ? L"on" : L"off"); auto& write_sl = sl.BeginChange(RhRdkChangeContext::Program); write_sl.SetOn(false); write_sl.EndChange(); RhinoApp().Print(L"Skylight after: %s\n", sl.On() ? L"on" : L"off"); return success; } ``` -------------------------------------------------------------------------------- # RDK Sun Source: https://developer.rhino3d.com/en/guides/cpp/rdk-sun-classes/ This document describes how to use the RDK sun classes in C/C++. ### Introduction The angle and color of sunlight at various times of the day changes drastically with the location on Earth, the time of day and the time of year. When rendering an outdoor scene, the angle and color of the sunlight can be a very important part of the result. Buildings and other objects might be designed and placed to look aesthetically pleasing or have certain highlights at certain times of the day or year. To facilitate this kind of visualization, the RDK provides a comprehensive set of sun tools which allow the plug-in developer to do sun calculations and display a sun user interface. ![Buildings](https://developer.rhino3d.com/images/rdk-sun-buildings.jpg) Costa Mesa, CA., USA ~ Turku Castle, Finland ~ Downtown Austin, TX., USA ~ Photos by John Croudy. ### IRhRdkSun _IRhRdkSun_ is an abstract sun interface. It provides access to all the properties of a sun. It can be used to modify the RDK Document Sun or any temporary 'working' sun you might have access to. ### The RDK Document Sun The _RDK Document Sun_ is a document-resident sun which affects viewports and renderings. If you have a Rhino document, you can read and write that document's sun through the document's IRhRdkSun interface. Any changes you make will appear in the main sun UI and will also be stored in the 3dm file. Getting the sun from a document always returns a const reference. To write to the sun, you must begin a batch of write operations and afterwards end the batch. This is done using the RDK's standard BeginChange / EndChange system: ```cpp // Read some information from the document sun. const auto& sun = pDoc->Sun(); const bool b = sun.EnableOn(); ... // Read other properties here. // Change some properties of the document sun. auto& write_sun = sun.BeginChange(RhRdkChangeContext::Program); write_sun.SetEnableOn(true); ... // Set other properties here. write_sun.EndChange(); ``` ### CRhRdkSun _CRhRdkSun_ is a simple sun object that can be placed on the stack or used as a class member. It can be used as a temporary 'working' sun and it provides access to an underlying implementation of IRhRdkSun. You can use this to do sun angle calculations without affecting the document sun. This might be useful, for example, to create an ephemeris or some other table of sun information. The following is a test command that uses CRhRdkSun to generate a display of the monthly sun position for the year 2025 at Seattle, Washington, USA. ```cpp static class CTestSunEphemeris : public CRhinoTestCommand { protected: virtual UUID CommandUUID() override { static const UUID uuid = { your_uuid_here } }; return uuid; } virtual const wchar_t* EnglishCommandName() override { return RHSTR_LIT(L"SunEphemeris"); } virtual CRhinoCommand::result RunCommand(const CRhinoCommandContext& context) override; } theTestSunEphemerisCmd; CRhinoCommand::result CTestSunEphemeris::RunCommand(const CRhinoCommandContext& context) { auto* pDoc = context.Document(); if (nullptr == pDoc) return failure; // Make a temporary sun and set some properties. CRhRdkSun s; auto& sun = s.Sun(); sun.SetManualControlOn(false); // Set the observer's location to Seattle, Washington, USA. sun.SetLatitude(47.606); sun.SetLongitude(-122.331); sun.SetTimeZone(-8.0); static const wchar_t* aMonth[12] = { L"Jan", L"Feb", L"Mar", L"Apr", L"May", L"Jun", L"Jul", L"Aug", L"Sep", L"Oct", L"Nov", L"Dec" }; // Display sun horizon coordinates for Seattle at noon on the first day // of each month of 2025 and add a light to show the direction. int day = 1, year = 2025; for (int i = 0; i < 12; i++) { // Set the date and time. Disregard daylight saving time. const int month = i + 1; sun.SetLocalDateTime(year, month, day, 12.0); RhinoApp().Print(L"Date: %04u.%02u.%02u - Sun Azimuth: %.1f, Sun Altitude: %.1f\n", year, month, day, sun.Azimuth(), sun.Altitude()); // Add a light to the document to show the position. auto light = sun.Light(); const double angle = 2.0 * ON_PI * (2 - i) / 12.0; light.Translate(ON_3dVector(10.0 * cos(angle), 10.0 * sin(angle), 0.0)); pDoc->m_light_table.AddLight(light); // Add text for the month. const auto point = ON_3dPoint(14.0 * cos(angle) - 1.3, 14.0 * sin(angle) + 0.5, 0.0) pDoc->AddTextObject(aMonth[i], ON_Plane(), point); } pDoc->DeferredRedraw(); return success; } ``` ### CRhRdkSunDialog _CRhRdkSunDialog_ is a wrapper around a sun UI. This dialog works with a _data source_, which is a platform-independent way of getting and setting the back-end data that a UI displays and edits. To make it easier to use without getting too deeply into data sources, the RDK provides `CRhRdkSimpleSunDataSource` which can be used with this dialog. The following code shows how to use the dialog to edit a temporary sun on the stack: ```cpp CRhRdkSun s; // Temporary working sun. auto& sun = s.Sun(); // Get temporary IRhRdkSun. // You can edit any non-const instance of IRhRdkSun. CRhRdkSimpleSunDataSource ds; // Copy working sun data to the data source. ds.Sun().CopyFrom(sun); // Set up a controller with this data source attached. const auto con = IRhinoUiController::make_shared(new CRhRdkGenericController); con->AddDataSource(ds); // Launch the dialog using the controller. CRhRdkSunDialog dlg; dlg.SetController(con); if (IDOK != dlg.DoModal()) return cancel; // User cancelled. // Copy edited sun data back to the working sun. sun.CopyFrom(ds.Sun()); ... // Do something with the updated working sun here. ``` -------------------------------------------------------------------------------- # RDK Tasks Source: https://developer.rhino3d.com/en/guides/cpp/rdk-task-classes/ This document describes how to use the RDK task classes in C/C++. ### Introduction An RDK _task_ encapsulates the functionality of any operation the user can perform by clicking a menu item or pressing a key in a Render Content Editor. For example, when the user right-clicks in the preview area of such an editor, a context menu is displayed with commands such as 'Assign to Objects' and 'Delete'. All of these menu items are implemented by tasks, derived from [CRhRdkTask](/api/cpp/class_c_rh_rdk_task.html). To make it easy for plug-in developers to add their own tasks to these menus, the RDK provides the class [CRhRdkCustomTask](/api/cpp/class_c_rh_rdk_custom_task.html). The developer only has to implement a subclass of this and then register the subclass with the RDK. An example of how to do this is shown below. Tasks use an interface called [IRhRdkTaskOrigin](/api/cpp/class_i_rh_rdk_task_origin.html) which represents the place in the UI where the user clicked to invoke the menu. Among other things, this interface allows the task to get the contents that are currently selected in the UI. These are the contents that the user wants to perform an operation on. ### Registering custom tasks To register a custom task, you first need to derive a class from CRhRdkCustomTask. You provide a menu _string_ that is displayed to the user, a menu _order_ which tells the RDK where on the menu to put the item, and one (or optionally two) _icons_. You also implement an Update() method and an Execute() method. ```cpp class CExampleCustomTask : public CRhRdkCustomTask { public: virtual UUID Id(void) const override { static const UUID uuid = your_uuid_here; return uuid; } virtual UUID PlugInId(void) const final override { return your_plug_in_uuid_here; } virtual bool IsEnabled(const IRhRdkTaskOrigin& origin) const override { return true; } virtual const wchar_t* MenuString(const IRhRdkTaskOrigin&, CRhRdkContent::Kinds) const override { return L"Example Custom Task"; } virtual bool IconOut(CRhRdkContent::Kinds kind, int w, int h, OUT CRhinoDib& dib) const override { return false; } virtual bool IconIn(CRhRdkContent::Kinds kind, int w, int h, OUT CRhinoDib& dib) const override; virtual void Update(IRhRdkTaskUpdate& tu) const override; virtual Result Execute(const IRhRdkTaskOrigin&) const override; virtual int MenuOrder(const IRhRdkTaskOrigin& origin) const override; }; int CExampleCustomTask::MenuOrder(const IRhRdkTaskOrigin& origin) const { // This value should be below 100 to make your task appear // before all the RDK's tasks, and above 10,000 to make it // appear below all the RDK's tasks. After that, simply use // numbers in the order you want the tasks to appear. return 10; } bool CExampleCustomTask::IconIn(CRhRdkContent::Kinds kind, int width, int height, OUT CRhinoDib& dib) const { // Depending on the 'kind' you might want to use different icons. // Use the supplied 'width' and 'height' to load an icon by // whatever means your platform allows, and set the icon into // the supplied 'dib'. The following just uses a simple check mark. return ::RhRdkGetMenuIcon(RhRdkMenuIcons::Check, ON_2iSize(width, height), dib); } void CExampleCustomTask::Update(IRhRdkTaskUpdate& tu) const override { // This is called when the user opens the menu. Here you can set if the // item is enabled or disabled, and if the item is checked or checked // like a radio button. Note that there is also an IsEnabled() method // on the task. Update() is only called if IsEnabled() returns true. // Generally it is best to have IsEnabled() return true and implement // Update() for finer control. tu.SetIsChecked(...); // or tu.SetRadio(...); tu.SetIsEnabled(...); } CRhRdkTask::Result CExampleCustomTask::Execute(const IRhRdkTaskOrigin& origin) const { // This is called when the user chooses the menu item associated with this task. CRhRdkContentArray aContent; origin.GetSelection(aContent); for (int i = 0; i < aContent.Count(); i++) { const auto sName = aContent[i]->InstanceName(); RhinoApp().Print(L"Selected: %s\n", static_cast(sName)); } return Result::Success; } ``` Finally, in your override of `CRhRdkPlugIn::RegisterExtensions()`, you need to register your custom task as an RDK extension: ```cpp void CMyRdkPlugIn::RegisterExtensions(void) const { ... AddExtension(new CExampleCustomTask); ... ``` -------------------------------------------------------------------------------- # Read & Write UTF-8 Files Source: https://developer.rhino3d.com/en/guides/rhinoscript/read-write-utf8/ This brief guide demonstrates how to read and write UTF-8 encoded text files using VBScript. ## Problem If you have a text file saved as UTF-8, sometimes - when you read the file - it reads in weird characters and not the correct characters. This happens often when the files contain Chinese characters. How can you make it read the correct characters? ## Solution The [File System Object](http://msdn.microsoft.com/en-us/library/aa242706(v=vs.60).aspx), generally used by VBScript developers to read and write text files, can read only ASCII or Unicode text files. You cannot use it to read or write UTF-8 encoded text files. But, if you can use [Microsoft ActiveX Data Objects (ADO)](http://msdn.microsoft.com/en-us/library/windows/desktop/ms676526%28v=vs.85%29.aspx), you can read UTF-8 encoded text files like this: ```vbnet Dim objStream, strData Set objStream = CreateObject("ADODB.Stream") objStream.CharSet = "utf-8" objStream.Open objStream.LoadFromFile("C:\Users\admin\Desktop\test.txt") strData = objStream.ReadText() ``` If you want to write a UTF-8 encode text file, you can do so like this: ```vbnet Dim objStream Set objStream = CreateObject("ADODB.Stream") objStream.CharSet = "utf-8" objStream.Open objStream.WriteText "The data I want in utf-8" objStream.SaveToFile "C:\Users\admin\Desktop\test.txt", 2 ``` ## Related Topics - [File System Object on MSDN](http://msdn.microsoft.com/en-us/library/aa242706(v=vs.60).aspx) - [Microsoft ActiveX Data Objects (ADO) on MSDN](http://msdn.microsoft.com/en-us/library/windows/desktop/ms676526%28v=vs.85%29.aspx) -------------------------------------------------------------------------------- # Read Dimension Text Source: https://developer.rhino3d.com/en/samples/rhinocommon/read-dimension-text/ Demonstrates how to read dimension text from an annotation object. ```cs partial class Examples { public static Result ReadDimensionText(RhinoDoc doc) { var go = new GetObject(); go.SetCommandPrompt("Select annotation"); go.GeometryFilter = ObjectType.Annotation; go.Get(); if (go.CommandResult() != Result.Success) return Result.Failure; var annotation = go.Object(0).Object() as AnnotationObjectBase; if (annotation == null) return Result.Failure; RhinoApp.WriteLine("Annotation text = {0}", annotation.DisplayText); return Result.Success; } } ``` ```python # No Python sample available ``` -------------------------------------------------------------------------------- # Reading Excel Files Source: https://developer.rhino3d.com/en/guides/rhinoscript/reading-excel-files/ This brief guide demonstrates how to read a Microsoft Excel file from RhinoScript. ## Problem You would like to read a Microsoft Excel file from RhinoScript into an array that can be accessed in Rhino. ## Solution The following general purpose function will read an Excel worksheet into a two-dimensional array... ```vbnet ' Description: ' Reads a Microsoft Excel file. ' Parameters: ' strFile - [in] The name of the Excel file to read. ' Returns: ' A two-dimension array of cell values, if successful. ' Null on error Option Explicit Function ReadExcelFile(ByVal strFile) ' Local variable declarations Dim objExcel, objSheet, objCells Dim nUsedRows, nUsedCols, nTop, nLeft, nRow, nCol Dim arrSheet() ' Default return value ReadExcelFile = Null ' Create the Excel object On Error Resume Next Set objExcel = CreateObject("Excel.Application") If (Err.Number <> 0) Then Exit Function End If ' Don't display any alert messages objExcel.DisplayAlerts = 0 ' Open the document as read-only On Error Resume Next Call objExcel.Workbooks.Open(strFile, False, True) If (Err.Number <> 0) Then Exit Function End If ' If you wanted to read all sheets, you could call ' objExcel.Worksheets.Count to get the number of sheets ' and the loop through each one. But in this example, we ' will just read the first sheet. Set objSheet = objExcel.ActiveWorkbook.Worksheets(1) ' Get the number of used rows nUsedRows = objSheet.UsedRange.Rows.Count ' Get the number of used columns nUsedCols = objSheet.UsedRange.Columns.Count ' Get the topmost row that has data nTop = objSheet.UsedRange.Row ' Get leftmost column that has data nLeft = objSheet.UsedRange.Column ' Get the used cells Set objCells = objSheet.Cells ' Dimension the sheet array ReDim arrSheet(nUsedRows - 1, nUsedCols - 1) ' Loop through each row For nRow = 0 To (nUsedRows - 1) ' Loop through each column For nCol = 0 To (nUsedCols - 1) ' Add the cell value to the sheet array arrSheet(nRow, nCol) = objCells(nRow + nTop, nCol + nLeft).Value Next Next ' Close the workbook without saving Call objExcel.ActiveWorkbook.Close(False) ' Quit Excel objExcel.Application.Quit ' Return the sheet data to the caller ReadExcelFile = arrSheet End Function ``` You can use this function to dump the contents of a spreadsheet to Rhino's command line: ```vbnet Sub ExcelDumper() ' Local variable declarations Dim strFile, arrSheet, i, j, varCell, strFormat ' Prompt for the Excel file to read strFile = Rhino.OpenFileName("Open", "Excel Files (*.xls)|*.xls|") If IsNull(strFile) Then Exit Sub ' Read the Excel file arrSheet = ReadExcelFile(strFile) If IsNull(arrSheet) Then Exit Sub ' Dump the worksheet to the command line For i = 0 To UBound(arrSheet, 1) For j = 0 To UBound(arrSheet, 2) strFormat = "Sheet(" & CStr(i) & "," & CStr(j) & ") = " varCell = arrSheet(i, j) If IsEmpty(varCell) Then Rhino.Print strFormat & "" Else Rhino.Print strFormat & CStr(varCell) End If Next Next End Sub ``` -------------------------------------------------------------------------------- # Reading Notes from a 3dm Source: https://developer.rhino3d.com/en/samples/opennurbs/read-notes-from-3dm/ Demonstrates how to read the user-added notes field from a 3DM file using either C/C++ or the openNURBS toolkit. ```cpp bool ReadNotesFromRhino3dmFile( const wchar_t* filename, ON_wString& notes ) { if( 0 == filename || 0 == filename[0] ) return false; // STEP 1: Open the file FILE* archive_fp = ON::OpenFile( filename, L"rb" ); if( 0 == archive_fp ) return false; // STEP 2: Create a binary archive object ON_BinaryFile archive( ON::read3dm, archive_fp ); // STEP 3: Read 3dm start section int file_version = 0; ON_String start_section_comments; if( !archive.Read3dmStartSection(&file_version, start_section_comments) ) { ON::CloseFile( archive_fp ); return false; } // STEP 4: Read 3dm properties section ON_3dmProperties properties; if( !archive.Read3dmProperties(properties) ) { ON::CloseFile( archive_fp ); return false; } // STEP 5: Close the file ON::CloseFile( archive_fp ); // return the notes notes = properties.m_Notes.m_notes; return true; } ``` -------------------------------------------------------------------------------- # Reading Per-Face Render Materials Source: https://developer.rhino3d.com/en/guides/opennurbs/reading-per-face-render-materials/ This brief guide describes how to read render materials from Brep faces using the openNURBS toolkit. New in Rhino 6 is per-Brep face render material assignment. This eliminates the step of extracting faces just to change the material required in prior versions. The `ON_BrepFace:m_face_material_channel` property provides a way to have individual Brep faces use a rendering material that is different from the rendering material used by the parent Brep. If `ON_BrepFace:m_face_material_channel` is zero, which is the default value, then the face will use the parent Brep's rendering material. if `ON_BrepFace:m_face_material_channel` is greater than zero, then his value can use used to obtain the face's rendering material id from the parent Brep's rendering material's channel index array. If you are referencing the `Example_read` sample included with the openNURBS toolkit, then after the 3DM file has been read, you can query an object's render material as follows: ```cpp ONX_Model model = ..... ONX_ModelComponentIterator it(model, ON_ModelComponent::Type::ModelGeometry); const ON_ModelComponent* model_component = nullptr; for (model_component = it.FirstComponent(); nullptr != model_component; model_component = it.NextComponent()) { const ON_ModelGeometryComponent* model_geometry = ON_ModelGeometryComponent::Cast(model_component); if (nullptr == model_geometry) continue; const ON_Brep* brep = ON_Brep::Cast(model_geometry->Geometry(nullptr)); const ON_3dmObjectAttributes* attributes = model_geometry->Attributes(nullptr); if (nullptr == brep || nullptr == attributes) continue; const ON_Material* material = ModelGeometryRenderMaterial(model, *attributes); if (nullptr != material) { for (int fi = 0; fi < brep->m_F.Count(); fi++) { const ON_Material* face_material = nullptr; const ON_BrepFace& face = brep->m_F[fi]; const int channel = face.m_face_material_channel; if (channel > 0 && channel < material->m_material_channel.Count()) { const ON_UuidIndex& idx = material->m_material_channel[channel - 1]; face_material = ModelGeometryRenderMaterial(model, idx.m_id); } if (nullptr == face_material) face_material = material; if (nullptr != face_material) { // Dump the diffuse color, for example. ON_Color color = face_material->Diffuse(); // TODO... } } } } /* Helper function to return an ON_Material, given an ON_ObjectAttributes. */ static const ON_Material* ModelGeometryRenderMaterial( const ONX_Model& model, const ON_3dmObjectAttributes& attributes ) { ON_ModelComponentReference rc = model.RenderMaterialFromAttributes(attributes); if (!rc.IsEmpty()) return ON_Material::Cast(rc.ModelComponent()); return nullptr; } /* Helper function to return an ON_Material, given a material id. */ static const ON_Material* ModelGeometryRenderMaterial( const ONX_Model& model, const ON_UUID& material_id ) { ON_ModelComponentReference rc = model.RenderMaterialFromId(material_id); if (!rc.IsEmpty()) return ON_Material::Cast(rc.ModelComponent()); return nullptr; } ``` -------------------------------------------------------------------------------- # Reading Render Meshes Source: https://developer.rhino3d.com/en/guides/opennurbs/reading-render-meshes/ This brief guide describes how to read render meshes using the openNURBS toolkit. If you are developing software to read .3dm files, you might find that the software only *seems* to read NURBS data; but render meshes are ignored. We do provide methods for third-party developers to read render meshes from .3dm files. An object's render meshes are stored on that object. For example, the render meshes for `ON_Brep` and `ON_Extrusion` objects are stored on that object. The developer can obtain an object's render meshes from a Brep by calling `ON_Brep::GetMesh` and from an Extrusion by calling `ON_MeshCache::Mesh`. If you are referencing the `Example_read` sample included with the openNURBS toolkit, then after the 3DM file has been read, you can obtain the render meshes from the `ONX_Model` object as follows: ```cpp ONX_Model model = ... ONX_ModelComponentIterator it(model, ON_ModelComponent::Type::ModelGeometry); const ON_ModelComponent* model_component = nullptr; for (model_component = it.FirstComponent(); nullptr != model_component; model_component = it.NextComponent()) { const ON_ModelGeometryComponent* model_geometry = ON_ModelGeometryComponent::Cast(model_component); if (nullptr != model_geometry) { // Test for mesh object const ON_Mesh* mesh = ON_Mesh::Cast(model_geometry->Geometry(nullptr)); if (nullptr != mesh) { // TODO: do something with ON_Mesh object... continue; } // Test for Brep object const ON_Brep* brep = ON_Brep::Cast(model_geometry->Geometry(nullptr)); if (nullptr != brep) { ON_SimpleArray meshes(brep->m_F.Count()); const int mesh_count = brep->GetMesh(ON::render_mesh, meshes); if (mesh_count > 0) { // TODO: do something with array of ON_Mesh objects... } continue; } // Test for extrusion object const ON_Extrusion* extrusion = ON_Extrusion::Cast(model_geometry->Geometry(nullptr)); if (nullptr != extrusion) { const ON_Mesh* mesh = extrusion->m_mesh_cache.Mesh(ON::render_mesh); if (nullptr != mesh) { // TODO: do something with ON_Mesh object... } continue; } } } ``` -------------------------------------------------------------------------------- # Reading Subdivision Surfaces Source: https://developer.rhino3d.com/en/guides/opennurbs/reading-subdivision-surfaces/ This brief guide describes how to read subdivision surfaces using the openNURBS toolkit. ## Overview Rhino SubD objects are high-precision Catmull-Clark subdivision surfaces. They can have creases, sharp or smooth corners, and holes. The Rhino SubD object is designed to quickly model and edit complex organic shapes. Unlike traditional mesh-based SubD implementations, Rhino SubD objects are NOT a subdivided mesh object. The Rhino SubD user experience will be the same as Rhino NURBS and mesh object experience. There will also be new SubD modeling and editing tools based on traditional techniques. Rhino SubD surfaces are predictable, measurable, and manufacturable. They can be converted to either high-quality NURBS or mesh (quads or triangles) objects when needed. Rhino SubD objects will be supported in all Rhino export formats that support either meshes or NURBS including IGES, STEP, OBJ, and STL. ## openNURBS and ON_SubD Rhino SubD object are defined on openNURBS in the `ON_SubD` class. See `opennurbs_subd.h`, in the openNURBS source code, for details. Like other Rhino objects, the `ON_SubD` class inherits from `ON_Geometry`. Thus when you are reading 3DM files, you can use `ONX_ModelComponentIterator` object to look for SubD object just like you would if you were looking for other types of geometry, such as curves, Breps, and meshes. The following code sample demonstrates how to iterate an `ONX_Model` object and look for `ON_SubD` objects. When found, the control net mesh (or input mesh used to calculate a subdivision surface) is obtained. ```cpp ONX_Model model = ... ONX_ModelComponentIterator it(model, ON_ModelComponent::Type::ModelGeometry); const ON_ModelComponent* model_component = nullptr; for (model_component = it.FirstComponent(); nullptr != model_component; model_component = it.NextComponent()) { const ON_ModelGeometryComponent* model_geometry = ON_ModelGeometryComponent::Cast(model_component); if (nullptr != model_geometry) { // Test for subd object const ON_SubD* subd = ON_SubD::Cast(model_geometry->Geometry(nullptr)); if (nullptr != subd) { // Get control net mesh ON_Mesh* mesh = subd->GetControlNetMesh(nullptr, ON_SubDGetControlNetMeshPriority::Geometry); if (nullptr != mesh) { // TODO: do something with mesh delete mesh; // don't leak memory } } } } ``` The control net is mesh generally coarse and not acceptable for use for rendering, rapid prototyping, and other. So the version of openNURBS, included with Rhino, contains two powerful functions that are useful for converting SubD objects to Breps or smooth meshes: ```cpp // Gets a ON_Brep representation the subdivision limit surface ON_SubD::GetSurfaceBrep() // Get a ON_Mesh representation of the subdivision limit surface ON_SubD::GetSurfaceMesh() ``` These two function are used internally by Rhino when exporting to file formats that do not support SubD objects. However, these two functions **are not available** in the free, publicly available openNURBS toolkit. Rhino SubD objects are 100% "industry standard" and Rhino evaluation results comply 100% with public domain algorithms widely described in published technical literature. Please see the [Rhino SubD Rules](https://docs.google.com/document/d/13QkEGz9SedvauQQegiZ2HXSKOiwn0INVO4FxGlfvRps) document for complete details about the subdivision algorithm. If you already have actually have code that correctly meshes and performs the "to NURBS" conversion on Catmull-Clark subdivision surfaces, then you should use it. If you don't have this capability, then you might try subdividing the SubD object before acquiring the control net mesh. Here is an example: ```cpp const ON_SubD* subd = ON_SubD::Cast(model_geometry->Geometry(nullptr)); if (nullptr != subd) { ON_SubD* new_subd = subd->Duplicate(); if (nullptr != new_subd) { // The number of subdivisions you require const int count = 3; // Apply the Catmull-Clark subdivision algorithm and save the results in this ON_SubD new_subd->GlobalSubdivide(count); // Get control net mesh ON_Mesh* mesh = new_subd->GetControlNetMesh(nullptr, ON_SubDGetControlNetMeshPriority::Geometry); if (nullptr != mesh) { // TODO: do something with mesh delete mesh; // don't leak memory } delete new_subd; // don't leak memory } } ``` ## Related Topics - [What is openNURBS?](/guides/opennurbs/what-is-opennurbs) - [Rhino SubD Rules](https://docs.google.com/document/d/13QkEGz9SedvauQQegiZ2HXSKOiwn0INVO4FxGlfvRps) -------------------------------------------------------------------------------- # Registering Plugins (Windows) Source: https://developer.rhino3d.com/en/guides/rhinocommon/registering-plugins-windows/ This guide provides instructions for registering plugins for Windows. ## Overview While a Rhino plugin can simply be distributed as a *.rhp* file, and loaded using Rhino's *PlugInManager* command, it is often necessary to install the plugin as part of a product installation process. To install plugin from an installer, your installer will be required to access a number of entries in the Windows Registry. While reading this article, it is helpful to use the standard Windows Registry editing tool, *REGEDIT.EXE*, to follow along and see how Rhino's Registry entries are structured. ## Finding the Rhino Registry Key Rhino plugins are registered at the following location in the Windows Registry: `HKEY_LOCAL_MACHINE\Software\McNeel\Rhinoceros\MajorVersion.0\Plug-ins` Where `MajorVersion` is the major version of Rhino (e.g. 6, 7, 8). ## Registering Your Plugin To register your plugin with Rhino, you will need to create a new Registry key at the above location. The name of the Registry key will be your plugin's GUID, formatted as a string. For example: `HKEY_LOCAL_MACHINE\Software\McNeel\Rhinoceros\MajorVersion.0\Plug-ins\` Under this new Registry key, create two new value names, ```Name``` and ```FileName``` that contain strings that identify your plugin's name and the full path to the *.rhp* file, respectively. For example, if you had created a new plugin named *"MySamplePlugIn"*, the registry might look something like the following: ``` HKEY_LOCAL_MACHINE\SOFTWARE\McNeel\Rhinoceros\MajorVersion.0\Plug-Ins\F3CF4A28-EA9E-4E08-BABA-5FC6645A5D72 Value: Name Type: REG_SZ Data: MySamplePlugIn Value: FileName Type: REG_SZ Data: C:\Program Files\My Company\My Sample PlugIn\MySamplePlugIn.rhp ``` ## Automatic Loading Rhino will attempt to load your plugin the next time Rhino launches if the ```Name``` and ```FileName``` Registry values and data are present in your plugin's Registry key. Rhino will briefly display a *"Preparing plugins for first use"* dialog if the plugin loads correctly. When your plugin loads for the first time, the rest of the normal Registry keys/value pairs in your plugin's Registry key will be filled in. ## Other Useful Values There are other useful key values that are found in the following locations depending on the Rhino version: `HKEY_LOCAL_MACHINE\SOFTWARE\McNeel\Rhinoceros\MajorVersion.0\Install` where... - ```InstallPath``` contains the full path to the Rhino installation folder. - ```Path``` contains the full path to the System folder under the Rhino folder. ## Hints Look in the Registry with *REGEDIT.EXE* and confirm that existing plugins follow these conventions. Use the right-click context menu in *REGEDIT.EXE* to access the "Copy Key Name" and "Rename" functions so you can get accurate copies of names and values in the Registry. Test your installer to handle all of these situations: 1. No Rhino is installed on the computer. 2. Only the Rhino Evaluation or Beta editions are installed. 3. Rhino is too old to run your plugin. 4. Your plugin is already installed. 5. The user wants to uninstall your plugin and its Registry entries. -------------------------------------------------------------------------------- # Rename Block Source: https://developer.rhino3d.com/en/samples/rhinocommon/rename-block/ Demonstrates how to rename an instance definition (block). ```cs partial class Examples { public static Result RenameBlock(RhinoDoc doc) { // Get the name of the insance definition to rename var instance_definition_name = ""; var rc = RhinoGet.GetString("Name of block to rename", true, ref instance_definition_name); if (rc != Result.Success) return rc; if (string.IsNullOrWhiteSpace(instance_definition_name)) return Result.Nothing; // Verify instance definition exists var instance_definition = doc.InstanceDefinitions.Find(instance_definition_name, true); if (instance_definition == null) { RhinoApp.WriteLine("Block \"{0}\" not found.", instance_definition_name); return Result.Nothing; } // Verify instance definition is rename-able if (instance_definition.IsDeleted || instance_definition.IsReference) { RhinoApp.WriteLine("Unable to rename block \"{0}\".", instance_definition_name); return Result.Nothing; } // Get the new instance definition name string instance_definition_new_name = ""; rc = RhinoGet.GetString("Name of block to rename", true, ref instance_definition_new_name); if (rc != Result.Success) return rc; if (string.IsNullOrWhiteSpace(instance_definition_new_name)) return Result.Nothing; // Verify the new instance definition name is not already in use var existing_instance_definition = doc.InstanceDefinitions.Find(instance_definition_new_name, true); if (existing_instance_definition != null && !existing_instance_definition.IsDeleted) { RhinoApp.WriteLine("Block \"{0}\" already exists.", existing_instance_definition); return Result.Nothing; } // change the block name if (!doc.InstanceDefinitions.Modify(instance_definition.Index, instance_definition_new_name, instance_definition.Description, true)) { RhinoApp.WriteLine("Could not rename {0} to {1}", instance_definition.Name, instance_definition_new_name); return Result.Failure; } return Result.Success; } } ``` ```python import rhinoscriptsyntax as rs from scriptcontext import doc def Rename(): blockName = rs.GetString("block to rename") instanceDefinition = doc.InstanceDefinitions.Find(blockName, True) if not instanceDefinition: print("{0} block does not exist".format(blockName)) return newName = rs.GetString("new name") instanceDefinition = doc.InstanceDefinitions.Find(newName, True) if instanceDefinition: print("the name '{0}' is already taken by another block".format(newName)) return rs.RenameBlock(blockName, newName) if __name__ == "__main__": Rename() ``` -------------------------------------------------------------------------------- # Rename Layer Source: https://developer.rhino3d.com/en/samples/rhinocommon/rename-layer/ Demonstrates how to rename a layer. ```cs partial class Examples { public static Result RenameLayer(RhinoDoc doc) { string layer_name = ""; var rc = RhinoGet.GetString("Name of layer to rename", true, ref layer_name); if (rc != Result.Success) return rc; if (String.IsNullOrWhiteSpace(layer_name)) return Result.Nothing; // because of sublayers it's possible that more than one layer has the same name // so simply calling doc.Layers.Find(layerName) isn't good enough. If "layerName" returns // more than one layer then present them to the user and let him decide. var matching_layers = (from layer in doc.Layers where layer.Name == layer_name select layer).ToList(); Layer layer_to_rename = null; if (matching_layers.Count == 0) { RhinoApp.WriteLine("Layer \"{0}\" does not exist.", layer_name); return Result.Nothing; } else if (matching_layers.Count == 1) { layer_to_rename = matching_layers[0]; } else if (matching_layers.Count > 1) { for (int i = 0; i < matching_layers.Count; i++) { RhinoApp.WriteLine("({0}) {1}", i+1, matching_layers[i].FullPath.Replace("::", "->")); } int selected_layer = -1; rc = RhinoGet.GetInteger("which layer?", true, ref selected_layer); if (rc != Result.Success) return rc; if (selected_layer > 0 && selected_layer <= matching_layers.Count) layer_to_rename = matching_layers[selected_layer - 1]; else return Result.Nothing; } if (layer_to_rename == null) return Result.Nothing; layer_name = ""; rc = RhinoGet.GetString("New layer name", true, ref layer_name); if (rc != Result.Success) return rc; if (String.IsNullOrWhiteSpace(layer_name)) return Result.Nothing; layer_to_rename.Name = layer_name; if (!layer_to_rename.CommitChanges()) return Result.Failure; return Result.Success; } } ``` ```python import rhinoscriptsyntax as rs from scriptcontext import doc def rename(): layerName = rs.GetString("Name of layer to rename") matchingLayers = [layer for layer in doc.Layers if layer.Name == layerName] layerToRename = None if len(matchingLayers) == 0: print("Layer \"{0}\" does not exist.".format(layerName)) return if len(matchingLayers) == 1: layerToRename = matchingLayers[0] elif len(matchingLayers) > 1: i = 0; for layer in matchingLayers: print("({0}) {1}".format( i+1, matchingLayers[i].FullPath.replace("::", "->"))) i += 1 selectedLayer = rs.GetInteger( "which layer?", -1, 1, len(matchingLayers)) if selectedLayer == None: return layerToRename = matchingLayers[selectedLayer - 1] layerName = rs.GetString("New layer name") layerToRename.Name = layerName layerToRename.CommitChanges() return if __name__ == "__main__": rename() ``` -------------------------------------------------------------------------------- # Rename Object Source: https://developer.rhino3d.com/en/samples/rhinocommon/rename-object/ Demonstrates how to rename a user-specified object. ```cs partial class Examples { public static Result RenameObject(RhinoDoc doc) { ObjRef obj_ref; var rc = RhinoGet.GetOneObject("Select object to change name", true, ObjectType.AnyObject, out obj_ref); if (rc != Result.Success) return rc; var rhino_object = obj_ref.Object(); var new_object_name = ""; rc = RhinoGet.GetString("New object name", true, ref new_object_name); if (rc != Result.Success) return rc; if (string.IsNullOrWhiteSpace(new_object_name)) return Result.Nothing; if (rhino_object.Name != new_object_name) { rhino_object.Attributes.Name = new_object_name; rhino_object.CommitChanges(); } return Result.Success; } } ``` ```python import rhinoscriptsyntax as rs obj_id = rs.GetObject("Select object to change name") object_new_name = rs.GetString("New object name") rs.ObjectName(obj_id, object_new_name) ``` -------------------------------------------------------------------------------- # Renaming Layers Source: https://developer.rhino3d.com/en/guides/cpp/renaming-layers/ This brief guide discusses how to rename a layer using C/C++. ## Overview Rhino layers (`CRhinoLayer`) are stored on a layer table (`CRhinoLayerTable`) which is located on the active document. The process for modifying an existing layer, such as changing its name, is: 1. Get the existing layer. 1. Make a copy of it. 1. Modify the copy. 1. Call `CRhinoLayerTable::ModifyLayer()`. ## Sample The following code sample demonstrates how to rename an existing layer... ```cpp CRhinoCommand::result CCommandTestSdk::RunCommand(const CRhinoCommandContext& context) { // Get the layer name CRhinoGetString gs; gs.SetCommandPrompt( L"Name of layer to rename" ); gs.GetString(); if( gs.CommandResult() != CRhinoCommand::success ) return gs.CommandResult(); // Validate the string ON_wString layer_name = gs.String(); layer_name.TrimLeftAndRight(); if( layer_name.IsEmpty() ) return CRhinoCommand::nothing; // Get a reference to the layer table CRhinoLayerTable& layer_table = context.m_doc.m_layer_table; // Find the layer int layer_index = layer_table.FindLayer( layer_name ); if( layer_index < 0 ) { RhinoApp().Print( L"Layer \"%s\" does not exist.\n", layer_name ); return CRhinoCommand::cancel; } // Get the new layer name gs.SetCommandPrompt( L"New layer name" ); gs.GetString(); if( gs.CommandResult() != CRhinoCommand::success ) return gs.CommandResult(); // Validate the string ON_wString new_name = gs.String(); layer_name.TrimLeftAndRight(); if( layer_name.IsEmpty() ) return CRhinoCommand::nothing; // Compare both names if( layer_name.CompareNoCase(new_name) == 0 ) return CRhinoCommand::nothing; // Get the layer const CRhinoLayer& layer = layer_table[layer_index]; // Make a copy of it, and modify the name ON_Layer onlayer( layer ); onlayer.SetLayerName( new_name ); // Modify the exising layer with the new definition CRhinoCommand::result rc = CRhinoCommand::cancel; if( layer_table.ModifyLayer(onlayer, layer_index) ) rc = CRhinoCommand::success; return rc; } ``` -------------------------------------------------------------------------------- # Render Engine Integration - Introduction Source: https://developer.rhino3d.com/en/guides/rhinocommon/render-engine-integration-introduction/ This guide introduces integrating a render engine in Rhino using RhinoCommon. ## Overview If you're a render engine developer and you're thinking of writing an integration plug-in for Rhino, then you definitely should keep reading on. For this series I'll be looking into how one would go about integrating a render engine using RhinoCommon. The subject will be broken up into several guides: 1. Setting up the plug-in (this guide) 1. [Modal Rendering](/guides/rhinocommon/render-engine-integration-modal/) 1. [ChangeQueue](/guides/rhinocommon/render-engine-integration-changequeue/) 1. [Interactive render - viewport integration](/guides/rhinocommon/render-engine-integration-interactive-viewport/) 1. Preview render *(forthcoming)* For each guide we'll take a look at relevant parts of the integration plug-in for the Cycles render engine while doing several simple example plug-ins at the same time. The code for the example plug-ins will be stripped of (most) of the comments that are added by the template, so we can focus on the parts that matter. Source code for the sample project, dubbed [MockingBird, is available on GitHub](https:/github.com/mcneel/rhino-developer-samples/tree/6/rhinocommon/cs/SampleCsRendererIntegration/MockingBird). ## Creating Render Plug-in project ### Install the RhinoCommon template package To make developing a new plug-in for Rhinoceros 3D easy McNeel has published a template packages. Search for the string rhino and install the relevant package. ![template image](https://developer.rhino3d.com/images/mockingbird/001_rhinocommon_templates.png) Once the template package is installed we're ready to write amazing plug-ins for Rhino. ### Create a new RhinoCommon v6 project ![template image](https://developer.rhino3d.com/images/mockingbird/002_new_plugin_project.png) Lets create a new plug-in now that the wizard is installed. Simply create a new project in Visual Studio, and from the Visual C# section under Templates select Rhinoceros. Pick RhinoCommon Plug-In for Rhinoceros and give a name. After you click on OK you'll be presented with a wizard dialog where settings can be changed. For our case we select Render plug-in. If the wizard fails to recognize a Rhino installation path one can set the necessary paths before accepting the settings. Click on the Finish button when happy with the settings. ![template image](https://developer.rhino3d.com/images/mockingbird/003_plugin_settings.png) The plug-in wizard will generate a set of files for the developer. ### Adjust assembly configuration Before diving into the deep it probably is a good idea to change the assembly information and plug-in description strings. I'd suggest at least some minimal contact information and a short description of what the plug-in is supposed to do. ```cs [assembly: PlugInDescription(DescriptionType.Address, "Turku")] [assembly: PlugInDescription(DescriptionType.Country, "Finland")] [assembly: PlugInDescription(DescriptionType.Email, "jesterking@letwory.net")] [assembly: PlugInDescription(DescriptionType.Phone, "-")] [assembly: PlugInDescription(DescriptionType.Fax, "-")] [assembly: PlugInDescription(DescriptionType.Organization, "Letwory Interactive")] [assembly: PlugInDescription(DescriptionType.UpdateUrl, "-")] [assembly: PlugInDescription(DescriptionType.WebSite, "http:/www.letworyinteractive.com")] [assembly: AssemblyTitle("MockingBird")] // Plug-In title is extracted from this [assembly: AssemblyDescription("A sample render plug-in for Rhinoceros 6")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("Letwory Interactive")] [assembly: AssemblyProduct("MockingBird")] [assembly: AssemblyCopyright("Copyright © 2016")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] // The following GUID is for the ID of the typelib if this project is exposed to COM // This will also be the Guid of the Rhino plug-in [assembly: Guid("ccb6ab63-fdef-44ac-9c1f-7eca810d5b75")] ``` ### The main part of the plug-in We'll ignore the the command class that the wizard also has added. For the purpose of this example plug-in it is not needed, so it could just be removed as well. This leaves the major entry point for the plug-in as our starting point. ```cs using System; using Rhino; namespace MockingBird { public class MockingBirdPlugIn : Rhino.PlugIns.RenderPlugIn { public MockingBirdPlugIn() { Instance = this; } public static MockingBirdPlugIn Instance { get; private set; } protected override Rhino.Commands.Result Render(RhinoDoc doc, Rhino.Commands.RunMode mode, bool fastPreview) { throw new NotImplementedException("Render is not implemented in the MockingBird.MockingBirdPlugIn class."); } protected override Rhino.Commands.Result RenderWindow(RhinoDoc doc, Rhino.Commands.RunMode mode, bool fastPreview, Rhino.Display.RhinoView view, System.Drawing.Rectangle rect, bool inWindow) { throw new NotImplementedException("RenderWindow is not implemented by the MockingBird.MockingBirdPlugIn class."); } } } ``` Currently the wizard gives a public constructor that always assigns itself to the static Instance property. I think it is better to assign **only** when Instance is not set. There are two mandatory functions to override. They have been added with default implementations that throw a NotImplementedException when called. For this example plug-in we are not going to bother with RenderWindow, so we'll just return with a success code. For now we'll do the same for Render(), but we'll using that to hook up our render engine. ```cs using System; using Rhino; namespace MockingBird { public class MockingBirdPlugIn : Rhino.PlugIns.RenderPlugIn { public MockingBirdPlugIn() { if(Instance==null) Instance = this; } public static MockingBirdPlugIn Instance { get; private set; } protected override Rhino.Commands.Result Render(RhinoDoc doc, Rhino.Commands.RunMode mode, bool fastPreview) { return Result.Success; } protected override Rhino.Commands.Result RenderWindow(RhinoDoc doc, Rhino.Commands.RunMode mode, bool fastPreview, Rhino.Display.RhinoView view, System.Drawing.Rectangle rect, bool inWindow) { return Result.Success; } } } ``` With these changes in the very first version of the plug-in can be compiled. This will create a .rhp file in the build folder. ![template image](https://developer.rhino3d.com/images/mockingbird/004_first_compiled_rhp.png) Start a Debug session with Visual Studio, and drag-and-drop the .rhp file on the Rhino instance that opens. The Rhino command-line should tell that the plug-in has been loaded. The Current Renderer menu should show the newly-loaded plug-in as an entry as well. ![template image](https://developer.rhino3d.com/images/mockingbird/005_plugin_loaded.png) *Congratulations!* The render engine can be selected, but it won't do anything useful yet :) *Now what?* ## Next Steps With these steps completed the basics of the plug-in are done. It is time to have [a proper look at integrating the render engine](/guides/rhinocommon/render-engine-integration-modal/). ## Related Topics - [Render Engine Integration - Modal](/guides/rhinocommon/render-engine-integration-modal/) - [Render Engine Integration - ChangeQueue](/guides/rhinocommon/render-engine-integration-changequeue/) - [Render Engine Integration - Interactive Viewport](/guides/rhinocommon/render-engine-integration-interactive-viewport/) - Preview render *(forthcoming)* -------------------------------------------------------------------------------- # Render Named Views Source: https://developer.rhino3d.com/en/samples/rhinoscript/render-named-views/ Demonstrates how to render named views using RhinoScript. ```vbnet Option Explicit ' Renders one or more named views to a user-defined folder Sub RenderNamedViews ' Let the user pick the named view to render Dim render_views render_views = GetRenderViews If Not IsArray(render_views) Then Exit Sub ' Let the user pick the folder to save the renderings Dim folder folder = Rhino.BrowseForFolder(Rhino.DocumentPath, "Browse for folder", "Batch Render") If IsNull(folder) Then Exit Sub ' Save the active view Dim saved_view_name saved_view_name = Rhino.CurrentView Rhino.Command "_-NamedView _Save $$_save_$$ _Enter", 0 ' Process each named view Dim view For Each view In render_views If IsStandardView(view) Then ' If the named view is a standard view Rhino.Command "_-SetView _World _" & view, 0 Else ' If the named view is not a standard view Rhino.Command "_-NamedView _Restore " & view & " _Enter", 0 End If ' Render the scene with the current render engine Rhino.Command "_-Render" ' Save the render to a jpg file Rhino.Command "_-SaveRenderWindowAs " & GetRenderFileName(folder, view, "jpg") ' Close the render window Rhino.Command "_-CloseRenderWindow" Next ' Restore the active view Rhino.Command "_-NamedView _Restore $$_save_$$ _Enter", 0 Rhino.RenameView Rhino.CurrentView, saved_view_name ' Delete the temporary named view Rhino.Command "_-NamedView _Delete $$_save_$$ _Enter", 0 End Sub ' Returns an array of view names to render Function GetRenderViews() GetRenderViews = vbNull Dim all_views, selected_views all_views = GetAllViews selected_views = Rhino.MultiListBox(all_views, "Select views to render.", "Batch Render") If IsArray(selected_views) Then GetRenderViews = selected_views End If End Function ' Returns a render-formatted file name Function GetRenderFileName(folder, view, ext) Dim doc, file, temp doc = Rhino.DocumentName temp = "_" & view & "." & ext file = LCase(Replace(doc, ".3dm", temp, 1, -1, 1)) GetRenderFileName = Chr(34) & folder & file & Chr(34) End Function ' Returns an array of both standard and named view names Function GetAllViews() Dim all_views, std_views, named_views std_views = GetStandardViews named_views = Rhino.NamedViews If IsArray(named_views) Then all_views = Rhino.JoinArrays(std_views, named_views) all_views = Rhino.CullDuplicateStrings(all_views) GetAllViews = Rhino.SortStrings(all_views) Else GetAllViews = std_views End If End Function ' Returns an array of standard view names Function GetStandardViews() GetStandardViews = Array("Back", "Bottom", "Front", "Left", "Perspective", "Right", "Top") End Function ' Verifies a string is a standard view name Function IsStandardView(str) IsStandardView = vbFalse Dim std_views, i std_views = GetStandardViews For i = 0 To UBound(std_views) If StrComp(std_views(i), str, 1) = 0 Then IsStandardView = vbTrue Exit For End If Next End Function ``` -------------------------------------------------------------------------------- # Reparameterize Curve Source: https://developer.rhino3d.com/en/samples/cpp/reparameterize-curve/ Demonstrates how to Reparameterize a curve object. ```cpp CRhinoCommand::result CCommandTest::RunCommand(const CRhinoCommandContext& context) { CRhinoCommand::result rc = CRhinoCommand::success; CRhinoGetObject go; go.SetCommandPrompt( L"Select curve to reparameterize" ); go.SetGeometryFilter( CRhinoGetObject::curve_object ); go.GetObjects( 1, 1 ); rc = go.CommandResult(); if( rc != CRhinoCommand::success ) return rc; CRhinoObjRef& objref = go.Object(0); const ON_Curve* pC = objref.Curve(); if( !pC ) return CRhinoCommand::failure; double s0, s1; pC->GetDomain( &s0, &s1 ); CRhinoGetNumber gn; gn.SetCommandPrompt( L"Domain start" ); gn.SetDefaultNumber( s0 ) ; gn.AcceptNothing(); gn.GetNumber(); rc = gn.CommandResult(); if( rc != CRhinoCommand::success ) return rc; double t0 = gn.Number(); gn.SetCommandPrompt( L"Domain end" ); gn.SetDefaultNumber( s1 ); gn.SetLowerLimit( t0, TRUE ); gn.AcceptNothing(); gn.GetNumber(); rc = gn.CommandResult(); if( rc != CRhinoCommand::success ) return rc; double t1 = gn.Number(); if( s0 == t0 && s1 == t1 ) return CRhinoCommand::nothing; ON_Curve *pNC = pC->DuplicateCurve(); if( pNC ) { pNC->SetDomain( t0, t1 ); CRhinoCurveObject* obj = new CRhinoCurveObject(); if( obj ) { obj->SetCurve( pNC ); context.m_doc.ReplaceObject( objref, obj ); context.m_doc.Redraw(); } else rc = CRhinoCommand::failure; } else rc = CRhinoCommand::failure; return rc; } ``` -------------------------------------------------------------------------------- # Reparameterize Curves Source: https://developer.rhino3d.com/en/samples/rhinocommon/reparameterize-curves/ Demonstrates how to reparameterize - or change the domain of - user-specified curves. ```cs partial class Examples { public static Result ReparameterizeCurve(RhinoDoc doc) { ObjRef obj_ref; var rc = RhinoGet.GetOneObject("Select curve to reparameterize", false, ObjectType.Curve, out obj_ref); if (rc != Result.Success) return rc; var curve = obj_ref.Curve(); if (curve == null) return Result.Failure; double domain_start = 0; rc = RhinoGet.GetNumber("Domain start", false, ref domain_start); if (rc != Result.Success) return rc; double domain_end = 0; rc = RhinoGet.GetNumber("Domain end", false, ref domain_end); if (rc != Result.Success) return rc; if (Math.Abs(curve.Domain.T0 - domain_start) < RhinoMath.ZeroTolerance && Math.Abs(curve.Domain.T1 - domain_end) < RhinoMath.ZeroTolerance) return Result.Nothing; var curve_copy = curve.DuplicateCurve(); curve_copy.Domain = new Interval(domain_start, domain_end); if (!doc.Objects.Replace(obj_ref, curve_copy)) return Result.Failure; else { doc.Views.Redraw(); return Result.Success; } } } ``` ```python from System import * from Rhino import * from Rhino.Commands import * from Rhino.DocObjects import * from Rhino.Geometry import * from Rhino.Input import * from scriptcontext import doc def RunCommand(): rc, obj_ref = RhinoGet.GetOneObject("Select curve to reparameterize", False, ObjectType.Curve) if rc != Result.Success: return rc curve = obj_ref.Curve() if curve == None: return Result.Failure domain_start = 0 rc, domain_start = RhinoGet.GetNumber("Domain start", False, domain_start) if rc != Result.Success: return rc domain_end = 100 rc, domain_end = RhinoGet.GetNumber("Domain end", False, domain_end) if rc != Result.Success: return rc if Math.Abs(curve.Domain.T0 - domain_start) < RhinoMath.ZeroTolerance and \ Math.Abs(curve.Domain.T1 - domain_end) < RhinoMath.ZeroTolerance: return Result.Nothing curve_copy = curve.DuplicateCurve() curve_copy.Domain = Interval(domain_start, domain_end) if not doc.Objects.Replace(obj_ref, curve_copy): return Result.Failure else: doc.Views.Redraw() return Result.Success if __name__ == "__main__": RunCommand() ``` -------------------------------------------------------------------------------- # Replace Hatch Pattern Source: https://developer.rhino3d.com/en/samples/rhinocommon/replace-hatch-pattern/ Demonstrates how to replace an object's hatch pattern. ```cs partial class Examples { public static Result ReplaceHatchPattern(RhinoDoc doc) { ObjRef[] obj_refs; var rc = RhinoGet.GetMultipleObjects("Select hatches to replace", false, ObjectType.Hatch, out obj_refs); if (rc != Result.Success || obj_refs == null) return rc; var gs = new GetString(); gs.SetCommandPrompt("Name of replacement hatch pattern"); gs.AcceptNothing(false); gs.Get(); if (gs.CommandResult() != Result.Success) return gs.CommandResult(); var hatch_name = gs.StringResult(); var pattern_index = doc.HatchPatterns.Find(hatch_name, true); if (pattern_index < 0) { RhinoApp.WriteLine("The hatch pattern \"{0}\" not found in the document.", hatch_name); return Result.Nothing; } foreach (var obj_ref in obj_refs) { var hatch_object = obj_ref.Object() as HatchObject; if (hatch_object.HatchGeometry.PatternIndex != pattern_index) { hatch_object.HatchGeometry.PatternIndex = pattern_index; hatch_object.CommitChanges(); } } doc.Views.Redraw(); return Result.Success; } } ``` ```python from Rhino import * from Rhino.DocObjects import * from Rhino.Commands import * from Rhino.Input import * from Rhino.Input.Custom import * from scriptcontext import doc def RunCommand(): rc, obj_refs = RhinoGet.GetMultipleObjects("Select hatches to replace", False, ObjectType.Hatch) if rc != Result.Success or obj_refs == None: return rc gs = GetString() gs.SetCommandPrompt("Name of replacement hatch pattern") gs.AcceptNothing(False) gs.Get() if gs.CommandResult() != Result.Success: return gs.CommandResult() hatch_name = gs.StringResult() pattern_index = doc.HatchPatterns.Find(hatch_name, True) if pattern_index < 0: RhinoApp.WriteLine("The hatch pattern \"{0}\" not found in the document.", hatch_name) return Result.Nothing for obj_ref in obj_refs: hatch_object = obj_ref.Object() if hatch_object.HatchGeometry.PatternIndex != pattern_index: hatch_object.HatchGeometry.PatternIndex = pattern_index hatch_object.CommitChanges() doc.Views.Redraw() return Result.Success if __name__ == "__main__": RunCommand() ``` -------------------------------------------------------------------------------- # Replace Object Hatch Pattern Source: https://developer.rhino3d.com/en/samples/cpp/replace-object-hatch-pattern/ Demonstrates how to replace a Hatch Object's pattern. ```cpp CRhinoCommand::result CCommandTest::RunCommand( const CRhinoCommandContext& context ) { CRhinoGetObject go; go.SetCommandPrompt( L"Select hatches to replace pattern" ); go.SetGeometryFilter( CRhinoGetObject::hatch_object ); go.GetObjects( 1, 0 ); if( go.CommandResult() != success ) return go.CommandResult(); CRhinoGetString gs; gs.SetCommandPrompt( L"Name of replacement hatch pattern" ); gs.GetString(); if( gs.CommandResult() != success ) return gs.CommandResult(); ON_wString pattern_name = gs.String(); pattern_name.TrimLeftAndRight(); if( pattern_name.IsEmpty() ) return nothing; int hatch_index = context.m_doc.m_hatchpattern_table.FindHatchPattern( pattern_name ); if( hatch_index < 0 ) { RhinoApp().Print( L"Specified hatch pattern not found in the document.\n" ); return nothing; } int i, replaced = 0; for( i = 0; i < go.ObjectCount(); i++ ) { const CRhinoHatch* hatch_obj = CRhinoHatch::Cast( go.Object(i).Object() ); if( 0 == hatch_obj ) continue; if( hatch_index == hatch_obj->PatternIndex() ) continue; const ON_Hatch* hatch = hatch_obj->Hatch(); if( 0 == hatch ) continue; ON_Hatch* dup_hatch = hatch->DuplicateHatch(); if( 0 == dup_hatch ) continue; dup_hatch->SetPatternIndex( hatch_index ); CRhinoHatch* dup_obj = hatch_obj->Duplicate(); if( 0 == dup_obj ) { delete dup_hatch; continue; } dup_obj->SetHatch( dup_hatch ); if( !context.m_doc.ReplaceObject(CRhinoObjRef(hatch_obj), dup_obj) ) { delete dup_obj; continue; } replaced++; } if( replaced > 0 ) { context.m_doc.Redraw(); if( 1 == replaced ) RhinoApp().Print( L"1 hatch pattern replaced.\n" ); else RhinoApp().Print( L"%d hatch patterns replaced.\n", replaced ); } else RhinoApp().Print( L"0 hatch patterns replaced.\n" ); return success; } ``` -------------------------------------------------------------------------------- # Replace the Color Picking Dialog Source: https://developer.rhino3d.com/en/samples/rhinocommon/replace-the-color-picking-dialog/ Demonstrates how to replace Rhino's color picking dialog. ```cs partial class Examples { private static ColorDialog m_dlg = null; public static Result ReplaceColorDialog(RhinoDoc doc) { Dialogs.SetCustomColorDialog(OnSetCustomColorDialog); return Result.Success; } static void OnSetCustomColorDialog(object sender, GetColorEventArgs e) { m_dlg = new ColorDialog(); if (m_dlg.ShowDialog(null) == DialogResult.Ok) { var c = m_dlg.Color; e.SelectedColor = System.Drawing.Color.FromArgb (c.Ab, c.Rb, c.Gb, c.Bb); } } } ``` ```python # No Python sample available ``` -------------------------------------------------------------------------------- # Replacing Points with Blocks Source: https://developer.rhino3d.com/en/guides/rhinoscript/replacing-points-with-blocks/ This guide demonstrates how to replace point objects with block objects using RhinoScript. ## Problem Imagine you have a number of point objects in your model and you would like to replace them with a block so they appear as markers. How can this be done without running the Insert command a bunch of times? ## Solution The following sample code demonstrates how to replace point objects with block objects using RhinoScript... ```vbnet ' Replaces points with blocks Sub ReplacePointsWithBlocks ' Select points to replace with a block Dim arrObjects arrObjects = Rhino.GetObjects("Select points to replace with a block", 1, True, True) If Not IsArray(arrObjects) Then Exit Sub ' Get the names of all block definitions in the document Dim arrBlocks arrBlocks = Rhino.BlockNames(True) If Not IsArray(arrBlocks) Then Rhino.Print "No block definitions found in the document." Exit Sub End If ' Select a block name from a list Dim strBlock strBlock = Rhino.ListBox(arrBlocks, "Select block", "Replace Points") If IsNull(strBlock) Then Exit Sub ' Turn off redrawing (faster) Rhino.EnableRedraw True ' Process each selected point object Dim strObject, arrPoint For Each strObject In arrObjects ' Get the point object's coordinates arrPoint = Rhino.PointCoordinates(strObject) ' Insert the block at that location Rhino.InsertBlock strBlock, arrPoint Next ' Delete all of the point objects Rhino.DeleteObjects arrObjects ' Turn redrawing back on Rhino.EnableRedraw True End Sub ``` ## Inverse The following script will do just the opposite - it will replace block objects with point objects... ```vbnet ' Replaces blocks with points Sub ReplaceBlocksWithPoints ' Select blocks to replace with points Dim arrObjects arrObjects = Rhino.GetObjects("Select blocks to replace with points", 4096, True, True) If Not IsArray(arrObjects) Then Exit Sub ' Turn off redrawing (faster) Rhino.EnableRedraw True ' Process each selected block object Dim strObject, arrPoint For Each strObject In arrObjects ' Get the block's insertion point arrPoint = Rhino.BlockInstanceInsertPoint(strObject) ' Add a point object at that location Rhino.AddPoint arrPoint Next ' Delete all of the block objects Rhino.DeleteObjects arrObjects ' Turn redrawing back on Rhino.EnableRedraw True End Sub ``` -------------------------------------------------------------------------------- # Retrieving authentication and authorization tokens Source: https://developer.rhino3d.com/en/guides/rhinocommon/rhinoaccounts/ra-example/ This guide gives an example of how to obtain authentication and authorization tokens within Rhino from Rhino Accounts. To retrieve an OAuth 2 Token for authorization or an OpenID Connect token to learn about the user's identity, all that is needed is to call one of the different overloads of `GetAuthTokensAsync`. This method will asynchronously ask the user for permission to obtain the tokens, and return them to you so that you can use them as you wish. By default, a successful call to `GetAuthTokensAsync` will cache the tokens you retrieved in a secure persistent store so that they can later be retrieved using `TryGetAuthTokens` without having to ask the user for permission or wait for potentially lengthy network requests. In most scenarious, it makes sense to call `TryGetAuthTokens` first to see if there are any cached tokens available. If there aren't, you can then call `GetAuthTokensAsync` and ask the user for permission. **Important Note**: Both `GetAuthTokensAsync` and `TryGetAuthTokens` must be executed inside a protected function that is passed to `ExecuteProtectedCodeAsync`. This is done to make sure that only a valid, signed assembly can retrieve auth tokens. Code inside the protected function should be kept as small as possible for performance and security reasons. Example: ```cs using Rhino.Runtime.RhinoAccounts; ... Tuple authTokens = null; await RhinoAccountsManager.ExecuteProtectedCodeAsync(async (SecretKey secretKey) => { authTokens = RhinoAccountsManager.TryGetAuthTokens("MY_PLUGIN_ID",secretKey); if (authTokens == null) { authTokens = await RhinoAccountsManager.GetAuthTokensAsync( "MY_PLUGIN_ID", "MY_PLUGIN_SECRET", secretKey, CancellationToken.None ); } }); ``` For details on all the available options you can specify on the methods described above such as `scope` and `maxage`, please see the [Rhino Accounts Reference](https://docs.google.com/document/d/1-U0FYt6iQAM3UA6Rio4z0sDVXBSdc0kQk5e4zumnKig). -------------------------------------------------------------------------------- # Retrieving Rhino Data from the Clipboard Source: https://developer.rhino3d.com/en/guides/cpp/retreiving-rhino-data-from-clipboard/ This guide demonstrates how to access Rhino data from the Windows Clipboard using C/C++. ## Overview Like most Windows applications, Rhino can cut, copy, and paste information to and from the Windows Clipboard. When Rhino either cuts or copies geometry to the clipboard, it creates a temporary 3DM file that contains the selected geometry. Rhino then stores the temporary filename in the Clipboard. When a paste operation is invoked, Rhino determines if the Clipboard contains the name of a temporary Rhino file. If found, Rhino simply imports the temporary file. Rhino registers custom Clipboard formats to store the temporary file names: a unique Clipboard format for each Rhino version. ## Sample The following sample demonstrates how to determine of there is Rhino information in the Windows Clipboard. If there is Rhino data in the Clipboard, the sample will retrieve the name of the temporary file. Once this file name is known, you can whatever means necessary to retrieve the data. For example, if you are using this code from a Rhino plugin, you could script the running of Rhino's *Paste* command to insert the geometry into the current document. If you are in a standalone application, you would use the openNURBS toolkit to read the temporary .3DM file. ```cpp ///////////////////////////////////////////////////////////////////////////// // clipboard.cpp #include "StdAfx.h" #include // MFC OLE clipboard support // TODO: Fill this in with your own message printing static void myPrintMessage( LPCTSTR lpMessage ) { if( 0 == lpMessage || 0 == lpMessage[0] ) return; #ifdef RHINO_SDK_CLASS RhinoApp().Print( ON_wString(lpMessage) ); #else MessageBox( 0, lpMessage, _T("Clipboard Message"), MB_SYSTEMMODAL|MB_OK|MB_ICONINFORMATION); #endif } // TODO: Return your application main window handle static HWND myMainWnd() { #ifdef RHINO_SDK_CLASS return RhinoApp().MainWnd(); #else return AfxGetMainWnd()->m_hWnd; #endif } static UINT myGetClipboardFormat( int ver = 4 ) { UINT v = 0; if( 1 == ver ) // Rhino 1.0 { static UINT v1 = 0; if( v1 < 1 ) v1 = RegisterClipboardFormat( _T("Rhino 1.0 3DM Clip") ); v = v1; } else if( 2 == ver ) // Rhino 2.0 { static UINT v2 = 0; if( v2 < 1 ) v2 = RegisterClipboardFormat( _T("Rhino 2.0 3DM Clip") ); v = v2; } else if( 3 == ver ) // Rhino 3.0 { static UINT v3 = 0; if( v3 < 1 ) v3 = RegisterClipboardFormat( _T("Rhino 3.0 3DM Clip") ); v = v3; } else if( 4 == ver ) // Rhino 4.0 { static UINT v4 = 0; if( v4 < 1 ) v4 = RegisterClipboardFormat( _T("Rhino 4.0 3DM Clip global mem") ); v = v4; } return v; } static BOOL myGetTempFileName( CString& strResult ) { CString strPath, strFileName; int rc = GetTempPath( _MAX_PATH, strPath.GetBuffer(_MAX_PATH) ); strPath.ReleaseBuffer(); if( rc == 0 ) return false; rc = GetTempFileName( strPath, _T("rh$"), 0, strFileName.GetBuffer(_MAX_PATH) ); strFileName.ReleaseBuffer(); if( rc == 0 ) return false; strResult = strFileName; return true; } static BOOL CopyClipboardToTempFile( CString& strTempFileName, UINT clipboard_format ) { if( !IsClipboardFormatAvailable(clipboard_format) ) { myPrintMessage( _T("CopyClipboardV2ToTempFile() - Rhino clipboard format is not available.\n") ); return false; } CString strFileName; if( !myGetTempFileName(strFileName) ) { myPrintMessage( _T("CopyClipboardV2ToTempFile() - Unable to calculate temporary file name.\n" )); return false; } if( !OpenClipboard(myMainWnd()) ) { myPrintMessage( _T("CopyClipboardV2ToTempFile() - Unable to open clipboard.\n") ); return false; } HANDLE hMem = GetClipboardData( clipboard_format ); if( 0 == hMem ) { myPrintMessage( _T("CopyClipboardV2ToTempFile() - Unable to get clipboard data.\n") ); CloseClipboard(); return false; } DWORD* ptr = (ULONG*)GlobalLock( hMem ); if( 0 == ptr ) { myPrintMessage( _T("CopyClipboardV2ToTempFile() - Unable to lock global memory block.\n") ); CloseClipboard(); return false; } // size is encoded in the first dword int size = (int)ptr[0]; if( size <= 0) { myPrintMessage( _T("CopyClipboardV2ToTempFile() - Unable to determine size of clipboard memory block.\n") ); GlobalUnlock( hMem ); CloseClipboard(); return false; } FILE* fp = _tfopen( strFileName, _T("wb") ); #define CopyClipboardV2ToTempFile_BAIL( msg )\ {\ GlobalUnlock( hMem );\ CloseClipboard();\ if( fp )\ {\ fclose( fp );\ ::DeleteFile( strFileName );\ }\ CloseClipboard();\ myPrintMessage( msg );\ } if( 0 == fp ) CopyClipboardV2ToTempFile_BAIL( _T("CopyClipboardV2ToTempFile() - Unable to open temporary file.\n") ); if( fwrite(&ptr[1], size, 1, fp) != 1 ) CopyClipboardV2ToTempFile_BAIL( _T("CopyClipboardV2ToTempFile() - Unable to write information to temporary file.\n") ); if( ferror(fp) ) CopyClipboardV2ToTempFile_BAIL( _T("CopyClipboardV2ToTempFile() - Error in temporary file stream.\n") ); if( fflush(fp) ) CopyClipboardV2ToTempFile_BAIL( _T("CopyClipboardV2ToTempFile() - Error in flushing temporary file buffers.\n")); if( fclose(fp) ) CopyClipboardV2ToTempFile_BAIL( _T("CopyClipboardV2ToTempFile() - Error in closing temporary file.\n") ); GlobalUnlock( hMem ); if( 0 == CloseClipboard() ) { DeleteFile( strFileName ); myPrintMessage( _T("CopyClipboardV2ToTempFile() - Unable to close the clipboard.\n") ); return false; } strTempFileName = strFileName; return true; } static BOOL ReadFileNameFromClipboard( const wchar_t* lpFileName, CString& strOutputFile ) { CString strFileName( lpFileName ); strFileName.TrimLeft(); strFileName.TrimRight(); if( strFileName.IsEmpty() ) return false; if( !myGetTempFileName(strOutputFile) ) return false; return CopyFile( strFileName, strOutputFile, false ); } static BOOL GetFileFromClipboard( CString& strFileName, int& file_version ) { FORMATETC fe = { 0, 0, DVASPECT_CONTENT, -1, TYMED_FILE }; STGMEDIUM stgm; COleDataObject odo; odo.AttachClipboard(); // V4 file in clipboard UINT v4_cbformat = myGetClipboardFormat( 4 ); fe.cfFormat = v4_cbformat; if( odo.GetData(v4_cbformat, &stgm, &fe) && stgm.tymed == TYMED_FILE ) { file_version = 4; return ReadFileNameFromClipboard( stgm.lpszFileName, strFileName ); } // V3 file in clipboard UINT v3_cbformat = myGetClipboardFormat( 3 ); fe.cfFormat = v3_cbformat; if( odo.GetData(v3_cbformat, &stgm, &fe) && stgm.tymed == TYMED_FILE ) { file_version = 3; return ReadFileNameFromClipboard( stgm.lpszFileName, strFileName ); } // V1 or V2 file in clipboard UINT v1_cbformat = myGetClipboardFormat( 1 ); UINT v2_cbformat = myGetClipboardFormat( 2 ); UINT clipboard_format = 0; if( ::IsClipboardFormatAvailable(v2_cbformat) ) { file_version = 2; clipboard_format = v2_cbformat; } else if( ::IsClipboardFormatAvailable(v1_cbformat) ) { file_version = 1; clipboard_format = v1_cbformat; } if( clipboard_format > 0 ) return CopyClipboardToTempFile( strFileName, clipboard_format ); return false; } ``` -------------------------------------------------------------------------------- # Reverse Curve Source: https://developer.rhino3d.com/en/samples/rhinocommon/reverse-curve/ Demonstrates how to reverse the direction of user-specified curves. ```cs partial class Examples { public static Result ReverseCurve(RhinoDoc doc) { ObjRef[] obj_refs; var rc = RhinoGet.GetMultipleObjects("Select curves to reverse", true, ObjectType.Curve, out obj_refs); if (rc != Result.Success) return rc; foreach (var obj_ref in obj_refs) { var curve_copy = obj_ref.Curve().DuplicateCurve(); if (curve_copy != null) { curve_copy.Reverse(); doc.Objects.Replace(obj_ref, curve_copy); } } return Result.Success; } } ``` ```python import rhinoscriptsyntax as rs from scriptcontext import * import Rhino def ReverseCurves(): crvs = rs.GetObjects("Select curves to reverse", rs.filter.curve) if not crvs: return for crvid in crvs: crv = rs.coercecurve(crvid) if not crv: continue dup = crv.DuplicateCurve() if dup: dup.Reverse() doc.Objects.Replace(crvid, dup) if __name__ == "__main__": ReverseCurves() ``` -------------------------------------------------------------------------------- # Reversing Arrays Source: https://developer.rhino3d.com/en/guides/rhinoscript/reversing-arrays/ This brief guide demonstrates how to reverse an array using RhinoScript. ## Problem How does one quickly reverse the order of the elements in an array? ## Solution Consider the following subroutine: ```vbnet Sub ReverseArray(ByRef arr) Dim i, j, last, half, temp last = UBound(arr) half = Int(last/2) For i = 0 To half temp = arr(i) arr(i) = arr(last-i) arr(last-i) = temp Next End Sub ``` ...which can be used as follows: ```vbnet Sub Main() Dim arr, i arr = Array(1,2,3) For i = 0 To UBound(arr) Rhino.Print arr(i) Next Call ReverseArray(arr) For i = 0 To UBound(arr) Rhino.Print arr(i) Next End Sub ``` -------------------------------------------------------------------------------- # Revoking authorization tokens Source: https://developer.rhino3d.com/en/guides/rhinocommon/rhinoaccounts/ra-revoke/ This guide discusses authorization token revocation within Rhino from Rhino Accounts. [Once you have obtained an OAuth 2 Token](/guides/rhinocommon/rhinoaccounts/ra-example), you can use it in any authentication workflow you wish. There may come a time, however, when the token is no longer needed. In such cases, it is highly recommended that your software has a way of revoking the OAuth 2 token. Revoking a token will generate a network request to the Rhino Accounts server which will invalidate the token so that it can no longer be used. **Important Note**: `RevokeTokenAsync` must be executed inside a protected function that is passed to `ExecuteProtectedCodeAsync`. This is done to make sure that only a valid, signed assembly can retrieve auth tokens. Code inside the protected function should be kept as small as possible for performance and security reasons. ```cs using Rhino.Runtime.RhinoAccounts; ... await RhinoAccountsManager.ExecuteProtectedCodeAsync(async (SecretKey secretKey) => { await RhinoAccountsManager.RevokeAuthTokenAsync(oauth2Token, secretKey, CancellationToken.None); }); ``` -------------------------------------------------------------------------------- # Revolve Profile Curves Source: https://developer.rhino3d.com/en/samples/rhinoscript/revolve-profile-curves/ Demonstrates how to create a surface by revolving one or more profile curves using RhinoScript. ```vbnet Option Explicit Sub MyRevolve ' Declare local variables Dim obj_list, crv_list, crv, axis0, axis1 ' Select one or more curves to revolve obj_list = Rhino.GetObjects("Select curves to revolve", 4, True, True) If IsNull(obj_list) Then Exit Sub ' Pick start of revolve axis axis0 = Rhino.GetPoint("Start of revolve axis") If IsNull(axis0) Then Exit Sub ' Pick end of revolve axis axis1 = Rhino.GetPoint("End of revolve axis", axis0) If IsNull(axis1) Then Exit Sub ' If more than one curve as picked, try to join them If (UBound(obj_list) > 0) Then crv_list = Rhino.JoinCurves(obj_list, False) Else crv_list = Array(obj_list(0)) End If ' Create the surfaces of revolution For Each crv In crv_list Call Rhino.AddRevSrf(crv, Array(axis0, axis1)) Next ' Delete the temporary joined curves Call Rhino.DeleteObjects(crv_list) End Sub ``` -------------------------------------------------------------------------------- # RhinoScriptSyntax in Python Source: https://developer.rhino3d.com/en/guides/rhinopython/python-rhinoscriptsyntax-introduction/ This guide provides an overview of the RhinoScriptSyntax in Python. ## Overview The RhinoScriptSyntax module contains hundreds of easy-to-use functions that perform a variety of operations on Rhino. The library allows Python to be aware of Rhino's: * Geometry * Commands * Document objects * Application methods To make these methods easy-to-use, all RhinoScriptSyntax methods return simple Python variables or Python List-based data structures. Thus, once you are familiar with Python, you will be able to use any and all functions in the RhinoScriptSyntax methods library. For the whole namespace, see the [Rhinoscriptsyntax API reference](https://developer.rhino3d.com/api/RhinoScriptSyntax/). ## Importing RhinoScriptSyntax Before using RhinoScriptSyntax a line must be added to the top of each Python file to allow access to RhinoScriptSyntax: ```pyhon import rhinoscriptsyntax as rs ``` The `import` above not only imports the library, but also renames the module to `rs.`. This is done to just make it easier to type when accessing the methods in RhinoScriptSyntax. Access to the methods can now be made by starting methods with 'rs.'. For example, accessing the `AddCircle()` method in the RhinoScriptSyntax module can be used to create a circle: ```python import rhinoscriptsyntax as rs centerpoint = [1, 2, 4] rs.AddCircle( centerpoint, 5.0 ) ``` ## Geometry Rhino is a 3D modeler, therefore creating and modifying geometry is key to developing in Rhino. Here are the primitive geometry types of Rhino: - [Points](/guides/rhinopython/python-rhinoscriptsyntax-points) - [List of Points](/guides/rhinopython/python-rhinoscriptsyntax-list-points) - [Vectors](/guides/rhinopython/python-rhinoscriptsyntax-vectors) - [Lines](/guides/rhinopython/python-rhinoscriptsyntax-line) - [Planes](/guides/rhinopython/python-rhinoscriptsyntax-plane) - [Rhino Geometry Objects](/guides/rhinopython/python-rhinoscriptsyntax-objects) Creating, accessing and manipulating geometry is one of the first places RhinoScriptSyntax is used. Simple geometry such as points, lines, and planes can be described with lists in Python. More complicated geometry objects such as NURBS curves, Surfaces and Poly-surfaces can be created by Rhino and referenced by an object ID in RhinoScriptSyntax. -------------------------------------------------------------------------------- # Rotate Object Around Point Source: https://developer.rhino3d.com/en/samples/rhinoscript/rotate-object-around-point/ Demonstrates how to rotate an object around the centroid of its bounding box using RhinoScript. ```vbnet Sub Rotate1 Dim sObj, aBox, aMin, aMax, aCen sObj = Rhino.GetObject("Select object to rotate 1 degree", 0, True) If Not IsNull(sObj) Then aBox = Rhino.BoundingBox(sObj) If IsArray(aBox) Then aMin = aBox(0) aMax = aBox(6) aCen = Array( _ 0.5*(aMax(0)+aMin(0)), _ 0.5*(aMax(1)+aMin(1)), _ 0.5*(aMax(2)+aMin(2)) _ ) Rhino.RotateObject sObj, aCen, 1.0 End If End If End Sub ``` -------------------------------------------------------------------------------- # Rotate Objects Around Center Source: https://developer.rhino3d.com/en/samples/cpp/rotate-objects-around-center/ Demonstrates how rotate objects around the center point of their bounding box. ```cpp CRhinoCommand::result CCommandTest::RunCommand( const CRhinoCommandContext& context ) { // Select objects to rotate CRhinoGetObject go; go.SetCommandPrompt( L"Select objects to rotate" ); go.GetObjects( 1, 0 ); if( go.CommandResult() != success ) return go.CommandResult(); // Rotation angle (in degrees) CRhinoGetNumber gn; gn.SetCommandPrompt( L"Rotation angle" ); gn.SetDefaultNumber( m_angle ); gn.GetNumber(); if( gn.CommandResult() != success ) return gn.CommandResult(); // Validate input double angle = gn.Number(); if( angle == 0 ) return nothing; m_angle = angle; // Get the active view's construction plane ON_Plane plane = RhinoActiveCPlane(); // Do not split objects that get kinky // when they are transformed. CRhinoKeepKinkySurfaces keep_kinky_srfs; int i; for( i = 0; i < go.ObjectCount(); i++ ) { // Get an object reference const CRhinoObjRef& ref = go.Object(i); // Get the real object const CRhinoObject* obj = ref.Object(); if( !obj ) continue; // Get the object's tight bounding box ON_BoundingBox bbox; if( !obj->GetTightBoundingBox(bbox, false, 0) ) continue; // Create transformation matrix ON_Xform xform; xform.Rotation( m_angle * ON_PI / 180.0, plane.zaxis, bbox.Center() ); // Transform the object context.m_doc.TransformObject( obj, xform, true, true, true ); } context.m_doc.Redraw(); return success; } ``` -------------------------------------------------------------------------------- # Rotate Plane Parallel to World Source: https://developer.rhino3d.com/en/samples/rhinoscript/rotate-plane-parallel-to-world/ Demonstrates how to rotate a plane so its x-axis is parallel to the world using RhinoScript. ```vbnet Option Explicit '------------------------------------------------------------------------------ ' Subroutine: XParallelPlane ' Purpose: Rotate a plane about it's z-axis so that it's x-axis is parallel with the world xy-plane. ' Parameters: ' plane - A valid plane to rotate ' dir - Direction (True = positive, False = negative) ' Returns: ' A valid plane if successful, Null otherwise. '------------------------------------------------------------------------------ Function XParallelPlane(plane, dir) Dim xaxis, yaxis, zaxis XParallelPlane = Null 'default return value zaxis = Rhino.VectorUnitize(plane(3)) If (dir = True) Then xaxis = Rhino.VectorUnitize(Array(zaxis(1), -zaxis(0), 0.0)) Else xaxis = Rhino.VectorUnitize(Array(-zaxis(0), zaxis(1), 0.0)) End If yaxis = Rhino.VectorCrossProduct(zaxis, xaxis) If IsArray(yaxis) Then yaxis = Rhino.VectorUnitize(yaxis) XParallelPlane = Rhino.PlaneFromFrame(plane(0), xaxis, yaxis) End If End Function ``` -------------------------------------------------------------------------------- # Rounding Numbers Source: https://developer.rhino3d.com/en/guides/rhinoscript/rounding-numbers/ This guide discusses number rounding in RhinoScript. ## Overview You need to round when you want to convert a number of greater precision into a number of lesser precision. The most common case is when you need to convert a floating-point number into an integer. There are many different types of rounding: rounding up, rounding down, banker's rounding. This guide covers many of the common rounding methods and demonstrates those that can be done in VBScript. ## Rounding Down The simplest form of rounding is truncation. Any digits after the desired precision are simply ignored. The `Fix` function is an example of truncation. For example: ```vbnet Rhino.Print Fix( 3.5) ' 3 Rhino.Print Fix(-3.5) ' -3 ``` The `Int` function rounds down to the highest integer less than the value. Both `Int` and `Fix` act the same way with positive numbers - truncating - but give different results for negative numbers: ```vbnet Rhino.Print Int( 3.5) ' 3 Rhino.Print Int(-3.5) ' -4 ``` The `Fix` function is an example of symmetric rounding because it affects the magnitude (absolute value) of positive and negative numbers in the same way. The `Int` function is an example of asymmetric rounding because it affects the magnitude of positive and negative numbers differently. RhinoScript has a `Floor` function that truncates positive values, but does not work with negative numbers: ```vbnet Rhino.Print Rhino.Floor( 3.5) ' 3 Rhino.Print Rhino.Floor(-3.5) ' -4 ``` ## Rounding Up RhinoScript has a `Ceil` function which always rounds fraction values up (more positive) to the next value. ```vbnet Rhino.Print Rhino.Ceil( 3.5) ' 4 Rhino.Print Rhino.Ceil(-3.5) ' -3 ``` VBScript does not have a corresponding round-up function. However, for negative numbers, both `Fix` and `Int` can be used to round upward, in different ways. `Fix` rounds towards 0 (up in the absolute sense, but down in absolute magnitude): ```vbnet Rhino.Print Fix( 3.5) ' 3 Rhino.Print Fix(-3.5) ' -3 ``` `Int` rounds away from 0 (up in terms of absolute magnitude, but down in the absolute sense): ```vbnet Rhino.Print Int( 3.5) ' 3 Rhino.Print Int(-3.5) ' -4 ``` ## Arithmetic Rounding When you always round one direction, the resulting number is not necessarily the closest to the original number. For example, if you round 1.9 down to 1, the difference is a lot larger than if you round it up to 2. It is easy to see that numbers from 1.6 to 2.4 should be rounded to 2. But, what about 1.5, which is equidistant between 1 and 2? By convention, the half-way number is rounded up. You can implement rounding half-way numbers in a symmetric fashion, such that -.5 is rounded down to -1, or in an asymmetric fashion, where -.5 is rounded up to 0. VBScript does not have any functions that do arithmetic rounding. ## Banker's Rounding When you add rounded values together, always rounding .5 in the same direction results in a bias that grows the more numbers you add together. One way to minimize the bias is with banker's rounding. Banker's rounding rounds .5 up sometimes and down sometimes. The convention is to round to the nearest even number, so that both 1.5 and 2.5 round to 2, and 3.5 and 4.5 both round to 4. Banker's rounding is symmetric. In VBScript, the following numeric functions perform banker's rounding: `CByte`, `CInt`, `CLng`, `CCur`, and `Round`. ## Random Rounding Even banker's rounding can bias totals. You can take an extra step to remove bias by rounding .5 up or down in a truly random fashion. This way, even if the data is deliberately biased, bias might be minimized. However, using random rounding with randomly distributed data might result in a larger bias than banker's rounding. Random rounding could result in two different totals on the same data. VBScript does not have any functions that do random rounding. ## Alternate Rounding Alternate rounding is rounding between .5 up and .5 down on successive calls. VBScript does not have any functions that do alternate rounding. ## Round to Nearest Imagine you want to create a script that rounds a number up or down by a specified increment. For example, given the number 3.23, rounding to the nearest .05 results in the number 3.25. The example below accepts any positive rounding increment as a parameter. In addition to rounding numbers to the nearest fractional amount, you can also round to whole numbers, such as 1, 10, or 100... ```vbnet ' Function: ' RoundToNearest ' Description ' Rounds a number by an increment ' Parameters: ' Amt (Number) - number to round ' RoundAmt (Number) - increment to which Amt will be rounded ' bRoundUp (Boolean) - rounding direction (up or down) ' Function RoundToNearest(Amt, RoundAmt, bRoundUp) On Error Resume Next Dim Temp : Temp = Amt / RoundAmt If Int(Temp) = Temp Then RoundToNearest = Amt Else If (bRoundUp = True) Then Temp = Int(Temp) + 1 Else Temp = Int(Temp) End If RoundToNearest = Temp * RoundAmt End If End Function ``` The above script can be used as follows... ``` MsgBox RoundToNearest(1.36, 0.25, True) MsgBox RoundToNearest(1.36, 0.05, False) MsgBox RoundToNearest(1.36, 0.75, True) ``` ## Kitchen Sink The code that follows provides sample implementations for each of the rounding types described. All these functions take two arguments: the number to be rounded and a factor. If the factor is 1, then the functions return an integer created by one of the above methods. If the factor is something other than 1, the number is scaled by the factor to create different rounding effects. For example AsymArith(2.55, 10) produces 2.6, that is, it rounds to 1/factor = 1/10 = 0.1. **NOTE**: a factor of 0 generates a run-time error: 1/factor = 1/0. ```vbnet ' Asymmetrically rounds numbers down - similar to Int(). ' Negative numbers get more negative. Function AsymDown(X, Factor) AsymDown = Int(X * Factor) / Factor End Function ' Symmetrically rounds numbers down - similar to Fix(). ' Truncates all numbers toward 0. ' Same as AsymDown for positive numbers. Function SymDown(X, Factor) SymDown = Fix(X * Factor) / Factor End Function ' Asymmetrically rounds numbers fractions up. ' Same as SymDown for negative numbers. ' Similar to Rhino.Ceil(). Function AsymUp(X, Factor) Dim Temp Temp = Int(X * Factor) AsymUp = (Temp + IIf(X = Temp, 0, 1)) / Factor End Function ' Symmetrically rounds fractions up - that is, away from 0. ' Same as AsymUp for positive numbers. ' Same as AsymDown for negative numbers. Function SymUp(X, Factor) Dim Temp Temp = Fix(X * Factor) SymUp = (Temp + IIf(X = Temp, 0, Sgn(X))) / Factor End Function ' Asymmetric arithmetic rounding - rounds .5 up always. Function AsymArith(X, Factor) AsymArith = Int(X * Factor + 0.5) / Factor End Function ' Symmetric arithmetic rounding - rounds .5 away from 0. ' Same as AsymArith for positive numbers. Function SymArith(X, Factor) SymArith = Fix(X * Factor + 0.5 * Sgn(X)) / Factor End Function ' Banker's rounding. ' Rounds .5 up or down to achieve an even number. ' Symmetrical by definition. Function BRound(X, Factor) Dim Temp, FixTemp Temp = X * Factor FixTemp = Fix(Temp + 0.5 * Sgn(X)) If Temp - Int(Temp) = 0.5 Then If FixTemp / 2 <> Int(FixTemp / 2) Then FixTemp = FixTemp - Sgn(X) End If End If BRound = FixTemp / Factor End Function ' Random rounding. ' Rounds .5 up or down in a random fashion. ' (Execute Randomize statement somewhere prior to calling.) Function RandRound(X, Factor) Dim Temp, FixTemp Temp = X * Factor FixTemp = Fix(Temp + 0.5 * Sgn(X)) If Temp - Int(Temp) = 0.5 Then FixTemp = FixTemp - Int(Rnd * 2) * Sgn(X) End If RandRound = FixTemp / Factor End Function ' Alternating rounding. ' Alternates between rounding .5 up or down. Public fReduce Function AltRound(X, Factor) Dim Temp, FixTemp If IsEmpty(fReduce) Then fReduce = False Temp = X * Factor FixTemp = Fix(Temp + 0.5 * Sgn(X)) If Temp - Int(Temp) = 0.5 Then If (fReduce And Sgn(X) = 1) Or (Not fReduce And Sgn(X) = -1) Then FixTemp = FixTemp - Sgn(X) End If fReduce = Not fReduce End If AltRound = FixTemp / Factor End Function ``` Note, many of these sample functions require the following utility function... ```vbnet ' VBScript equivalent to VB's Immediate If function. Function IIf(expr, true_val, false_val) If expr Then IIf = true_val Else IIf = false_val End If End Function ``` The following table shows the effects of various factors: | Expression | | | | Result | | | | Comment | |:--------|:-:|:-:|:-:|:-------:|:-:|:-:|:-:|:--------| | `AsymArith(2.5)` | | | | 3 | | | | Rounds up to next integer. | | `BRound(2.18, 20)` | | | | 2.2 | | | | Rounds to the nearest 1/20. | | `SymDown(25, .1)` | | | | 20 | | | | Rounds down to an even multiple of 10. | ## Floating point Limitations All the rounding implementations presented here use the double data type, which can represent approximately 15 decimal digits. Since not all fractional values can be expressed exactly, you might get unexpected results because the display value does not match the stored value. For example, the number 2.25 might be stored internally as 2.2499999..., which would round down with arithmetic rounding, instead of up as you might expect. Also, the more calculations a number is put through, the greater possibility that the stored binary value will deviate from the ideal decimal value. ## Dropping Precision As taught in school, rounding is usually arithmetic rounding using positive numbers. With this type of rounding, you only need to know the number to 1 digit past where you are rounding to. You ignore digits past the first decimal place. In other words, precision is dropped as a shortcut to rounding the value. For example, both 2.5 and 2.51 round up to 3, while both 2.4 and 2.49 round down to 2. When you use banker's rounding (or other methods that round .5 either up or down) or when you round negative numbers using asymmetric arithmetic rounding, dropping precision can lead to incorrect results where you might not round to the nearest number. For example, with banker's rounding, 2.5 rounds down to 2 and 2.51 rounds up to 3. With asymmetric arithmetic rounding, -2.5 rounds up to -2 while -2.51 rounds down to -3. The user-defined functions presented in this guide take the number's full precision into account when performing rounding. -------------------------------------------------------------------------------- # Run a Rhino command from a Plugin Source: https://developer.rhino3d.com/en/guides/rhinocommon/run-rhino-command-from-plugin/ This guide covers the proper techniques when running a Rhino command from within the context of a plugin command. ## The Problem One of the most common questions asked by new plugin developers is how to run, or script, existing Rhino commands from a plugin command. Rhino doesn't allow plugin commands to run other commands except under very special circumstances. Here's the problem: If you have a command that is modifying the Rhino document, and you run another command, problems can happen. To work around this, the RhinoCommon provides a special kind of command called a script command. You can create a script command as follows... ## The Solution When defining your command class, make sure to add the `ScriptRunner` command style attribute. In other words, instead of defining your command classes like this: ```cs public class TestCommand : Rhino.Commands.Command ``` ```vbnet Public Class TestCommand Inherits Rhino.Commands.Command ``` Define your command classes like this: ```cs [Rhino.Commands.CommandStyle(Rhino.Commands.Style.ScriptRunner)] public class TestCommand : Rhino.Commands.Command ``` ```vbnet Public Class TestCommand Inherits Rhino.Commands.Command ``` Then, from within your command class's `RunCommand()` method, you can call `RhinoApp.RunScript()` to script the running of a Rhino command. For example... ```cs protected override Rhino.Commands.Result RunCommand(Rhino.RhinoDoc doc, Rhino.Commands.RunMode mode) { Rhino.RhinoApp.RunScript("_-Line 0,0,0 10,10,10", false); return Rhino.Commands.Result.Success; } ``` ```vbnet Protected Overrides Function RunCommand(ByVal doc As Rhino.RhinoDoc, ByVal mode As Rhino.Commands.RunMode) As Rhino.Commands.Result Rhino.RhinoApp.RunScript("_-Line 0,0,0 10,10,10", False) Return Rhino.Commands.Result.Success End Function ``` ## Warnings This kind of command can be very dangerous. Please be sure you understand the following: 1. If you are not very familiar with how references work, you should only call `Rhino.RhinoApp.RunScript()` from within a `RhinoScriptCommand` derived command. 1. If you are very familiar with references, then please observe the following rules: 1. If you get a reference or pointer to any part of the Rhino run-time database, this reference or pointer will not be valid after you call `Rhino.RhinoApp.RunScript()`. 1. If you get a reference or a pointer, then call `Rhino.RhinoApp.RunScript()`, and then use the reference, Rhino will probably crash. 1. All pointers and references used by the command should be scoped such that they are only valid for the time between calls to `Rhino.RhinoApp.RunScript()`. This is because `Rhino.RhinoApp.RunScript()` can change the dynamic arrays in the run-time database. The result is that all pointers and references become invalid. Be sure to scope your variables between `Rhino.RhinoApp.RunScript()` calls. Never allow references and pointers from one section to be used in another section. In a normal command, when the user enters a command beginning with a !, the command exits. There is no documented way to get this behavior from within a script command. -------------------------------------------------------------------------------- # Running Rhino Commands from Plugins Source: https://developer.rhino3d.com/en/guides/cpp/running-rhino-commands-from-plugins/ This guide discusses the proper techniques to use when running a Rhino command from within the context of a C/C++ plugin command. ## Overview One of the most common questions asked by new plugin developers is how to run, or script, existing Rhino commands from a plugin command. Rhino doesn't allow plugin commands to run other commands except under very special circumstances. Here's the issue: If you have a command that is modifying the run-time database, and you run another command, problems can happen. To work around this, the Rhino C/C++ SDK provides a special kind of command called a script command. You can create a script command as follows... ## How To Derive your command class from CRhinoScriptCommand instead of CRhinoCommand. In other words, instead of defining your command class like this: ```cpp class CCommandTest : public CRhinoCommand ``` Define your command class like this: ```cpp class CCommandTest : public CRhinoScriptCommand ``` Then, from within your command class's `RunCommand()` member, you can call `CRhinoApp::RunScript()` to script the running of a Rhino command. For example: ```cpp CRhinoCommand::result CCommandTest::RunCommand( const CRhinoCommandContext& context ) { RhinoApp().RunScript( L"_-Line 0,0,0 10,10,10", 0 ); return CRhinoCommand::success; } ``` ## Warning This kind of command can be very dangerous. Please be sure you understand the following: 1. If you are not very familiar with how C++ references work, you should only call `CRhinoApp::RunScript()` from within a `CRhinoScriptCommand` derived command. 1. If you are very familiar with C++ references, then please observe the following rules: 1. If you get a reference or pointer to any part of the Rhino run-time database, this reference or pointer will not be valid after you call `CRhinoApp::RunScript()`. 1. If you get a reference or a pointer, then call `CRhinoApp::RunScript()`, and then use the reference, Rhino will probably crash. 1. All pointers and references used by the command should be scoped such that they are only valid for the time between calls to `CRhinoApp::RunScript()`. This is because `CRhinoApp::RunScript()` can change the dynamic arrays in the run-time database. The result is that all pointers and references become invalid. Be sure to scope your variables between `CRhinoApp::RunScript()` calls. **NOTE**: In a normal command, when the user enters a command beginning with a !, the command exits. There is no documented way to get this behavior from within a script command. ## Sample Here's good scoping practice when your command is a script command. ```cpp CRhinoCommand::result CCommandTest::RunCommand( const CRhinoCommandContext& context ) { { section A ... do some stuff ... } RhinoApp().RunScript(...); { section B ... do some stuff ... } RhinoApp().RunScript(...); { section C ... do some stuff ... } RhinoApp().RunScript(...); { section D ... do some stuff ... } RhinoApp().RunScript(...); { section E ... do some stuff ... } return CRhinoCommand::success; } ``` Never allow references and pointers from one section to be used in another section. -------------------------------------------------------------------------------- # Running Rhino from the Command Line Source: https://developer.rhino3d.com/en/guides/cpp/running-rhino-from-command-line/ This guide explains how to run Rhino from the command line. ## Overview Under some circumstances, it is useful to run Rhino from the command prompt. Perhaps you are batch processing many files, or maybe you need to run Rhino from a render farm management system. Rhino provides command line options (arguments) to allow you to do exactly this. ## Rhino for Windows The format is: ```cmd Rhino.exe /runscript="-command -secondCommand" ``` For example, if you want to start Rhino with a file called `hdri_test.3dm`, render it with the built-in renderer, save the file, and exit Rhino, you would pass this: ```cmd "C:\Program Files\Rhinoceros 5 (64-bit)\System\Rhino.exe" /runscript="_SetCurrentRenderPlugIn Rhinoceros _Render _-SaveRenderWindowAs test.jpg _-CloseRenderWindow _-Exit" test.3dm ``` ### Command line options The following command line options are available in Rhino for Windows: - `/safemode`: Start in safe mode. - `/nosplash`: Suppress startup splash screen. - `/bigdump`: When Rhino crashes, include the full memory content and, also, call stack information. This can generate huge crash dumps – as big as the amount of RAM Rhino is using when it crashes. - `/notemplate`: Start Rhino with a default model based on hard-coded defaults. - `/language`: Set the startup language. For example, to start in Spanish, pass `/language=1034`. - `/scheme`: Use a custom scheme. This is used by third party developers that include custom schemes. - `/runscript`: Run a script at startup. -------------------------------------------------------------------------------- # Running Scripts from Macros Source: https://developer.rhino3d.com/en/guides/rhinoscript/running-scripts-from-macros/ This guide explains how to set up and run macros and RhinoScripts. ## How To ### Creating button or alias for your macro or script The simplest way to save and run your macro is from a toolbar button or alias. If you don’t know how to make a new toolbar button or alias, look in the Help file. There’s a good explanation. Once you have your new button (or have chosen to edit an existing one), open the editor by shift+right-clicking on the button. For an alias, you will do the same thing, but instead of creating a new button, go into *Options* > *Aliases* and use the *New* button to create a new alias. ### Use the macro editor to work out new macros The *MacroEditor* command opens a text editing window in which you can type macros and try them out without the need to edit a button every time. The run button on the lower edge of the editor runs the macro. If there is selected text, it runs the selected text. When it all runs to your satisfaction, copy and paste the macro to a toolbar button. ![Macro Editor](https://developer.rhino3d.com/images/running-scripts-from-macros-01.png) ### Paste your macro or script into the button or alias Now, there are two ways to approach associating the macro or script to your button or alias. First and simplest is to just copy/paste the whole thing into the left or right button box (or in the alias box). The advantage of the button method is that it is portable. That is, if you copy or export the button to another installation the macro goes with it. Once the test is pasted in, click *OK* to exit the button editor, and you’re ready to go! The paste-in-button (or alias) method is fine for macros of Rhino commands and shorter, smaller scripts, but it gets a bit unwieldy to edit if there is a great deal of text. For larger scripts, some people like to place them externally in a folder with a link so that Rhino can find them. Both toolbar buttons and aliases can link to external scripts. One advantage of this system is that all scripts are located in one spot so you can easily find and update them. The problem here is that if you copy your button or workspace for use somewhere else, you have to remember to bring the script with it. ### Linking to external scripts To set up an external scripts folder: Find a logical place to create your folder. *Take care in choosing this folder. Make sure that the user has permissions to access it.* Open the *Options* dialog, and navigate to the *Files* tab. In the file search paths box, click the new button and then the little *...* button and browse to the location of the scripts folder. Then click *OK*. Exit the options dialog. Rhino will now go looking for scripts in this folder. Currently Python does not read this section, so if you use Python scripts you will also need to do a similar operation inside the Python script editor: Open the editor with *EditPythonScript* and go to *Tools* > *Options* and enter the path to your folder in *Module Search Paths*. To link your button or alias to an external script: The syntax used will depend on the type of script. If it is a simple text file with normal Rhino commands (like a long macro), you will need to use the command *ReadCommandFile Filename.txt* Substitute the name of your text file for *Filename.txt*. Paste that string into the left or right button box and you’re good to go. To run a *RhinoScript .rvb* file use the command *LoadScript Filename.rvb* instead. In general, that’s all you need to do, but some scripts are written to run immediately on load. Others are not, so some script tweaking may be necessary. ![Edit Toolbar Button Dialog](https://developer.rhino3d.com/images/running-scripts-from-macros-02.png) You can also paste an entire RhinoScript into a button. For that, start with the command *-_RunScript* (not *-_LoadScript*) followed by a space and an open parentheses. At the end of the script you need a close parentheses. For python scripts, you want to use *-_RunPythonScript* instead of just *-_RunScript*. Don't forget the dash in front of the command, otherwise it will stop and prompt you for what script you want to run. The underbar insures that the *_-Runscript* command will run in languages other than English, but it will not insure the actual script will do the same. It has to be written correctly as well. ``` ! _NoEcho -_Runscript ( Paste in your entire script here ) ``` The button editor showing pasted in complete RhinoScript should look something like this... ![Edit Toolbar Button Done](https://developer.rhino3d.com/images/running-scripts-from-macros-03.png) -------------------------------------------------------------------------------- # Save and Restore Layer States Source: https://developer.rhino3d.com/en/samples/rhinoscript/save-and-restore-layer-states/ Demonstrates how to save and restore the states of layers using RhinoScript. ```vbnet Option Explicit '-------------------------------------------------------------------- ' Subroutine: SaveLayerStates ' Purpose: Saves a "named" layer state to an INI-style file. '-------------------------------------------------------------------- Sub SaveLayerStates If StrComp(Rhino.DocumentName, "untitled", 1) = 0 Then Rhino.MessageBox "You must save your model before using _ this script.", 48, "LayerStates" Exit Sub End If Dim arrLayers arrLayers = Rhino.LayerNames If Not IsArray(arrLayers) Then Exit Sub Dim strName strName = Rhino.StringBox("Save Layer State As", , "LayerStates") If IsNull(strName) Then Exit Sub Dim strFile strFile = Rhino.DocumentPath & Rhino.DocumentName strFile = Replace(strFile, ".3dm", ".layer", 1, -1, 1) Dim strLayer, strValue For Each strLayer In arrLayers strValue = CStr(CInt(Rhino.IsLayerCurrent(strLayer))) strValue = strValue & ";" & CStr(Rhino.LayerMode(strLayer)) strValue = strValue & ";" & CStr(Rhino.LayerColor(strLayer)) Rhino.SaveSettings strFile, strName, strLayer, strValue Next End Sub '-------------------------------------------------------------------- ' Subroutine: RestoreLayerStates ' Purpose: Restores a "named" layer state from an INI-style file. '-------------------------------------------------------------------- Sub RestoreLayerStates If StrComp(Rhino.DocumentName, "untitled", 1) = 0 Then Rhino.MessageBox "This script only works on saved models.", _ 48, "LayerStates" Exit Sub End If Dim strFile strFile = Rhino.DocumentPath & Rhino.DocumentName strFile = Replace(strFile, ".3dm", ".layer", 1, -1, 1) Dim arrNames arrNames = Rhino.GetSettings(strFile) If Not IsArray(arrNames) Then Rhino.MessageBox "No layer states to restore.", 64, "LayerStates" Exit Sub End If Dim strName strName = Rhino.ListBox(arrNames, "Layer state to restore", _ "LayerStates") If IsNull(strName) Then Exit Sub Dim arrLayers arrLayers = Rhino.GetSettings(strFile, strName) If Not IsArray(arrLayers) Then Rhino.MessageBox "No layers to restore.", 64, "LayerStates" Exit Sub End If Dim strLayer, strValue, arrValues For Each strLayer In arrLayers strValue = Rhino.GetSettings(strFile, strName, strLayer) arrValues = Split(strValue, ";") If CBool(arrValues(0)) = True Then Rhino.CurrentLayer strLayer Rhino.LayerMode strLayer, CInt(arrValues(1)) Rhino.LayerColor strLayer, CLng(arrValues(2)) Next End Sub ``` -------------------------------------------------------------------------------- # Save Plugin List to File Source: https://developer.rhino3d.com/en/samples/rhinoscript/save-plugin-list-to-file/ Demonstrates how to save the names of loaded and unloaded plugins to a text file using RhinoScript. ```vbnet ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' SavePlugInInfo.rvb -- April 2008 ' If this code works, it was written by Dale Fugier. ' If not, I don't know who wrote it. ' Works with Rhino 4.0. Option Explicit Sub SavePlugInInfo() Dim objShell, objNetwork, objFSO, objFolder, objStream Dim arrPlugIns, arrSorted, strPlugIn, strDesktop, strFile, strName, strMsg Set objShell = CreateObject("WScript.Shell") Set objNetwork = CreateObject("WScript.Network") Set objFSO = CreateObject("Scripting.FileSystemObject") strName = "RhinoPlugInInfo.txt" strDesktop = objShell.SpecialFolders("Desktop") strFile = strDeskTop & "\" & strName 'On Error Resume Next Set objStream = objFSO.CreateTextFile(strFile, True) If Err Then MsgBox Err.Description Exit Sub End If objStream.WriteLine "**************************" objStream.WriteLine "Rhino Plug-in Info" objStream.WriteLine objStream.WriteLine "Computer Name = " & objNetwork.ComputerName objStream.WriteLine "Date and Time = " & CStr(Now) objStream.WriteLine "Rhino Build Date = " & CStr(Rhino.BuildDate) objStream.WriteLine "Rhino SDK Version = " & CStr(Rhino.SdkVersion) objStream.WriteLine "**************************" objStream.WriteLine objStream.WriteLine "**************************" objStream.WriteLine "Loaded Plug-ins" objStream.WriteLine "**************************" objStream.WriteLine arrPlugIns = Rhino.PlugIns(0, 1) If IsArray(arrPlugIns) Then arrSorted = Rhino.SortStrings(arrPlugIns) For Each strPlugIn In arrSorted objStream.WriteLine strPlugIn Next End If objStream.WriteLine objStream.WriteLine "**************************" objStream.WriteLine "Unloaded Plug-ins" objStream.WriteLine "**************************" objStream.WriteLine arrPlugIns = Rhino.PlugIns(0, 2) If IsArray(arrPlugIns) Then arrSorted = Rhino.SortStrings(arrPlugIns) For Each strPlugIn In arrSorted objStream.WriteLine strPlugIn Next End If objStream.Close strMsg = "A file named " & Chr(34) & strName & Chr(34) & VbCrLf strMsg = strMsg & "has been saved to your desktop." & VbCrLf & VbCrLf strMsg = strMsg & "If you are experiencing problems with Rhino," & VbCrLf strMsg = strMsg & "email this file to " & Chr(34) & "tech@mcneel.com" & Chr(34) & VbCrLf strMsg = strMsg & "along with a detailed description" & VbCrLf strMsg = strMsg & "of your problem." MsgBox strMsg, 64, "Rhinoceros" End Sub ' Rhino.AddStartUpScript Rhino.LastLoadedScriptFile ' Rhino.AddAlias "SavePlugInInfo", "_-RunScript (SavePlugInInfo)" ' Run it! Call SavePlugInInfo ``` -------------------------------------------------------------------------------- # Save Video Card Info Source: https://developer.rhino3d.com/en/samples/rhinoscript/save-video-card-info/ Demonstrates how to save information about your system's video card to a text file using RhinoScript. ```vbnet ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' SaveVideoInfo.rvb -- April 2008 ' If this code works, it was written by Dale Fugier. ' If not, I don't know who wrote it. ' Works with Rhino 4.0. Option Explicit Sub SaveVideoInfo() Dim objShell, objNetwork, objFSO, objFolder, objStream Dim strDesktop, strFile, strName, strMsg Set objShell = CreateObject("WScript.Shell") Set objNetwork = CreateObject("WScript.Network") Set objFSO = CreateObject("Scripting.FileSystemObject") strName = "RhinoVideoInfo.txt" strDesktop = objShell.SpecialFolders("Desktop") strFile = strDeskTop & "\" & strName On Error Resume Next Set objStream = objFSO.CreateTextFile(strFile, True) If Err Then MsgBox Err.Description Exit Sub End If objStream.WriteLine "**************************" objStream.WriteLine "Rhino Video Info" objStream.WriteLine objStream.WriteLine "Computer Name = " & objNetwork.ComputerName objStream.WriteLine "Date and Time = " & CStr(Now) objStream.WriteLine "Rhino Build Date = " & CStr(Rhino.BuildDate) objStream.WriteLine "Rhino SDK Version = " & CStr(Rhino.SdkVersion) objStream.WriteLine "**************************" objStream.WriteLine Call DisplayConfiguration(objStream) Call VideoAdapterInformation(objStream) Call VideoControllerProperties(objStream) Call MonitorProperties(objStream) ' Uncomment the following line if you want the report to include ' all possible video card resolutions. ' Call VideoResolutions(objStream) objStream.Close strMsg = "A file named " & Chr(34) & strName & Chr(34) & VbCrLf strMsg = strMsg & "has been saved to your desktop." & VbCrLf & VbCrLf strMsg = strMsg & "If you are experiencing problems with Rhino," & VbCrLf strMsg = strMsg & "email this file to " & Chr(34) & "tech@mcneel.com" & Chr(34) & VbCrLf strMsg = strMsg & "along with a detailed description" & VbCrLf strMsg = strMsg & "of your problem." MsgBox strMsg, 64, "Rhinoceros" End Sub ' Returns a list of all the possible video display resolutions. Sub VideoResolutions(ByRef objStream) Dim strComputer, objWMIService, colItems, objItem objStream.WriteLine "**************************" objStream.WriteLine "Video Resolutions" objStream.WriteLine "**************************" objStream.WriteLine On Error Resume Next strComputer = "." Set objWMIService = GetObject("winmgmts:" & "{impersonationLevel=impersonate}!\\" & strComputer & "\root\cimv2") Set colItems = objWMIService.ExecQuery ("Select * from CIM_VideoControllerResolution") For Each objItem In colItems objStream.WriteLine "Horizontal Resolution: " & objItem.HorizontalResolution objStream.WriteLine "Number Of Colors: " & objItem.NumberOfColors objStream.WriteLine "Refresh Rate: " & objItem.RefreshRate objStream.WriteLine "Scan Mode: " & objItem.ScanMode objStream.WriteLine "Setting ID: " & objItem.SettingID objStream.WriteLine "Vertical Resolution: " & objItem.VerticalResolution objStream.WriteLine Next End Sub ' Returns information about the current display settings. Sub DisplayConfiguration(ByRef objStream) Dim strComputer, objWMIService, colItems, objItem objStream.WriteLine "**************************" objStream.WriteLine "Display Configuration" objStream.WriteLine "**************************" objStream.WriteLine On Error Resume Next strComputer = "." Set objWMIService = GetObject("winmgmts:" & "{impersonationLevel=impersonate}!\\" & strComputer & "\root\cimv2") Set colItems = objWMIService.ExecQuery ("Select * from Win32_DisplayConfiguration") For Each objItem In colItems objStream.WriteLine "Bits Per Pel: " & objItem.BitsPerPel objStream.WriteLine "Device Name: " & objItem.DeviceName objStream.WriteLine "Display Flags: " & objItem.DisplayFlags objStream.WriteLine "Display Frequency: " & objItem.DisplayFrequency objStream.WriteLine "Driver Version: " & objItem.DriverVersion objStream.WriteLine "Log Pixels: " & objItem.LogPixels objStream.WriteLine "Pels Height: " & objItem.PelsHeight objStream.WriteLine "Pels Width: " & objItem.PelsWidth objStream.WriteLine "Setting ID: " & objItem.SettingID objStream.WriteLine "Specification Version: " & objItem.SpecificationVersion objStream.WriteLine Next End Sub ' Returns information about the desktop monitor. Sub MonitorProperties(ByRef objStream) Dim strComputer, objWMIService, colItems, objItem objStream.WriteLine "**************************" objStream.WriteLine "Monitor Properties" objStream.WriteLine "**************************" objStream.WriteLine strComputer = "." Set objWMIService = GetObject("winmgmts:" & "{impersonationLevel=impersonate}!\\" & strComputer & "\root\cimv2") Set colItems = objWMIService.ExecQuery("Select * from Win32_DesktopMonitor") For Each objItem In colItems objStream.WriteLine "Availability: " & objItem.Availability objStream.WriteLine "Bandwidth: " & objItem.Bandwidth objStream.WriteLine "Description: " & objItem.Description objStream.WriteLine "Device ID: " & objItem.DeviceID objStream.WriteLine "Display Type: " & objItem.DisplayType objStream.WriteLine "Is Locked: " & objItem.IsLocked objStream.WriteLine "Monitor Manufacturer: " & objItem.MonitorManufacturer objStream.WriteLine "Monitor Type: " & objItem.MonitorType objStream.WriteLine "Name: " & objItem.Name objStream.WriteLine "Pixels Per X Logical Inch: " & objItem.PixelsPerXLogicalInch objStream.WriteLine "Pixels Per Y Logical Inch: " & objItem.PixelsPerYLogicalInch objStream.WriteLine "PNP Device ID: " & objItem.PNPDeviceID objStream.WriteLine "Screen Height: " & objItem.ScreenHeight objStream.WriteLine "Screen Width: " & objItem.ScreenWidth objStream.WriteLine Next End Sub ' Returns information about the video adapters. Sub VideoAdapterInformation(ByRef objStream) Dim strComputer, objWMIService, colItems, objItem objStream.WriteLine "**************************" objStream.WriteLine "Video Adapter Information" objStream.WriteLine "**************************" objStream.WriteLine On Error Resume Next strComputer = "." Set objWMIService = GetObject("winmgmts:" & "{impersonationLevel=impersonate}!\\" & strComputer & "\root\cimv2") Set colItems = objWMIService.ExecQuery ("Select * from Win32_DisplayControllerConfiguration") For Each objItem In colItems objStream.WriteLine "Bits Per Pixel: " & objItem.BitsPerPixel objStream.WriteLine "Color Planes: " & objItem.ColorPlanes objStream.WriteLine "Device Entries in a Color Table: " & objItem.DeviceEntriesInAColorTable objStream.WriteLine "Device Specific Pens: " & objItem.DeviceSpecificPens objStream.WriteLine "Horizontal Resolution: " & objItem.HorizontalResolution objStream.WriteLine "Name: " & objItem.Name objStream.WriteLine "Refresh Rate: " & objItem.RefreshRate objStream.WriteLine "Setting ID: " & objItem.SettingID objStream.WriteLine "Vertical Resolution: " & objItem.VerticalResolution objStream.WriteLine "Video Mode: " & objItem.VideoMode objStream.WriteLine Next End Sub ' Retrieves information about the video controller. Sub VideoControllerProperties(ByRef objStream) Dim strComputer, objWMIService, colItems, objItem, strCapability objStream.WriteLine "**************************" objStream.WriteLine "Video Controller Properties" objStream.WriteLine "**************************" objStream.WriteLine On Error Resume Next strComputer = "." Set objWMIService = GetObject("winmgmts:\\" & strComputer & "\root\cimv2") Set colItems = objWMIService.ExecQuery ("Select * from Win32_VideoController") For Each objItem In colItems For Each strCapability In objItem.AcceleratorCapabilities objStream.WriteLine "Accelerator Capability: " & strCapability Next objStream.WriteLine "Adapter Compatibility: " & objItem.AdapterCompatibility objStream.WriteLine "Adapter DAC Type: " & objItem.AdapterDACType objStream.WriteLine "Adapter RAM: " & objItem.AdapterRAM objStream.WriteLine "Availability: " & objItem.Availability objStream.WriteLine "Color Table Entries: " & objItem.ColorTableEntries objStream.WriteLine "Current Bits Per Pixel: " & objItem.CurrentBitsPerPixel objStream.WriteLine "Current Horizontal Resolution: " & objItem.CurrentHorizontalResolution objStream.WriteLine "Current Number of Colors: " & objItem.CurrentNumberOfColors objStream.WriteLine "Current Number of Columns: " & objItem.CurrentNumberOfColumns objStream.WriteLine "Current Number of Rows: " & objItem.CurrentNumberOfRows objStream.WriteLine "Current Refresh Rate: " & objItem.CurrentRefreshRate objStream.WriteLine "Current Scan Mode: " & objItem.CurrentScanMode objStream.WriteLine "Current Vertical Resolution: " & objItem.CurrentVerticalResolution objStream.WriteLine "Description: " & objItem.Description objStream.WriteLine "Device ID: " & objItem.DeviceID objStream.WriteLine "Device Specific Pens: " & objItem.DeviceSpecificPens objStream.WriteLine "Dither Type: " & objItem.DitherType objStream.WriteLine "Driver Date: " & objItem.DriverDate objStream.WriteLine "Driver Version: " & objItem.DriverVersion objStream.WriteLine "ICM Intent: " & objItem.ICMIntent objStream.WriteLine "ICM Method: " & objItem.ICMMethod objStream.WriteLine "INF Filename: " & objItem.InfFilename objStream.WriteLine "INF Section: " & objItem.InfSection objStream.WriteLine "Installed Display Drivers: " & objItem.InstalledDisplayDrivers objStream.WriteLine "Maximum Memory Supported: " & objItem.MaxMemorySupported objStream.WriteLine "Maximum Number Controlled: " & objItem.MaxNumberControlled objStream.WriteLine "Maximum Refresh Rate: " & objItem.MaxRefreshRate objStream.WriteLine "Minimum Refresh Rate: " & objItem.MinRefreshRate objStream.WriteLine "Monochrome: " & objItem.Monochrome objStream.WriteLine "Name: " & objItem.Name objStream.WriteLine "Number of Color Planes: " & objItem.NumberOfColorPlanes objStream.WriteLine "Number of Video Pages: " & objItem.NumberOfVideoPages objStream.WriteLine "PNP Device ID: " & objItem.PNPDeviceID objStream.WriteLine "Reserved System Palette Entries: " & objItem.ReservedSystemPaletteEntries objStream.WriteLine "Specification Version: " & objItem.SpecificationVersion objStream.WriteLine "System Palette Entries: " & objItem.SystemPaletteEntries objStream.WriteLine "Video Architecture: " & objItem.VideoArchitecture objStream.WriteLine "Video Memory Type: " & objItem.VideoMemoryType objStream.WriteLine "Video Mode: " & objItem.VideoMode objStream.WriteLine "Video Mode Description: " & objItem.VideoModeDescription objStream.WriteLine "Video Processor: " & objItem.VideoProcessor objStream.WriteLine Next End Sub ' Rhino.AddStartUpScript Rhino.LastLoadedScriptFile ' Rhino.AddAlias "SavePlugInList", "_-RunScript (SaveVideoInfo)" ' Run it! Call SaveVideoInfo ``` -------------------------------------------------------------------------------- # Saving File Summary Info Source: https://developer.rhino3d.com/en/guides/rhinoscript/saving-file-summary-info/ This brief guide demonstrates how to display the File Properties dialog when saving Rhino files using RhinoScript. ## Problem When you right-click on a file, using Windows Explorer, and pick “Properties...”, to bring up the File Properties dialog, you can add summary information to a file by clicking on the Summary tab and entering the appropriate information. Imagine you would like to do this when saving Rhino files. Rhino does not have this capability, but RhinoScript can help. ## Solution The following example script will save the Rhino file by scripting Rhino's Save command. If the command was successful in saving the file, script will then display the File Properties dialog and display the summary information for that file... ```vbnet Sub SuperSaver() Rhino.Command "_Save" If (0 = Rhino.LastCommandResult()) Then Dim objShell, objFolder, objFolderItem, objInfo Set objShell = CreateObject("Shell.Application") Set objFolder = objShell.NameSpace(Rhino.DocumentPath) If (Not objFolder Is Nothing) Then Set objFolderItem = objFolder.ParseName(Rhino.DocumentName) If (Not objFolderItem Is Nothing) Then Call objFolderItem.InvokeVerbEx("properties", "summary") End If Set objFolderItem = Nothing End If Set objFolder = Nothing Set objShell = Nothing End If End Sub ``` -------------------------------------------------------------------------------- # Saving Files Source: https://developer.rhino3d.com/en/samples/rhinoscript/saving-files/ Demonstrates how to save a file using RhinoScript. ```vbnet Option Explicit Sub SaveRhinoFile ' Declare local variables Dim strFileName, strCommand ' Prompt the user for the name of the file to save strFileName = Rhino.SaveFileName("Save", "Rhino 3D Models (*.3dm)|*.3dm||") If IsNull(strFileName) Then Exit Sub ' Since filenames can contain spaces, we need to ' surround the string with double-quote characters, ' or "", when scripting. strFileName = Chr(34) & strFileName & Chr(34) ' Build the command script strCommand = "_-Save " & strFileName ' Script the save command Call Rhino.Command(strCommand, 0) End Sub ``` -------------------------------------------------------------------------------- # Saving Persistent Settings Source: https://developer.rhino3d.com/en/guides/cpp/saving-persistent-settings/ This guide discusses how to save plugin settings to the Registry using C/C++. ## Problem Your plugin maintains a number of settings that need to be retained between sessions. Is there an easy way to do this? Do these setting migrate from one Rhino service release to another? ## Solution You can load and save persistent plugin setting from and to the Registry using the `CRhinoPlugIn::LoadProfile` and `CRhinoPlugIn::SaveProfile` virtual functions. For information on these virtual function, see *rhinoSdkPlugIn.h*. ## How To Lets say we have a sample plugin class declaration that looks something like the following: ```cpp class CTestPlugIn : public CRhinoUtilityPlugIn { public: CTestPlugIn(); ~CTestPlugIn(); // Required overrides const wchar_t* PlugInName() const; const wchar_t* PlugInVersion() const; GUID PlugInID() const; BOOL OnLoadPlugIn(); void OnUnloadPlugIn(); private: ON_wString m_plugin_version; // Persistent data bool m_value0; int m_value1; double m_value2; ON_wString m_value3; }; ``` Simply, overload the `CRhinoPlugIn::LoadProfile` and `CRhinoPlugIn::SaveProfile` virtual functions by adding the the following public declarations to our plugin class: ```cpp void LoadProfile( LPCTSTR lpszSection, CRhinoProfileContext& pc ); void SaveProfile( LPCTSTR lpszSection, CRhinoProfileContext& pc ); ``` Then, provide the definition: ```cpp void CTestPlugIn::LoadProfile( LPCTSTR lpszSection, CRhinoProfileContext& pc ) { pc.LoadProfileBool( lpszSection, L"value0", &m_value0 ); pc.LoadProfileInt( lpszSection, L"value1", &m_value1 ); pc.LoadProfileDouble( lpszSection, L"value2", &m_value2 ); pc.LoadProfileString( lpszSection, L"value3", m_value3 ); } void CTestPlugIn::SaveProfile( LPCTSTR lpszSection, CRhinoProfileContext& pc ) { pc.SaveProfileBool( lpszSection, L"value0", m_value0 ); pc.SaveProfileInt( lpszSection, L"value1", m_value1 ); pc.SaveProfileDouble( lpszSection, L"value2", m_value2 ); pc.SaveProfileString( lpszSection, L"value3", m_value3 ); } ``` That's it! Your data is now saved at the following location in the Registry: ``` HKEY_CURRENT_USER\Software\McNeel\Rhinoceros\\\Plug-ins\\Settings ``` -------------------------------------------------------------------------------- # Scale Text by Dimension Scale Source: https://developer.rhino3d.com/en/samples/rhinoscript/scale-text-by-dimension-scale/ Demonstrates how to properly scale text objects by the document's dimension scale in RhinoScript. ```vbnet Option Explicit ' Scales all text objects by the document's dimension scale Sub DimScaleText ' Dim local variables Dim arrObjects, strObject, arrPlane, dblScale ' Get document's dimension scale dblScale = Rhino.DimScale If dblScale = 1.0 Then Rhino.Print "Dimension scale set to 1.0." Exit Sub End If ' Get ids of all annotation objects arrObjects = Rhino.ObjectsByType(512) If Not IsArray(arrObjects) Then Rhino.Print "No text objects to scale." Exit Sub End If ' Turn off viewport redrawing (faster) Rhino.EnableRedraw False ' Save current view's construction plane arrPlane = Rhino.ViewCPlane(Rhino.CurrentView) ' Process each object For Each strObject In arrObjects ' Verify object is a text object If Rhino.IsText(strObject) And Rhino.IsObjectSelectable(strObject) Then ' Set the current view's construction plane to plane ' that defines the position and orientatio of the text Rhino.ViewCPlane Rhino.CurrentView, Rhino.TextObjectPlane(strObject) ' Select the object Rhino.SelectObject strObject ' Scale the object by the dimension scale Rhino.Command "_-Scale 0,0,0 " & CStr(dblScale), False ' Unselect the object Rhino.UnselectObject strObject End If Next ' Restore current view's construction plane Rhino.ViewCPlane Rhino.CurrentView, arrPlane ' Turn on viewport drawing Rhino.EnableRedraw True End Sub ``` -------------------------------------------------------------------------------- # Screen Capture All Viewports Source: https://developer.rhino3d.com/en/samples/cpp/screen-capture-all-viewports/ Demonstrates how to screen capture all the visible viewports to a file. ```cpp CRhinoCommand::result CCommandTest::RunCommand( const CRhinoCommandContext& context ) { AFX_MANAGE_STATE( ::RhinoApp().RhinoModuleState() ); CWnd* pMainWnd = CWnd::FromHandle( RhinoApp().MainWnd() ); if( 0 == pMainWnd ) return failure; CRhinoGetFileDialog gf; gf.SetScriptMode( context.IsInteractive() ? FALSE : TRUE ); BOOL rc = gf.DisplayFileDialog( CRhinoGetFileDialog::save_bitmap_dialog, 0, pMainWnd ); if( !rc ) return cancel; ON_wString filename = gf.FileName(); filename.TrimLeftAndRight(); if( filename.IsEmpty() ) return nothing; // Wait for the dialog to disappear. Otherwise, // we might see dialog artifacts in our image. RhinoApp().Wait( 500 ); CMDIFrameWnd* pFrameWnd = (CMDIFrameWnd*)pMainWnd; if( pFrameWnd ) { CWnd* pClientWnd = CWnd::FromHandle( pFrameWnd->m_hWndMDIClient ); if( pClientWnd ) { CClientDC srcDC( pClientWnd ); CRect rect; pClientWnd->GetClientRect( rect ); CRhinoDib dib; if( dib.CreateDib(rect.Width(), rect.Height(), 24, true) ) { CDC* dstDC = dib; if( dstDC ) { dstDC->BitBlt( 0, 0, rect.Width(), rect.Height(), &srcDC, 0, 0, SRCCOPY ); dib.CopyToClipboard( 0 ); dib.WriteToFile( filename ); } } } } return success; } ``` -------------------------------------------------------------------------------- # Screen Capture Viewport Source: https://developer.rhino3d.com/en/samples/cpp/screen-capture-viewport/ Demonstrates how to screen capture a viewport to a file. ```cpp CRhinoCommand::result CCommandTest::RunCommand( const CRhinoCommandContext& context ) { CWnd* pMainWnd = CWnd::FromHandle( RhinoApp().MainWnd() ); if( 0 == pMainWnd ) return failure; CRhinoGetFileDialog gf; gf.SetScriptMode( context.IsInteractive() ? FALSE : TRUE ); BOOL rc = gf.DisplayFileDialog( CRhinoGetFileDialog::save_bitmap_dialog, 0, pMainWnd ); if( !rc ) return cancel; ON_wString filename = gf.FileName(); filename.TrimLeftAndRight(); if( filename.IsEmpty() ) return nothing; CRhinoView* view = RhinoApp().ActiveView(); if( view ) { CRect rect; view->GetClientRect( rect ); CRhinoDib dib; if( dib.CreateDib(rect.Width(), rect.Height(), 24, true) ) { // Set these flags as you wish. BOOL bIgnoreHighlights = TRUE; BOOL bDrawTitle = FALSE; BOOL bDrawConstructionPlane = FALSE; BOOL bDrawWorldAxes = FALSE; CRhinoObjectIterator it( CRhinoObjectIterator::normal_or_locked_objects, CRhinoObjectIterator::active_and_reference_objects ); if( view->ActiveViewport().DisplayMode() == ON::wireframe_display ) { context.m_doc.DrawToDC( it, dib, dib.Width(), dib.Height(), view->ActiveViewport().View(), bIgnoreHighlights, bDrawTitle, bDrawConstructionPlane, bDrawWorldAxes ); } else { context.m_doc.RenderToDC( it, dib, dib.Width(), dib.Height(), view->ActiveViewport().View(), bIgnoreHighlights, bDrawTitle, bDrawConstructionPlane, bDrawWorldAxes, view->ActiveViewport().GhostedShade() ); } dib.WriteToFile( filename ); } } return success; } ``` -------------------------------------------------------------------------------- # Script Demand Loading Source: https://developer.rhino3d.com/en/guides/rhinoscript/script-demand-load/ This guide demonstrates how to demand load and run RhinoScript routines. ## Overview The following RhinoScript example presents an organized and efficient method for loading and running all of the script that you have either written or accumulated. The following example below demonstrates how to write a single startup script that can load and run any of your scripts on demand. ## Details The *Startup.rvb* sample script, below, defines two special subroutines: 1. `DemandRun` checks for the existence of a user-defined subroutine. If the subroutine is not found, then script file, where the procedure is located, is loaded by running the LoadScript command. Finally, the specified procedure is called. 1. `DemandRegister` registers command aliases that run macros that utilize the DemandRun subroutine. At the bottom of the script file, you can “register” one or more command aliases and specify the scripts they will run. When Rhino starts and this script file is loaded, all of your command aliases are registered. But, none of your script files are loaded (except for the one and only *Startup.rvb*). It is not until you run a command alias is the script actually loaded. If you re-run the command alias, the script is not reloaded, but just run (since it is already loaded). Since scripts are only loaded as needed, you are not loading up a bunch of scripts that you will never use, thus conserving memory and allowing Rhino to load faster. ```vbnet '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' Startup.rvb -- September 2010 ' If this code works, it was written by Dale Fugier. ' If not, I don't know who wrote it. ' Works with Rhino 4.0 and 5.0. '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Option Explicit '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' Description: ' Demand loads and runs a script. ' Parameters: ' subroutine [in] - The name of the script subroutine to run. ' filename [in] - The name of the file were the subroutine ' is located. '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Sub DemandRun(ByVal subroutine, ByVal filename) Dim macro macro = "_-LoadScript " & QuoteString(filename) If Not Rhino.IsProcedure(subroutine) Then Call Rhino.Command(macro, 0) End If Call Execute(subroutine) End Sub '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' Description: ' Registers command aliases that run macros that utilize the ' DemandRun subroutine defined above. ' Parameters: ' alias [in] - The name of the command alias to register. ' subroutine [in] - The name of the script subroutine the the ' alias will run. ' filename [in] - The name of the file were the subroutine ' is located. '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Sub DemandRegister(ByVal alias, ByVal subroutine, ByVal filename) Dim macro, qsub, qfile qsub = QuoteString(subroutine) qfile = QuoteString(filename) macro = "_-NoEcho _-RunScript (DemandRun " & qsub & ", " & qfile & ")" Call Rhino.AddAlias(alias, macro) End Sub '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' Description: ' Surrounds a string with double-quote characters. ' Parameters: ' str [in] - The string to modify. ' Returns: ' The modified string '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Function QuoteString(ByVal str) QuoteString = Chr(34) & CStr(str) & Chr(34) End Function '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' TODO: Register your demand loading and running scripts here! ' In the following section below, register the aliases that you ' want to create and the scripts that you want them to run. '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Call DemandRegister("Hello", "Hello", "Hello.rvb") Call DemandRegister("BoxFrame", "BoxFrame", "C:\Users\Dale\Scripts\BoxFrame.rvb") ``` ## Related Topics - [Efficient Script Loading](/guides/rhinoscript/efficient-script-loading) -------------------------------------------------------------------------------- # Script FlowAlongSrf Source: https://developer.rhino3d.com/en/samples/rhinoscript/script-flowalongsrf/ Demonstrates how to script the FlowAlongSrf command using RhinoScript. ```vbnet Function DoFlowAlongSrf(arrObjects, strBase, strTarget) ' Declare local variables Dim saved, cmd ' For speed, turn of screen redrawing Rhino.EnableRedraw False ' Save any selected objects saved = Rhino.SelectedObjects ' Unselect all objects Rhino.UnSelectAllObjects ' Select the brep Rhino.SelectObjects arrObjects ' Script the split command cmd = "_FlowAlongSrf _SelID " & strBase & " _SelID " & strTarget Rhino.Command cmd, 0 ' By preselecting the brep, the results of ' Split will be selected. So, get the selected ' objects and return them to the caller. DoFlowAlongSrf = Rhino.SelectedObjects ' Unselect all objects Rhino.UnSelectAllObjects ' If any objects were selected before calling ' this function, re-select them If IsArray(saved) Then Rhino.SelectObjects(saved) ' Don't forget to turn redrawing back on Rhino.EnableRedraw True End Function ``` -------------------------------------------------------------------------------- # Script the Split Command Source: https://developer.rhino3d.com/en/samples/rhinoscript/script-the-split-command/ Demonstrates how to script the Split command using RhinoScript. ```vbnet Function DoBrepSplit(brep, cutter) ' Declare local variables Dim saved, cmd ' Set default return value DoBrepSplit = Null ' For speed, turn of screen redrawing Rhino.EnableRedraw False ' Save any selected objects saved = Rhino.SelectedObjects ' Unselect all objects Rhino.UnSelectAllObjects ' Select the brep Rhino.SelectObject brep ' Script the split command cmd = "_Split _SelID " & cutter & " _Enter" Rhino.Command cmd, 0 ' By preselecting the brep, the results of ' Split will be selected. So, get the selected ' objects and return them to the caller. DoBrepSplit = Rhino.SelectedObjects ' Unselect all objects Rhino.UnSelectAllObjects ' If any objects were selected before calling ' this function, re-select them If IsArray(saved) Then Rhino.SelectObjects(saved) ' Don't forget to turn redrawing back on Rhino.EnableRedraw True End Function ``` -------------------------------------------------------------------------------- # Select by Linetype Source: https://developer.rhino3d.com/en/samples/rhinoscript/select-by-linetype/ Demonstrates how to select objects by linetype using RhinoScript. ```vbnet Sub SelLinetype Dim strLinetype strLineType = Rhino.GetString("Object linetype name to select") If Not IsString(strLinetype) Then Exit Sub If Not Rhino.IsLinetype(strLinetype) Then Exit Sub Dim arrObjects, strObject ' If you want to filter on all visible objects, ' use the following line of code. arrObjects = Rhino.NormalObjects ' Or, if you want to filter on just curve objects ' use the following line of code. ' arrObjects = Rhino.ObjectsByType(4) If IsArray(arrObjects) Then Rhino.EnableRedraw False For Each strObject In arrObjects If StrComp(Rhino.ObjectLinetype(x), strLinetype, 1) = 0 Then Rhino.SelectObject(strObject) End If Next Rhino.EnableRedraw True End If End Sub Function IsString(ByVal str) IsString = False If VarType(str) = vbString Then IsString = True End Function ``` -------------------------------------------------------------------------------- # Select Curves by Degree Source: https://developer.rhino3d.com/en/samples/rhinoscript/select-curves-by-degree/ Demonstrates how to use RhinoScript to select all curves that are of a specified degree. ```vbnet ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' SelCrvDegree.rvb -- September 2011 ' If this code works, it was written by Dale Fugier. ' If not, I don't know who wrote it. ' Works with Rhino 4.0. ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Option Explicit ' Declare global variable Public g__nDegree ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' Selects curves of a user-specified degree ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Sub SelCrvDegree ' Declare local constants Const RH_CURVE = 4 Const RH_MAX_DEGREE = 11 Const RH_DEF_DEGREE = 3 ' Declare local variables Dim nDegree, arrCurves, strCurve, nCount ' Make sure global variable is initialized If IsEmpty(g__nDegree) Or IsNull(g__nDegree) Then g__nDegree = RH_DEF_DEGREE nDegree = g__nDegree ' Get all curve objects Call Rhino.EnableRedraw(False) arrCurves = Rhino.ObjectsByType(RH_CURVE) If IsNull(arrCurves) Then Call Rhino.Print("No curves to select.") Exit Sub End If ' Prompt for curve degree nDegree = Rhino.GetInteger("Degree of curves to select", nDegree, 1, RH_MAX_DEGREE) If IsNull(nDegree) Then Exit Sub ' Select curves of specified degree Call Rhino.EnableRedraw(False) nCount = 0 For Each strCurve In arrCurves If nDegree = Rhino.CurveDegree(strCurve) Then If Not Rhino.IsObjectSelected(strCurve) Then Call Rhino.SelectObject(strCurve) nCount = nCount + 1 End If End If Next Call Rhino.EnableRedraw(True) ' Print results If 0 = nCount Then Call Rhino.Print("No degree=" & CStr(nDegree) & " curves added to selection.") ElseIf 1 = nCount Then Call Rhino.Print("1 degree=" & CStr(nDegree) & " curve added to selection.") Else Call Rhino.Print(CStr(nCount) & " degree=" & CStr(nDegree) & " curves added to selection.") End If ' Remember curve degree g__nDegree = nDegree End Sub ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' Drag & drop support ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Rhino.AddStartUpScript Rhino.LastLoadedScriptFile Rhino.AddAlias "SelCrvDegree", "_-RunScript (SelCrvDegree)" ``` -------------------------------------------------------------------------------- # Select Dimensions by Style Source: https://developer.rhino3d.com/en/samples/rhinoscript/select-dimension-by-style/ Demonstrates how to select an objects (dimensions) by their dimension style using RhinoScript. ```vbnet Sub SelDimStyle ' Declare local constants and variables Const rhAnnotation = 512 Dim strStyle, arrObjects, strObject ' Prompt the user to enter the name of the dimension style strStyle = Rhino.GetString("Dimension style to select", Rhino.CurrentDimStyle) If IsNull(strStyle) Then Exit Sub If Not Rhino.IsDimStyle(strStyle) Then Exit Sub ' Create an array of all annotation objects arrObjects = Rhino.ObjectsByType(rhAnnotation) If Not IsArray(arrObjects) Then Exit Sub Rhino.EnableRedraw False ' Process each annotation object For Each strObject In arrObjects If Rhino.IsDimension(strObject) Then If StrComp(Rhino.DimensionStyle(strObject), strStyle, 1) = 0 Then Rhino.SelectObject strObject End If End If Next Rhino.EnableRedraw True End Sub ``` This one allows selection of a dimension style from a list... ```vbnet Sub SelDimStyleList ' Declare local constants and variables Const rhAnnotation = 512 Dim strStyle, arrObjects, strObject, arrStyles 'get the existing styles arrStyles = Rhino.DimStyleNames () ' Prompt the user to select the name of the dimension style strStyle = Rhino.ListBox(arrStyles, "Dimension style to select", "Dimension styles") If IsNull(strStyle) Then Exit Sub If Not Rhino.IsDimStyle(strStyle) Then Exit Sub ' Create an array of all annotation objects arrObjects = Rhino.ObjectsByType(rhAnnotation) If Not IsArray(arrObjects) Then Exit Sub Rhino.EnableRedraw False ' Process each annotation object For Each strObject In arrObjects If Rhino.IsDimension(strObject) Then If StrComp(Rhino.DimensionStyle(strObject), strStyle, 1) = 0 Then Rhino.SelectObject strObject End If End If Next Rhino.EnableRedraw True End Sub ``` This one selects dimensions matching the styles of selected dimensions... ```vbnet Sub SelDimStyleMatch ' Declare local constants and variables Const rhAnnotation = 512 Dim sStyle, aDims, sDim, strObject, aStyles(),aStyles1, aSel, sSel, i ' Create an array of all annotation objects aDims = Rhino.ObjectsByType(rhAnnotation) If Not IsArray(aDims) Then Exit Sub 'Get the selected dimensions to match aSel = Rhino.GetObjects("Select dimensions",rhAnnotation,True,True,True,aDims) 'create an array of the selcted dimension styles If isarray(aSel) Then For Each sSel In aSel ReDim Preserve aStyles(i) aStyles(i) = Rhino.DimensionStyle(sSel) i = i + 1 Next Else Exit Sub End If 'cull duplicate styles from the array aStyles1 = Rhino.CullDuplicateStrings(aStyles) Rhino.EnableRedraw False ' Process each annotation object with each selected dim stlye For Each sDim In aDims For Each sStyle In aStyles1 If StrComp(Rhino.DimensionStyle(sDim), sStyle, 1) = 0 Then Rhino.SelectObject sDim End If Next Next Rhino.EnableRedraw True End Sub ``` -------------------------------------------------------------------------------- # Select Multiple Files Source: https://developer.rhino3d.com/en/samples/rhinoscript/select-multiple-files/ Demonstrates how to use RhinoScript's MultiListBox function to select multiple files. ```vbnet Sub Test Dim sFolder sFolder = Rhino.BrowseForFolder( , "Select folder with 3DM files" ) If VarType( sFolder ) <> vbString Then Exit Sub Dim oFSO Set oFSO = CreateObject( "Scripting.FileSystemObject" ) Dim oFolder Set oFolder = oFSO.GetFolder( sFolder ) Dim oFile, aFiles(), nCount nCount = 0 For Each oFile In oFolder.Files ReDim Preserve aFiles(nCount) aFiles(nCount) = oFile.Name nCount = nCount + 1 Next If nCount = 0 Then Rhino.Print "Selected folder contained no 3DM files." Exit Sub End If Dim aSelected, sSelected aSelected = Rhino.MultiListBox(aFiles, oFolder.Path) If IsArray(aSelected) Then For Each sSelected In aSelected Rhino.Print sSelected Next End If End Sub ``` -------------------------------------------------------------------------------- # Select Named Objects from a List Source: https://developer.rhino3d.com/en/samples/rhinoscript/select-named-objects-from-a-list/ Demonstrates how to use a dialog box to select named objects using RhinoScript. ```vbnet Option Explicit Sub SelNamedObjects ' Get all the objects in the document Dim arrAll arrAll = Rhino.AllObjects If Not IsArray(arrAll) Then Rhino.Print "No objects added to selection." Exit Sub End If ' Find all of the object names Dim arrNames(), strName, nBound, i nBound = 0 For i = 0 To UBound(arrAll) strName = Rhino.ObjectName(arrAll(i)) If Not IsNull(strName) Then ReDim Preserve arrNames(nBound) arrNames(nBound) = strName nBound = nBound + 1 End If Next ' Exit if no names found If nBound = 0 Then Rhino.Print "No objects added to selection." Exit Sub End If ' Cull the duplicate names Dim arrCulled arrCulled = Rhino.CullDuplicateStrings(arrNames) ' Sort the list alphabetically Dim arrSorted arrSorted = Rhino.Sort(arrCulled) 'Error =>SortStrings ' Select one or more objects names from a list Dim arrSelected arrSelected = Rhino.MultiListBox(arrSorted, "Object names to select", "Select Named Objects") If Not IsArray(arrSelected) Then Exit Sub ' Select, and count, the objects Dim arrObjs, nCount nCount = 0 For i = 0 To UBound(arrSelected) arrObjs = Rhino.ObjectsByName(arrSelected(i), True) If IsArray(arrObjs) Then nCount = nCount + UBound(arrObjs) + 1 End If Next 'Print report to commandline If nCount = 0 Then Rhino.Print "No objects added to selection." ElseIf nCount = 1 Then Rhino.Print "1 object added to selection." Else Rhino.Print CStr(nCount) & " objects added to selection." End If End Sub ``` -------------------------------------------------------------------------------- # Select Objects by Layer Source: https://developer.rhino3d.com/en/samples/cpp/select-objects-by-layer/ Demonstrates how to select all of the objects on a specified layer. ```cpp CRhinoCommand::result CCommandTest::RunCommand( const CRhinoCommandContext& context ) { // Get a reference to the layer table CRhinoLayerTable& layer_table = context.m_doc.m_layer_table; // Get the current layer const CRhinoLayer& current_layer = layer_table.CurrentLayer(); // Prompt for a layer name CRhinoGetString gs; gs.SetCommandPrompt( L"Name of layer to select object" ); gs.SetDefaultString( current_layer.LayerName() ); gs.AcceptNothing( TRUE ); gs.GetString(); if( gs.CommandResult() != CRhinoCommand::success ) return gs.CommandResult(); // Validate the string ON_wString layer_name = gs.String(); layer_name.TrimLeftAndRight(); if( layer_name.IsEmpty() ) return CRhinoCommand::cancel; // Find the layer int layer_index = layer_table.FindLayer( layer_name ); if( layer_index < 0 ) { RhinoApp().Print( L"\"%s\" does not exists.\n", layer_name ); return CRhinoCommand::cancel; } // Get the layer const CRhinoLayer& layer = layer_table[layer_index]; // Get all of the objects on the layer ON_SimpleArray obj_list; int i, obj_count = context.m_doc.LookupObject( layer, obj_list ); // Select all of the layer objects for( i = 0; i < obj_count; i++ ) { CRhinoObject* obj = obj_list[i]; if( obj && obj->IsSelectable() ) obj->Select(); } if( obj_count ) context.m_doc.Redraw(); return CRhinoCommand::success; } ``` -------------------------------------------------------------------------------- # Select Objects in Object Groups Source: https://developer.rhino3d.com/en/samples/rhinocommon/select-objects-in-object-groups/ Demonstrates how to select objects that are an object group. ```cs partial class Examples { public static Result SelectObjectsInObjectGroups(RhinoDoc doc) { ObjRef obj_ref; var rs = RhinoGet.GetOneObject( "Select object", false, ObjectType.AnyObject, out obj_ref); if (rs != Result.Success) return rs; var rhino_object = obj_ref.Object(); if (rhino_object == null) return Result.Failure; var rhino_object_groups = rhino_object.Attributes.GetGroupList().DefaultIfEmpty(-1); var selectable_objects= from obj in doc.Objects.GetObjectList(ObjectType.AnyObject) where obj.IsSelectable(true, false, false, false) select obj; foreach (var selectable_object in selectable_objects) { foreach (var group in selectable_object.Attributes.GetGroupList()) { if (rhino_object_groups.Contains(group)) { selectable_object.Select(true); continue; } } } doc.Views.Redraw(); return Result.Success; } } ``` ```python from Rhino import * from Rhino.Commands import * from Rhino.DocObjects import * from Rhino.Input import * from scriptcontext import doc def RunCommand(): rs, obj_ref = RhinoGet.GetOneObject("Select object", False, ObjectType.AnyObject) if rs != Result.Success: return rs rhino_object = obj_ref.Object() if rhino_object == None: return Result.Failure rhino_object_groups = [group for group in rhino_object.Attributes.GetGroupList()] selectable_objects= [ obj for obj in doc.Objects.GetObjectList(ObjectType.AnyObject) if obj.IsSelectable(True, False, False, False)] for selectable_object in selectable_objects: for group in selectable_object.Attributes.GetGroupList(): if rhino_object_groups.Contains(group): selectable_object.Select(True) continue doc.Views.Redraw() return Result.Success if __name__ == "__main__": RunCommand() ``` -------------------------------------------------------------------------------- # Select Objects on Layer Source: https://developer.rhino3d.com/en/samples/rhinocommon/select-objects-on-layer/ Demonstrates how to select all the objects on a user-specified layer. ```cs partial class Examples { public static Rhino.Commands.Result SelLayer(Rhino.RhinoDoc doc) { // Prompt for a layer name string layername = doc.Layers.CurrentLayer.Name; Result rc = Rhino.Input.RhinoGet.GetString("Name of layer to select objects", true, ref layername); if (rc != Rhino.Commands.Result.Success) return rc; // Get all of the objects on the layer. If layername is bogus, you will // just get an empty list back Rhino.DocObjects.RhinoObject[] rhobjs = doc.Objects.FindByLayer(layername); if (rhobjs == null || rhobjs.Length < 1) return Rhino.Commands.Result.Cancel; for (int i = 0; i < rhobjs.Length; i++) rhobjs[i].Select(true); doc.Views.Redraw(); return Rhino.Commands.Result.Success; } } ``` ```python import Rhino import scriptcontext import System.Guid, System.Drawing.Color def SelLayer(): # Prompt for a layer name layername = scriptcontext.doc.Layers.CurrentLayer.Name rc, layername = Rhino.Input.RhinoGet.GetString("Name of layer to select objects", True, layername) if rc!=Rhino.Commands.Result.Success: return rc # Get all of the objects on the layer. If layername is bogus, you will # just get an empty list back rhobjs = scriptcontext.doc.Objects.FindByLayer(layername) if not rhobjs: Rhino.Commands.Result.Cancel for obj in rhobjs: obj.Select(True) scriptcontext.doc.Views.Redraw() return Rhino.Commands.Result.Success if __name__=="__main__": SelLayer() ``` -------------------------------------------------------------------------------- # Select Planar Meshes Source: https://developer.rhino3d.com/en/samples/rhinoscript/select-planar-meshes/ Demonstrates how to select mesh objects that are planar using RhinoScript. ```vbnet Sub SelPlanarMeshes Const rhMesh = 32 Dim arrMeshes, arrVertices, i arrMeshes = Rhino.ObjectsByType(rhMesh) If IsArray(arrMeshes) Then Rhino.EnableRedraw vbFalse For i = 0 To UBound(arrMeshes) arrVertices = Rhino.MeshVertices(arrMeshes(i)) If Rhino.PointsAreCoplanar(arrVertices) Then Rhino.SelectObject arrMeshes(i) End If Next Rhino.EnableRedraw vbTrue End If End Sub ``` -------------------------------------------------------------------------------- # Select Points by Z Coordinate Source: https://developer.rhino3d.com/en/samples/rhinoscript/select-points-by-z-coordinate/ Demonstrates how to select point objects with a user-specified z coordinate using RhinoScript. ```vbnet Option Explicit Sub SelZ() Dim arr arr = Rhino.ObjectsByType(1) If Not IsArray(arr) Then Rhino.Print "No point objects to select" Exit Sub End If Const zero_tol = 1.0e-12 Dim z, obj, pt z = Rhino.GetReal("Z coordinate", 0.0) If IsNumeric(z) Then For Each obj In arr pt = Rhino.PointCoordinates(obj) If IsArray(pt) Then If Abs(pt(2)-z) <= zero_tol Then Rhino.SelectObject obj End If End If Next End If End Sub ``` -------------------------------------------------------------------------------- # Select Text by Height Source: https://developer.rhino3d.com/en/samples/rhinoscript/select-text-by-height/ Demonstrates how to select text objects by their text height using RhinoScript. ```vbnet ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' SelTextHeight.rvb -- July 2009 ' If this code works, it was written by Dale Fugier. ' If not, I don't know who wrote it. ' Works with Rhino 4.0 and 5.0 ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Option Explicit ' Global variables Public g_min_height Public g_max_height ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' Selects text objects based on their text height ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Sub SelTextHeight() ' Local variables Dim min_height, max_height Dim objects, obj, height, sel_count ' Initialize global variables, if necessary If IsEmpty(g_min_height) Or IsNull(g_min_height) Then g_min_height = 1.0 If IsEmpty(g_max_height) Or IsNull(g_max_height) Then g_max_height = 1.0 ' Prompt for minimum height min_height = Rhino.GetReal("Minimum text height", g_min_height, 0.0) If IsNull(min_height) Then Exit Sub ' Prompt for maximum height max_height = Rhino.GetReal("Maximum text height", g_max_height, min_height) If IsNull(min_height) Then Exit Sub If (min_height > max_height) Then Exit Sub ' More initialization g_min_height = min_height g_max_height = max_height sel_count = 0 Rhino.EnableRedraw False ' Find text objects that meet our criteria objects = Rhino.ObjectsByType(512) 'annotations For Each obj In objects If Rhino.IsText(obj) Then height = Rhino.TextObjectHeight(obj) If (g_min_height <= height) And (height <= g_max_height) Then If Rhino.SelectObject(obj) Then sel_count = sel_count + 1 End If End If Next Rhino.EnableRedraw True Rhino.Print CStr(sel_count) & " text added to selection." End Sub ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Rhino.AddStartupScript Rhino.LastLoadedScriptFile Rhino.AddAlias "SelTextHeight", "_NoEcho _-RunScript (SelTextHeight)" ``` -------------------------------------------------------------------------------- # Select Text Objects Source: https://developer.rhino3d.com/en/samples/rhinoscript/select-text-objects/ Demonstrates how to use RhinoScript to select all text objects. ```vbnet Sub SelText Dim arrObjects, strObject arrObjects = Rhino.AllObjects If IsArray(arrObjects) Then For Each strObject In arrObjects If Rhino.IsText(strObject) Then Rhino.SelectObject strObject End If Next End If End Sub ``` -------------------------------------------------------------------------------- # Selecting Curves by Type Source: https://developer.rhino3d.com/en/guides/rhinoscript/selecting-curves-by-type/ This brief guide demonstrates how to select linear and non-linear curves using RhinoScript. ## Non-Linear Curves The following RhinoScript subroutine will select all non-linear curves in the document: ```vbnet Sub SelNonLinearCrv() Dim arrCurves, strCurve arrCurves = Rhino.ObjectsByType(4) If IsArray(arrCurves) Then Rhino.EnableRedraw False For Each strCurve In arrCurves If Not Rhino.IsCurveLinear(strCurve) Then Rhino.SelectObject strCurve End If Next Rhino.EnableRedraw True End If End Sub ``` ## Linear Curves The following RhinoScript subroutine will select all linear curves in the document: ```vbnet Sub SelLinearCrv() Dim arrCurves, strCurve arrCurves = Rhino.ObjectsByType(4) If IsArray(arrCurves) Then Rhino.EnableRedraw False For Each strCurve In arrCurves If Rhino.IsCurveL inear(strCurve) Then Rhino.SelectObject strCurve End If Next Rhino.EnableRedraw True End If End Sub ``` -------------------------------------------------------------------------------- # Selecting Objects Source: https://developer.rhino3d.com/en/guides/cpp/selecting-objects/ This guide demonstrates interactively selecting objects using C/C++. ## Overview The Rhino C/C++ SDK provides the class `CRhinoGetObject` that will let you interactively select objects on the screen. This is a large class with many options. We recommend that you read through the header file, *rhinoSdkGetObject.h*, before using. To use the `CRhinoGetObject` class, put one on the stack and call its `GetObjects()` member. It is possible to derive custom classes from `CRhinoGetObject`. But, the class is powerful enough that deriving new classes from it is usually unnecessary. If an instance of `CRhinoGetObject` was successful in selecting one or more objects, then use its `Object()` member to retrieve information about the selected object. The `Object()` member returns a `CRhinoObjRef` class which has several member functions to help you quickly get to the information you are looking for. ## Samples This sample selects a single object... ```cpp CRhinoGetObject go; go.SetCommandPrompt( L"Select object" ); CRhinoGet::result res = go.GetObjects( 1, 1 ); if( res == CRhinoGet::object ) { const CRhinoObjRef& obj_ref = go.Object( 0 ); const CRhinoObject* obj = obj_ref.Object(); if( obj ) { // TODO } } ``` This sample selects one or more objects... ```cpp CRhinoGetObject go; go.SetCommandPrompt( L"Select objects" ); CRhinoGet::result res = go.GetObjects( 1, 0 ); if( res == CRhinoGet::object ) { int i, count = go.ObjectCount(); for( i = 0; i < count; i++ ) { const CRhinoObjRef& obj_ref = go.Object( 0 ); const CRhinoObject* obj = obj_ref.Object(); if( obj ) { // TODO } } } ``` This sample selects a single curve object... ```cpp CRhinoGetObject go; go.SetCommandPrompt( L"Select curve" ); go.SetGeometryFilter( CRhinoGetObject::curve_object ); CRhinoGet::result res = go.GetObjects( 1, 1 ); if( res == CRhinoGet::object ) { const CRhinoObjRef& obj_ref = go.Object( 0 ); const ON_Curve* crv = obj_ref.Curve(); if( crv ) { // TODO } } ``` -------------------------------------------------------------------------------- # Set a CPlane to a View Source: https://developer.rhino3d.com/en/samples/cpp/set-cplane-to-view/ Demonstrates how to set the construction plane in the active viewport parallel to the view. ```cpp CRhinoCommand::result CCommandTest::RunCommand( const CRhinoCommandContext& context ) { CRhinoCommand::result rc = CRhinoCommand::cancel; // Get the active view object CRhinoView* view = ::RhinoApp().ActiveView(); if( view ) { // Get reference to the view's viewport object CRhinoViewport& vp = view->Viewport(); // Create plane object based on viewport parameters ON_Plane plane( vp.Target(), vp.VP().CameraX(), vp.VP().CameraY() ); // Copy viewport's cplane object ON_3dmConstructionPlane cplane = vp.ConstructionPlane(); // Set the cplane's plane object cplane.m_plane = plane; // Push the new cplane onto the cplane stack view->Viewport().PushConstructionPlane( cplane ); // Redraw the view view->Redraw(); rc = CRhinoCommand::success; } return rc; } ``` -------------------------------------------------------------------------------- # Set a View's Construction Plane Source: https://developer.rhino3d.com/en/samples/cpp/set-views-construction-plane/ Demonstrates how to set a view's construction plane. ```cpp CRhinoCommand::result CCommandTest::RunCommand( const CRhinoCommandContext& context ) { CRhinoView* view = ::RhinoApp().ActiveView(); if( !view ) return CRhinoCommand::failure; CRhinoGetOption go; go.SetCommandPrompt( L"Select a construction plane" ); int back_option = go.AddCommandOption( RHCMDOPTNAME(L"Back") ); int bottom_option = go.AddCommandOption( RHCMDOPTNAME(L"Bottom") ); int front_option = go.AddCommandOption( RHCMDOPTNAME(L"Front") ); int left_option = go.AddCommandOption( RHCMDOPTNAME(L"Left") ); int right_option = go.AddCommandOption( RHCMDOPTNAME(L"Right") ); int top_option = go.AddCommandOption( RHCMDOPTNAME(L"Top") ); go.GetOption(); if( go.CommandResult() != CRhinoCommand::success ) return go.CommandResult(); const CRhinoCommandOption* opt = go.Option(); if( !opt ) return CRhinoCommand::failure; int option_index = opt->m_option_index; ON_3dmConstructionPlane cplane = view->Viewport().ConstructionPlane(); if( option_index == back_option ) cplane.m_plane.CreateFromPoints( ON_origin, -ON_xaxis ,ON_zaxis ); else if( option_index == bottom_option ) cplane.m_plane.CreateFromPoints( ON_origin, -ON_xaxis, ON_yaxis ); else if( option_index == front_option ) cplane.m_plane.CreateFromPoints( ON_origin, ON_xaxis, ON_zaxis ); else if( option_index == left_option ) cplane.m_plane.CreateFromPoints( ON_origin, -ON_yaxis, ON_zaxis ); else if( option_index == right_option ) cplane.m_plane.CreateFromPoints( ON_origin, ON_yaxis, ON_zaxis ); else if( option_index == top_option ) cplane.m_plane.CreateFromPoints( ON_origin, ON_xaxis, ON_yaxis ); else return CRhinoCommand::failure; view->Viewport().PushConstructionPlane( cplane ); view->Redraw(); return CRhinoCommand::success; } ``` -------------------------------------------------------------------------------- # Set Camera Angle Source: https://developer.rhino3d.com/en/samples/rhinoscript/set-camera-angle/ Demonstrates how to set the camera angle using RhinoScript. ```vbnet Sub TestSetCameraAngle() ' Local constants (could easily prompt for these...) Const horz_angle = 7.3 Const vert_angle = 5.5 Const horz_res = 1200 ' Local variables Dim view, vert_res ' Current view must be a perspective view view = Rhino.CurrentView() If Not Rhino.IsViewPerspective(view) Then Call Rhino.Print("Select a perspective view and rerun the script.") Exit Sub End If ' Calculate vertical resolution based on existing parameters vert_res = CInt(Round((vert_angle * 2) / (horz_angle * 2) * horz_res)) ' Set the viewport size Call Rhino.Command("_-ViewportProperties _Size " & CStr(horz_res) & " " & CStr(vert_res) & " _Enter _Enter", 0) ' Set the viewing angle If (horz_res < vert_res) Then Call Rhino.Command("_-PerspectiveAngle " & CStr(horz_angle), 0) Else Call Rhino.Command("_-PerspectiveAngle " & CStr(vert_angle), 0) End If End Sub ``` -------------------------------------------------------------------------------- # Set Command Prompt Source: https://developer.rhino3d.com/en/samples/cpp/set-command-prompt/ Demonstrates how to set Rhino's command prompt text to show the progress of long processes. ```cpp CRhinoCommand::result CCommandTest::RunCommand( const CRhinoCommandContext& context ) { ON_wString prompt; int i, entity_count = 1000; for( i = 0; i < entity_count; i++ ) { prompt.Format( L"Importing IGES entity %d of %d", i+1, entity_count ); RhinoApp().SetCommandPrompt( prompt ); RhinoApp().Wait(0); // TODO: } RhinoApp().Print( L"IGES import successful.\n" ); return success; } ``` -------------------------------------------------------------------------------- # Set Hierarchical Layer Names Source: https://developer.rhino3d.com/en/samples/rhinoscript/set-hierarchical-layer-names/ Demonstrates how to rename layers in a hierarchical manner using RhinoScript. ```vbnet Option Explicit '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' SetHierarchicalLayerNames '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Sub SetHierarchicalLayerNames() ' String that separates layer names Const separator = "-" ' Get all of the layer names. Dim all_layer all_layer = Rhino.LayerNames ' If only one layer, just bail. If UBound(all_layer) = 0 Then Exit Sub ' Build an array of layers who have no parent ' and that are from a reference file. Dim root_layers(), layer_count layer_count = 0 Dim layer_name, parent_layer For Each layer_name In all_layer parent_layer = Rhino.ParentLayer(layer_name) If IsNull(parent_layer) And Rhino.IsLayerReference(layer_name) = vbFalse Then ReDim Preserve root_layers(layer_count) root_layers(layer_count) = layer_name layer_count = layer_count + 1 End If Next ' If the lists are the same size, then there are not ' child layers. So, just bail. If UBound(all_layer) = UBound(root_layers) Then Exit Sub ' Process the list of parentless layers ProcessLayerList root_layers, separator End Sub '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' ProcessLayerList '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Sub ProcessLayerList(layer_list, separator) ' Process each layer in the array. Note, ' this is a recursive function. Dim layer_name, layer_children For Each layer_name In layer_list ProcessLayer layer_name, separator Next End Sub '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' ProcessLayer '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Sub ProcessLayer(layer_name, separator) ' Get the layer's parent Dim parent_layer, renamed_layer, layer_children parent_layer = Rhino.ParentLayer(layer_name) ' If the layer has a parent, then modify its name ' to include its parent name. If IsNull(parent_layer) Then renamed_layer = layer_name Else renamed_layer = parent_layer & separator & layer_name Rhino.RenameLayer layer_name, renamed_layer End If ' Get the layer's immediate children layer_children = Rhino.LayerChildren(renamed_layer) If IsArray(layer_children) Then ' Process these layers too ProcessLayerList layer_children, separator End If End Sub ``` -------------------------------------------------------------------------------- # Set Length of Curve Source: https://developer.rhino3d.com/en/samples/rhinoscript/set-length-of-curve/ Demonstrates how to set the length of a curve object using RhinoScript. ```vbnet Option Explicit Sub SetCrvLength Dim crv, lold, lnew, dom, pts, t crv = Rhino.GetCurveObject("Select curve to set length", 4, True) If Not IsArray(crv) Then Exit Sub If Rhino.IsCurveClosed(crv(0)) Then Rhino.Print "Cannot set the length of closed curves." Exit Sub End If lold = Rhino.CurveLength(crv(0)) lnew = Rhino.GetReal("New curve length", lold, 0.0, lold) If Not IsNumeric(lnew) Then Exit Sub If (lnew <= 0) Or (lnew >= lold) Then Exit Sub dom = Rhino.CurveDomain(crv(0)) If (dom(1)-crv(4)) < (crv(4)-dom(0)) Then Rhino.ReverseCurve crv(0) dom = Rhino.CurveDomain(crv(0)) End If pts = Rhino.DivideCurveLength(crv(0), lnew) t = Rhino.CurveClosestPoint(crv(0), pts(1)) Rhino.TrimCurve crv(0), Array(dom(0), t), True End Sub ``` -------------------------------------------------------------------------------- # Set Material Colors from Object Colors Source: https://developer.rhino3d.com/en/samples/rhinoscript/set-material-colors-from-object-colors/ Demonstrates how to modify an object's material color to match its display color using RhinoScript. ```vbnet Option Explicit Sub SetMaterialColorsFromObjectColors ' Constants Const rhColorByLayer = 0 Const rhColorByObject = 1 ' Variables Dim aObjects, sObject Dim nColor, nSource Dim sLayer, nMaterial ' Get all objects in the document aObjects = Rhino.AllObjects If Not IsArray(aObjects) Then Exit Sub ' Process each object For Each sObject In aObjects ' Get the object's color and color source nColor = Rhino.ObjectColor(sObject) nSource = Rhino.ObjectColorSource(sObject) nMaterial = -1 ' If the object's color source is "by layer" ' then get the layer's material index. If the ' layer does not have a material, add one. If (nSource = rhColorByLayer) Then sLayer = Rhino.ObjectLayer(sObject) nMaterial = Rhino.LayerMaterialIndex(sLayer) If( nMaterial < 0 ) Then nMaterial = Rhino.AddMaterialToLayer(sLayer) End If ' If the object's color source is "by object" ' then get the object's material index. If the ' object does not have a material, add one. ElseIf (nSource = rhColorByObject) Then nMaterial = Rhino.ObjectMaterialIndex(sObject) If( nMaterial < 0 ) Then nMaterial = Rhino.AddMaterialToObject(sObject) End If End If ' Set the material color If (nMaterial >= 0) Then If (nColor <> Rhino.MaterialColor(nMaterial)) Then Rhino.MaterialColor nMaterial, nColor End If End If Next ' Redraw the document Rhino.Redraw End Sub ``` -------------------------------------------------------------------------------- # Set RhinoPageView Width and Height Source: https://developer.rhino3d.com/en/samples/rhinocommon/set-rhinopageview-width-and-height/ Demonstrates how to set the RhinoPageView width and height dimensions. ```cs partial class Examples { public static Result SetRhinoPageViewWidthAndHeight(RhinoDoc doc) { var width = 1189; var height = 841; var page_views = doc.Views.GetPageViews(); int page_number = (page_views==null) ? 1 : page_views.Length + 1; var pageview = doc.Views.AddPageView(string.Format("A0_{0}",page_number), width, height); int new_width = width; var rc = RhinoGet.GetInteger("new width", false, ref new_width); if (rc != Result.Success || new_width <= 0) return rc; int new_height = height; rc = RhinoGet.GetInteger("new height", false, ref new_height); if (rc != Result.Success || new_height <= 0) return rc; pageview.PageWidth = new_width; pageview.PageHeight = new_height; doc.Views.Redraw(); return Result.Success; } } ``` ```python # No Python sample available ``` -------------------------------------------------------------------------------- # Set Viewport Name Source: https://developer.rhino3d.com/en/samples/rhinocommon/set-viewport-name/ Demonstrates how to set a viewport's name or title. ```cs partial class Examples { public static Result SetViewName(RhinoDoc doc) { var view = doc.Views.ActiveView; if (view == null) return Result.Failure; view.MainViewport.Name = "Facade"; return Result.Success; } } ``` ```python from Rhino.Commands import * import rhinoscriptsyntax as rs from scriptcontext import doc def RunCommand(): view = doc.Views.ActiveView if view == None: return Result.Failure view.MainViewport.Name = "Facade" return Result.Success if __name__ == "__main__": RunCommand() ``` -------------------------------------------------------------------------------- # Setting Up a Cage Edit Source: https://developer.rhino3d.com/en/guides/cpp/setting-up-a-cage-edit/ This guide demonstrates how to setup a cage editing scenario using C/C++. ## Problem Imagine you have two objects: a surface and a line curve and you would like to setup cage editing, with the surface as the captive object and the line curve as the control object. ## Solution To allow the line curve to control the surface, you will need to create a `CRhinoMorphControl` object. A `CRhinoMorphControl` object has a `ON_MorphControl` object, as its data member, which contains the definition of the controlling object (in this case, line). Once you have properly defined the `ON_MorphControl` object and created the runtime `CRhinoMorphControl` object, you can use the RhinoCaptureObject API function to setup the "capture." The following example code demonstrates how you might write a command that allows a surface to be controlled by a line... ```cpp CRhinoCommand::result CCommandTest::RunCommand( const CRhinoCommandContext& context ) { ON_Workspace ws; CRhinoCommand::result rc = CRhinoCommand::success; // Get the captive object CRhinoGetObject go; go.SetCommandPrompt( L"Select captive surface or polysurface" ); go.SetGeometryFilter( CRhinoGetObject::surface_object | CRhinoGetObject::polysrf_object ); go.GetObjects( 1, 1 ); rc = go.CommandResult(); if( CRhinoCommand::success != rc ) return rc; const CRhinoObject* captive = go.Object(0).Object(); if( 0 == captive ) return CRhinoCommand::failure; // Define the control line ON_Line line; rc = RhinoGetLine( CArgsRhinoGetLine(), line ); if( CRhinoCommand::success != rc ) return rc; // Get the curve parameters int degree = 3; int cv_count = 4; for(;;) { CRhinoGetOption gl; gl.SetCommandPrompt( L"NURBS Parameters" ); gl.AcceptNothing(); int d_opt = gl.AddCommandOptionInteger( RHCMDOPTNAME(L"Degree"), °ree, L"Curve degree", 1.0, 100.0 ); int p_opt = gl.AddCommandOptionInteger( RHCMDOPTNAME(L"PointCount"), &cv_count, L"Number of control points", 2.0, 100.0 ); gl.GetOption(); rc = gl.CommandResult(); if( CRhinoCommand::success != rc ) return rc; if( CRhinoGet::nothing == gl.Result() ) break; if( cv_count <= degree ) { if( CRhinoGet::option != gl.Result() ) continue; const CRhinoCommandOption* opt = go.Option(); if( 0 == opt ) continue; if( d_opt == opt->m_option_index ) cv_count = degree + 1; else degree = cv_count - 1; } } // Set up morph control ON_MorphControl* control = new ON_MorphControl(); control->m_varient = 1; // 1= curve // Specify the source line curve control->m_nurbs_curve0.Create( 3, false, 2, 2 ); control->m_nurbs_curve0.MakeClampedUniformKnotVector(); control->m_nurbs_curve0.SetCV( 0, line.from ); control->m_nurbs_curve0.SetCV( 1, line.to ); // Specify the destination NURBS curve control->m_nurbs_curve.Create( 3, false, degree + 1, cv_count ); control->m_nurbs_curve.MakeClampedUniformKnotVector(); double* g = ws.GetDoubleMemory( control->m_nurbs_curve.m_cv_count ); control->m_nurbs_curve.GetGrevilleAbcissae( g ); ON_Interval d = control->m_nurbs_curve.Domain(); double s = 0.0; int i; for( i = 0; i < control->m_nurbs_curve.m_cv_count; i++ ) { s = d.NormalizedParameterAt( g[i] ); control->m_nurbs_curve.SetCV( i, line.PointAt(s) ); } // Make sure domains match s = line.Length(); if( s > ON_SQRT_EPSILON ) control->m_nurbs_curve0.SetDomain( 0.0, s ); d = control->m_nurbs_curve0.Domain(); control->m_nurbs_curve.SetDomain( d[0], d[1] ); // Create the morph control object CRhinoMorphControl* control_object = new CRhinoMorphControl(); control_object->SetControl( control ); context.m_doc.AddObject( control_object ); // Set up the capture RhinoCaptureObject( control_object, const_cast(captive) ); // Clean up display context.m_doc.UnselectAll(); // Turn on the control grips control_object->EnableGrips( true ); context.m_doc.Redraw( CRhinoView::mark_display_hint ); return rc; } ``` -------------------------------------------------------------------------------- # Setting Viewport Titles Source: https://developer.rhino3d.com/en/guides/cpp/setting-viewport-titles/ This brief guide demonstrates how to set the title of a viewport using C/C++. ## Problem You would like to change the name, or title, or a viewport using the the Rhino C/C++ SDK. For example, you would like to rename the "Front" viewport to say "Facade." ## Solution To change the title of a viewport, use `CRhinoViewport::SetName`. A Rhino view contains a "main viewport" that fills the entire view client window. To get a view's main viewport, you can call `CRhinoView::MainViewport`. For example: ```cpp CRhinoView* view = RhinoApp().ActiveView(); if (view) view->MainViewport().SetName("Facade"); ``` -------------------------------------------------------------------------------- # Shading Individual Objects Source: https://developer.rhino3d.com/en/guides/cpp/shading-individual-objects/ This guide demonstrates how to shade individual objects using C/C++. ## Overview The drawing display pipeline technology provides both users and developers great flexibility and control over how objects are drawn on the screen. One of the features available is the ability to have objects draw using different display modes in the same viewport. For example, it is possible for a viewport to display both wireframe and shaded objects at the same time. To allow an object to draw in a display mode other than what the viewport is currently set to, all you need to do is to add an `ON_DisplayMaterialRef` object to an object's attributes. A `ON_DisplayMaterialRef` defines what viewport an object will draw using a different display attributes and what display attributes it will use. ## Sample The following sample code demonstrates how to shade individual objects using the Rhino C/C++ SDK. This shading is independent of the viewport's current display mode, or display attribute. Is is accomplished by adding a `ON_DisplayMaterialRef` object to an object's attributes. ```cpp CRhinoCommand::result CCommandTest::RunCommand( const CRhinoCommandContext& context ) { // Get a list of available display attributes DisplayAttrsMgrList attrs_list; const int attrs_count = CRhinoDisplayAttrsMgr::GetDisplayAttrsList( attrs_list ); if( attrs_count <= 0 ) return CRhinoCommand::nothing; ON_wString display_name( L"Shaded" ); ON_UUID display_material_id = ON_nil_uuid; // Find the "Shaded" display attribute int i; for( i = 0; i < attrs_count; i++ ) { CDisplayPipelineAttributes* pAttrs = attrs_list[i].m_pAttrs; if( !pAttrs ) continue; ON_wString english_name = pAttrs->EnglishName(); english_name.Remove( '_' ); english_name.Remove( ' ' ); english_name.Remove( '-' ); english_name.Remove( ',' ); english_name.Remove( '.' ); if( english_name.CompareNoCase(display_name) == 0 ) { display_material_id = pAttrs->Id(); break; } } // Bail if not found if( display_material_id == ON_nil_uuid ) { RhinoApp().Print( L" \"%s\" display mode not found.\n", display_name ); return CRhinoCommand::nothing; } // Select the objects to shade CRhinoGetObject go; go.SetCommandPrompt( L"Select surfaces, polysurfaces, and meshes to shade" ); go.SetGeometryFilter( ON::surface_object | ON::brep_object | ON::mesh_object ); go.GetObjects( 1, 0 ); if( go.CommandResult() != success ) return go.CommandResult(); // Get the viewport to shade the objects in CRhinoView* view = RhinoApp().ActiveView(); if( !view ) return failure; // Create a display material reference and assign // the display attributes id and the viewport id // to it. ON_DisplayMaterialRef dmr; dmr.m_display_material_id = display_material_id; dmr.m_viewport_id = view->Viewport().VP().ViewportId(); // Process each selected object const int object_count = go.ObjectCount(); for( i = 0; i < object_count; i++ ) { const CRhinoObjRef& ref = go.Object(i); const CRhinoObject* obj = ref.Object(); if( !obj ) continue; // Make a copy of the object's attributes ON_3dmObjectAttributes attributes = obj->Attributes(); // Add a display material reference attributes.AddDisplayMaterialRef( dmr ); // Modify the object's attributes context.m_doc.ModifyObjectAttributes( ref, attributes ); } context.m_doc.Redraw(); return CRhinoCommand::success; } ``` -------------------------------------------------------------------------------- # Shading Viewports Source: https://developer.rhino3d.com/en/samples/cpp/shading-viewports/ Demonstrates how to set a viewport to shaded display. ```cpp CRhinoCommand::result CCommandTest::RunCommand( const CRhinoCommandContext& context ) { CRhinoView* view = RhinoApp().ActiveView(); if( 0 == view ) return CRhinoCommand::failure; ON::display_mode dm = view->ActiveViewport().DisplayMode(); if( dm != ON::shaded_display ) { view->ActiveViewport().SetDisplayMode( ON::shaded_display ); context.m_doc.ViewModified( view ); view->Redraw(); } return CRhinoCommand::success; } ``` -------------------------------------------------------------------------------- # Shortest Line between two Lines Source: https://developer.rhino3d.com/en/guides/rhinoscript/shortest-line/ This brief guide demonstrates how to calculate the shortest line between two lines. ## Overview Two lines in three dimensions generally do not intersect at a point. They may be parallel (no intersections) or they may be coincident (infinite intersections) but most often only their projection onto a plane intersects. When they do not exactly intersect at a point they can be connected by a line segment, the shortest line segment is unique and is often considered to be their intersection in 3D. ## Example The following example code demonstrates how to calculate the shortest line between two line segments using RhinoScript... ```vbnet Option Explicit ' Description: ' Returns the shortest line segment between ' two infinite line segments. ' Parameters: ' p1 - the starting point of the first line ' p2 - the ending point of the first line ' p3 - the starting point of the second line ' p4 - the ending point of the second line ' Returns ' Array - the shortest line segment if successful ' Null - if not successful or on error Function LineLineIntersect(p1, p2, p3, p4) LineLineIntersect = Null Const EPS = 2.2204460492503131e-016 Dim p13(2), p43(2), p21(2) Dim d1343, d4321, d1321, d4343, d2121, numer, denom, mua, mub Dim pa(2), pb(2) p13(0) = p1(0) - p3(0) p13(1) = p1(1) - p3(1) p13(2) = p1(2) - p3(2) p43(0) = p4(0) - p3(0) p43(1) = p4(1) - p3(1) p43(2) = p4(2) - p3(2) If Abs(p43(0)) < EPS And Abs(p43(1)) < EPS And Abs(p43(2)) < EPS Then Exit Function p21(0) = p2(0) - p1(0) p21(1) = p2(1) - p1(1) p21(2) = p2(2) - p1(2) If Abs(p21(0)) < EPS And Abs(p21(1)) < EPS And Abs(p21(2)) < EPS Then Exit Function d1343 = p13(0) * p43(0) + p13(1) * p43(1) + p13(2) * p43(2) d4321 = p43(0) * p21(0) + p43(1) * p21(1) + p43(2) * p21(2) d1321 = p13(0) * p21(0) + p13(1) * p21(1) + p13(2) * p21(2) d4343 = p43(0) * p43(0) + p43(1) * p43(1) + p43(2) * p43(2) d2121 = p21(0) * p21(0) + p21(1) * p21(1) + p21(2) * p21(2) denom = d2121 * d4343 - d4321 * d4321 If Abs(denom) < EPS Then Exit Function numer = d1343 * d4321 - d1321 * d4343 mua = numer / denom mub = (d1343 + d4321 * mua) / d4343 pa(0) = p1(0) + mua * p21(0) pa(1) = p1(1) + mua * p21(1) pa(2) = p1(2) + mua * p21(2) pb(0) = p3(0) + mub * p43(0) pb(1) = p3(1) + mub * p43(1) pb(2) = p3(2) + mub * p43(2) LineLineIntersect = Array(pa, pb) End Function ``` You can test the above function as follows: ```vbnet Sub Test Dim l0 : l0 = Rhino.GetObject("Select first line", 4) Dim l1 : l1 = Rhino.GetObject("Select second line", 4) Dim p1 : p1 = Rhino.CurveStartPoint(l0) Dim p2 : p2 = Rhino.CurveEndPoint(l0) Dim p3 : p3 = Rhino.CurveStartPoint(l1) Dim p4 : p4 = Rhino.CurveEndPoint(l1) Dim rc : rc = LineLineIntersect(p1, p2, p3, p4) If IsArray(rc) Then Rhino.AddPoints rc End If End Sub ``` -------------------------------------------------------------------------------- # Show Hidden Objects Source: https://developer.rhino3d.com/en/samples/cpp/show-hidden-objects/ Demonstrates how to iterate through the geometry table and unhide hidden objects. ```cpp int ShowAllHiddenObjects( CRhinoDoc& doc, bool bRedraw ) { CRhinoObjectIterator it( doc, CRhinoObjectIterator::undeleted_objects, CRhinoObjectIterator::active_and_reference_objects ); it.IncludeLights(); int count = 0; CRhinoObject* obj = 0; for( obj = it.First(); obj; obj = it.Next() ) { // Ignore objects that are not hidden if( obj->Attributes().Mode() != ON::hidden_object ) continue; // Ignore objects on hidden or locked layers if( ON::normal_layer != obj->ObjectLayer().Mode() ) continue; if( doc.ShowObject(obj) ) count++; } if( count > 0 && bRedraw ) doc.Redraw(); return count; } ``` -------------------------------------------------------------------------------- # Show Surface Direction Source: https://developer.rhino3d.com/en/samples/rhinocommon/show-surface-direction/ Demonstrates how to show a surface's direction using a display conduit. ```cs partial class Examples { public static Rhino.Commands.Result ShowSurfaceDirection(Rhino.RhinoDoc doc) { Rhino.DocObjects.ObjRef objref; var rc = Rhino.Input.RhinoGet.GetOneObject("Select surface or polysurface for direction display", false, Rhino.DocObjects.ObjectType.Surface | Rhino.DocObjects.ObjectType.PolysrfFilter, out objref); if (rc != Rhino.Commands.Result.Success) return rc; var brep = objref.Brep(); if (brep == null) return Rhino.Commands.Result.Failure; bool bIsSolid = brep.IsSolid; TestSurfaceDirConduit conduit = new TestSurfaceDirConduit(brep); conduit.Enabled = true; doc.Views.Redraw(); var gf = new Rhino.Input.Custom.GetOption(); gf.SetCommandPrompt("Press enter when done"); gf.AcceptNothing(true); if (!bIsSolid) gf.AddOption("Flip"); for (; ; ) { var res = gf.Get(); if (res == Rhino.Input.GetResult.Option) { conduit.Flip = !conduit.Flip; doc.Views.Redraw(); continue; } if (res == Rhino.Input.GetResult.Nothing) { if (!bIsSolid && conduit.Flip) { brep.Flip(); doc.Objects.Replace(objref, brep); } } break; } conduit.Enabled = false; doc.Views.Redraw(); return Rhino.Commands.Result.Success; } } class TestSurfaceDirConduit : Rhino.Display.DisplayConduit { readonly Brep m_brep; readonly List m_points; readonly List m_normals; public TestSurfaceDirConduit(Brep brep) { m_brep = brep; Flip = false; const int SURFACE_ARROW_COUNT = 5; int face_count = m_brep.Faces.Count; int capacity = face_count * SURFACE_ARROW_COUNT * SURFACE_ARROW_COUNT; m_points = new List(capacity); m_normals = new List(capacity); for (int i = 0; i < face_count; i++) { var face = brep.Faces[i]; var loop = face.OuterLoop; if (loop == null) continue; var udomain = face.Domain(0); var vdomain = face.Domain(1); var loop_bbox = loop.GetBoundingBox(true); if (loop_bbox.IsValid) { Interval domain = new Interval(loop_bbox.Min.X, loop_bbox.Max.X); domain = Interval.FromIntersection(domain, udomain); if (domain.IsIncreasing) udomain = domain; domain = new Interval(loop_bbox.Min.Y, loop_bbox.Max.Y); domain = Interval.FromIntersection(domain, vdomain); if (domain.IsIncreasing) vdomain = domain; } bool bUntrimmed = face.IsSurface; bool bRev = face.OrientationIsReversed; for (double u = 0; u < SURFACE_ARROW_COUNT; u += 1.0) { double d = u / (SURFACE_ARROW_COUNT - 1.0); double s = udomain.ParameterAt(d); var intervals = face.TrimAwareIsoIntervals(1, s); if (bUntrimmed || intervals.Length > 0) { for (double v = 0; v < SURFACE_ARROW_COUNT; v += 1.0) { d = v / (SURFACE_ARROW_COUNT - 1.0); double t = vdomain.ParameterAt(d); bool bAdd = bUntrimmed; for (int k = 0; !bAdd && k < intervals.Length; k++) { if (intervals[k].IncludesParameter(t)) bAdd = true; } if (bAdd) { var pt = face.PointAt(s, t); var vec = face.NormalAt(s, t); m_points.Add(pt); if (bRev) vec.Reverse(); m_normals.Add(vec); } } } } } } public bool Flip { get; set; } protected override void DrawOverlay(Rhino.Display.DrawEventArgs e) { if (m_points.Count > 0) { var color = Rhino.ApplicationSettings.AppearanceSettings.TrackingColor; for (int i = 0; i < m_points.Count; i++) { if (i % 100 == 0 && e.Display.InterruptDrawing()) break; var pt = m_points[i]; var dir = m_normals[i]; if (Flip) dir.Reverse(); e.Display.DrawDirectionArrow(pt, dir, color); } } } } ``` ```python # No Python sample available ``` -------------------------------------------------------------------------------- # Showing Objects Transforming Dynamically Source: https://developer.rhino3d.com/en/guides/cpp/showing-objects-transforming-dynamically/ This guide demonstrates how to dynamically draw transforming objects using C/C++. ## Overview The `CRhinoViewport` class has two member functions, `GetModelXform()` and `SetModelXform()`, that either retrieve or modify the model transformation matrix that is applied to objects before they are drawn. The model transformation matrix is intended to be used for dynamic drawing of objects. Note, the default model transformation matrix is the identity. Some of the Rhino command that use this technique to dynamically draw transforming objects include the *Move*, *Copy*, *Scale*, and *Rotate* commands. These commands derive new `CRhinoGetPoint` classes and override the virtual `DynamicDraw()` member function to draw objects dynamically as the mouse moves during a point picking operation. ## Sample The following is an sample `CRhinoGetPoint`-derived class that demonstrates how to dynamically draw transforming objects during a point picking operation. In this sample, the transformation is a simple translation, like used in the *Move* command. First, the class declaration... ```cpp //////////////////////////////////////////////////////////////////// // CRhinoGetTranslationPoint declaration class CRhinoGetTranslationPoint : public CRhinoGetPoint { public: CRhinoGetTranslationPoint(); ~CRhinoGetTranslationPoint() {} // CRhinoGetPoint overrides void SetBasePoint( ON_3dPoint base_point, BOOL bShowDistanceInStatusBar = false ); void OnMouseMove( CRhinoViewport& vp, UINT flags, const ON_3dPoint& pt, const CPoint* p ); void DynamicDraw( HDC hdc, CRhinoViewport& vp, const ON_3dPoint& pt ); // Additional helpers void AddObject( const CRhinoObject* object ); void CalculateTranslation( const ON_3dPoint& pt, ON_Xform& xform ); private: ON_3dPoint m_start_point; // starting point of translation ON_Xform m_xform; // transformation matrix ON_SimpleArray m_objects; //objects to transform }; //////////////////////////////////////////////////////////////////// // CRhinoGetTranslationPoint definition CRhinoGetTranslationPoint::CRhinoGetTranslationPoint() { m_xform.Identity(); } void CRhinoGetTranslationPoint::AddObject( const CRhinoObject* object ) { m_objects.Append( object ); } void CRhinoGetTranslationPoint::SetBasePoint( ON_3dPoint base_point, BOOL bShowDistanceInStatusBar ) { m_start_point = base_point; CRhinoGetPoint::SetBasePoint( base_point, bShowDistanceInStatusBar ); } void CRhinoGetTranslationPoint::CalculateTranslation( const ON_3dPoint& pt, ON_Xform& xform ) { ON_3dVector v = pt - m_start_point; if( v.IsTiny() ) xform.Identity(); else xform.Translation( v ); } void CRhinoGetTranslationPoint::OnMouseMove( CRhinoViewport& vp, UINT flags, const ON_3dPoint& pt, const CPoint* p ) { // Everytime the mouse moves, calculate the translation CalculateTranslation( pt, m_xform ); CRhinoGetPoint::OnMouseMove( vp, flags, pt, p ); } void CRhinoGetTranslationPoint::DynamicDraw( HDC hdc, CRhinoViewport& vp, const ON_3dPoint& pt ) { // Time to draw our objects dynamically int i, count = m_objects.Count(); if( m_xform.IsIdentity() == false && count > 0 ) { ON_Color saved_color = vp.DrawColor(); ON_Xform saved_model_xform; // Save the current model transformation, we will // need to restore it later vp.GetModelXform( saved_model_xform ); // Set the model transformation to ours vp.SetModelXform( m_xform ); // Draw all of the objects in our array for( i = 0; i < m_objects.Count(); i++ ) { const CRhinoObject* object = m_objects[i]; if( object == 0 ) continue; vp.SetDrawColor( object->ObjectDrawColor(TRUE) ); object->Draw( vp ); if( vp.InterruptDrawing() ) break; } // Reset modified viewport members vp.SetModelXform( saved_model_xform ); vp.SetDrawColor( saved_color ); } // Let the base class do its drawing too CRhinoGetPoint::DynamicDraw( hdc, vp, pt ); } ``` -------------------------------------------------------------------------------- # Simple Component Source: https://developer.rhino3d.com/en/guides/grasshopper/simple-component/ This guide gives an exhaustive, step by step explanation of how to build a simple Grasshopper component. ## Prerequisites This guide presumes you have all the necessary tools installed and have managed to debug a simple boilerplate component. If you are not there yet, please read [Installing Tools (Windows)](/guides/grasshopper/installing-tools-windows/) and [Your First Component (Windows)](/guides/grasshopper/your-first-component-windows) This guide will skip over any complicated issues (such as mathematics, geometry and data handling) in order to reduce the totality of new concepts. You will however need to have a good understanding of basic OOP concepts such as classes, types and inheritance. If you do not understand these essentials, we recommend you start with [some other reading material first](/guides/general/rhino-developer-prerequisites/#programming-knowledge). ## Kernel.GH_Component The Grasshopper component wizards (in both Visual Studio for Windows and Visual Studio for Mac) include an empty Grasshopper component template. For the moment, let's ignore this and construct a simple component "from scratch." This guide presumes that you already have a component library setup or are continuing from in the *HelloGrasshopper* solution from the [Your First Component (Windows)](/guides/grasshopper/your-first-component-windows) guide. Add an empty class to your solution, call it *MyFirstComponent*. At this point a new file should be created (*MyFirstComponent*). At this point a new file should be created with (something close to) the following content: ```cs using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace HelloGrasshopper { class MyFirstComponent { } } ``` ```vbnet Class MyFirstComponent End Class ``` Since we'll be using primarily types from the `Grasshopper.Kernel` namespace. We'll import this namespace so that we have easy access to all types contained within it (unless otherwise specified, all further types discussed in this topic belong to `Grasshopper.Kernel`). In C#, we can also remove some unnecessary using statements while we're at it: ```cs using System; using Grasshopper.Kernel; namespace HelloGrasshopper { class MyFirstComponent { } } ``` ```vbnet Imports Grasshopper.Kernel Class MyFirstComponent End Class ``` The idea is that this class will be loaded by Grasshopper whenever this component library is loaded, but in order for that to happen we must make sure that this class is "visible" from outside this project. I.e., we need to make the accessor "public": ```cs using System; using Grasshopper.Kernel; namespace HelloGrasshopper { // If a class is not public, it won't be visible // from where Grasshopper is sitting. public class MyFirstComponent { } } ``` ```vbnet Imports Grasshopper.Kernel Public Class MyFirstComponent End Class ``` Now, we need to derive our `MyFirstComponent` class from the [GH_Component](/api/grasshopper/html/T_Grasshopper_Kernel_GH_Component.htm) base class defined inside Grasshopper. `GH_Component` takes care of almost all the complicated actions and mannerisms that constitute a component. It will handle data conversion, GUI, menus, file Input/Output, Error trapping and much, much more. This allows us to focus only on the unique aspects of our component. In order to derive from `GH_Component`, add this to the class... ```cs using System; using Grasshopper.Kernel; namespace HelloGrasshopper { public class MyFirstComponent : GH_Component { } } ``` ```vbnet Imports Grasshopper.Kernel Public Class MyFirstComponent Inherits GH_Component End Class ``` Deriving (inheriting) from `GH_Component` requires you to implement a number of methods. (Visual Studio can insert default implementations for all of these via the *Implement Abstract Class* menu option when right-clicking `GH_Component`. In Visual Studio for Mac, you can right-click `GH_Component` and select *Refactoring* > *Implement abstract members*.) At this point, you should have the following... ```cs using System; using Grasshopper.Kernel; namespace HelloGrasshopper { public class MyFirstComponent : GH_Component { protected override void RegisterInputParams(GH_Component.GH_InputParamManager pManager) { throw new NotImplementedException(); } protected override void RegisterOutputParams(GH_Component.GH_OutputParamManager pManager) { throw new NotImplementedException(); } protected override void SolveInstance(IGH_DataAccess DA) { throw new NotImplementedException(); } public override Guid ComponentGuid { get { throw new NotImplementedException(); } } } } ``` ```vbnet Imports Grasshopper.Kernel Public Class MyFirstComponent Inherits GH_Component Protected Overrides Sub RegisterInputParams(ByVal pManager As GH_Component.GH_InputParamManager) 'Don't worry about this just yet! End Sub Protected Overrides Sub RegisterOutputParams(ByVal pManager As GH_Component.GH_OutputParamManager) 'We'll get to it soon enough. End Sub Protected Overrides Sub SolveInstance(ByVal DA As IGH_DataAccess) 'I know it all looks scary. End Sub Public Overrides ReadOnly Property ComponentGuid() As System.Guid Get 'But we'll deal with it one item at a time. End Get End Property End Class ``` ## The Component Constructor As we've seen in the previous section, Visual Studio can populate the `MyFirstComponent` class with a collection of properties and methods that we need to implement. There is however another method that requires our attention that is missing. This is the constructor. The constructor is a special method inside each class which gets called when the class is instantiated (or "constructed"). This can happen only once (we feeble humans can only be born once as well after all) and it necessarily happens before anything else is allowed to happen. The `GH_Component` base class has a constructor which is not empty, so we have to call that constructor from within our constructor and supply it with all the information it needs. Add the following code near the top of the `MyFirstComponent` class... ```cs using System; using Grasshopper.Kernel; namespace HelloGrasshopper { public class MyFirstComponent : GH_Component { public MyFirstComponent() : base("MyFirst", "MFC", "My first component", "Extra", "Simple") { } ... ``` ```vbnet Imports Grasshopper.Kernel Public Class MyFirstComponent Inherits GH_Component Public Sub New() MyBase.New("MyFirst", "MFC", "My first component", "Extra", "Simple") End Sub ... ``` As you can see we need to supply a set of text constants, which are used to name and identify our component within the Grasshopper GUI. The text fields are... - *name* is the name of our component. The name is what appears on tooltips and panel dropdowns. - *abbreviation* is the abbreviation of our component. The abbreviation is what is written on the component once it appears on the canvas. - *description* is a description of our component. The description is used on tooltips to provide users with a more detailed idea about what this component is for. - *category* is the tab category for the component. The category equals the name of the tab onto which the component will appear. If a non-existing category is supplied, a new Tab will be added to the Grasshopper GUI. ![Component labels](https://developer.rhino3d.com/images/simple-component-01.png) ## Component Guids Every type of object inside a Grasshopper document must have a `ComponentGuid` associated with it. When a Grasshopper file (\**.gh* or \**.ghx*) is written these Guids are used as markers, so it becomes clear what portions of the file belong to which object. When the file is read back in, that marker is compared against the list of all cached components and if a match is found the appropriate component is asked to deserialize itself from the appropriate file portion. When no matching component can be found it is assumed that whoever wrote the file had access to certain components that are not available locally, and that portion of the file is dutifully skipped. So, long story short, we need to invent a Guid (Globally Unique IDentifier) that will positively and unerringly indicate *this* component. You can generate new Guids using an [Online Guid Generator](https://www.guidgenerator.com/) or Microsoft's popular *guidgen.exe*. **Never** re-use a Guid and **never** edit one by hand. Always generate a proper one using an official tool. Once you have a new Guid standing by, modify the [ComponentGuid](/api/grasshopper/html/P_Grasshopper_Kernel_IGH_DocumentObject_ComponentGuid.htm) property to return it: ```cs ... public override Guid ComponentGuid { // Don't copy this GUID, make a new one get { return new Guid("419c3a3a-cc48-4717-8cef-5f5647a5ecfc"); } } ... ``` ```vbnet ... Public Overrides ReadOnly Property ComponentGuid() As System.Guid Get 'Don't copy this GUID, make a new one Return New Guid("419c3a3a-cc48-4717-8cef-5f5647a5ecfc") End Get End Property ... ``` ## Parameter Registration Components have unique input and output parameters which are most often fixed. We are ignoring those rare cases where a component either has no inputs or no outputs, or where there is a variable number of parameters. There are two methods that allow you to define (or "register") these parameters. These routines are called from within the base class constructor and they are only called once. Let's have a look at the default implementation again: ```cs ... protected override void RegisterInputParams(GH_Component.GH_InputParamManager pManager) { throw new NotImplementedException(); } protected override void RegisterOutputParams(GH_Component.GH_OutputParamManager pManager) { throw new NotImplementedException(); } ... ``` ```vbnet ... Protected Overrides Sub RegisterInputParams(ByVal pManager As GH_Component.GH_InputParamManager) End Sub Protected Overrides Sub RegisterOutputParams(ByVal pManager As GH_Component.GH_OutputParamManager) End Sub ... ``` Although it would technically be possible to manually register parameters, we highly recommend you use the methods on `pManager`. `pManager` has methods for adding all the basic parameter types and it often even allows you to specify default values. In this example we'll only create two parameters (one input, one output) and they will both be of type `String`... ```cs ... protected override void RegisterInputParams(GH_Component.GH_InputParamManager pManager) { pManager.AddTextParameter("String", "S", "String to reverse", GH_ParamAccess.item); } protected override void RegisterOutputParams(GH_Component.GH_OutputParamManager pManager) { pManager.AddTextParameter("Reverse", "R", "Reversed string", GH_ParamAccess.item); } ... ``` ```vbnet ... Protected Overrides Sub RegisterInputParams(ByVal pManager As Grasshopper.Kernel.GH_Component.GH_InputParamManager) pManager.AddTextParameter("String", "S", "String to reverse") End Sub Protected Overrides Sub RegisterOutputParams(ByVal pManager As Grasshopper.Kernel.GH_Component.GH_OutputParamManager) pManager.AddTextParameter("Reverse", "R", "Reversed string") End Sub ... ``` When we compile this project (assuming it has been setup correctly), the component will already be available on the Grasshopper tabs and it can be placed onto the canvas: ![Sanity Check](https://developer.rhino3d.com/images/simple-component-02.png) ## The Solver Routine Our new component sure looks perky and expensive, but it doesn't do anything yet. We still need to write the contents of the `SolveInstance()` subroutine, which is where all the action takes place. The `SolveInstance()` method is called upon whenever the component needs to handle input data. In this particular example, if we plug a list of twelve strings into the `[S]` parameter, `SolveInstance()` will be called twelve times. As you may already have guessed, the component we're writing will reverse a given textual string from `[S]` and output the result to `[R]`. Since we're operating on individual items of data (the default behaviour), all we need to do inside the `SolveInstance()` function is retrieve the current String from `[S]`, reverse it and assign it to `[R]`. Now, String reversal is not a function that is directly available in the framework `String` type, so we need to actually do some thinking: ```cs ... protected override void SolveInstance(IGH_DataAccess DA) { // Declare a variable for the input String string data = null; // Use the DA object to retrieve the data inside the first input parameter. // If the retieval fails (for example if there is no data) we need to abort. if (!DA.GetData(0, ref data)) { return; } // If the retrieved data is Nothing, we need to abort. // We're also going to abort on a zero-length String. if (data == null) { return; } if (data.Length == 0) { return; } // Convert the String to a character array. char[] chars = data.ToCharArray(); // Reverse the array of character. System.Array.Reverse(chars); // Use the DA object to assign a new String to the first output parameter. DA.SetData(0, new string(chars)); } ... ``` ```vbnet ... Protected Overrides Sub SolveInstance(ByVal DA As Grasshopper.Kernel.IGH_DataAccess) 'Declare a variable for the input String Dim data As String = Nothing 'Use the DA object to retrieve the data inside the first input parameter. 'If the retieval fails (for example if there is no data) we need to abort. If (Not DA.GetData(0, data)) Then Return 'If the retrieved data is Nothing, we need to abort. 'We're also going to abort on a zero-length String. If (data Is Nothing) Then Return If (data.Length = 0) Then Return 'Convert the String to a character array. Dim chars As Char() = data.ToCharArray() 'Reverse the array of character. Array.Reverse(chars) 'Use the DA object to assign a new String to the first output parameter. DA.SetData(0, New String(chars)) End Sub ... ``` Build, run, and ... ![That was easy](https://developer.rhino3d.com/images/simple-component-03.png) **DONE!** You have just built your first Grasshopper component from the ground up. **Now what?** ## Next Steps Now that you have built a component from scratch, let's discuss parameter order, default values for inputs, and go a little further in depth. Next, check out the [Simple Mathematics Component](/guides/grasshopper/simple-mathematics-component) guide. ## Related Topics - [What is a Grasshopper Component?](/guides/grasshopper/what-is-a-grasshopper-component) - [Installing Tools (Windows)](/guides/grasshopper/installing-tools-windows/) - [Your First Component (Windows)](/guides/grasshopper/your-first-component-windows) - [Simple Mathematics Component](/guides/grasshopper/simple-mathematics-component) - [Simple Geometry Component](/guides/grasshopper/simple-geometry-component) - [Simple Data Types](/guides/grasshopper/simple-data-types) - [Simple Parameters](/guides/grasshopper/simple-parameters) - [Custom Component Options](/guides/grasshopper/custom-component-options) -------------------------------------------------------------------------------- # Single Color Back Faces Source: https://developer.rhino3d.com/en/samples/rhinocommon/single-color-back-faces/ Demonstrates how to determine the curve and object colors of back faces. ```cs partial class Examples { public static Result SingleColorBackfaces(RhinoDoc doc) { var display_mode_descs = //DisplayModeDescription.GetDisplayModes(); from dm in DisplayModeDescription.GetDisplayModes() where dm.EnglishName == "Shaded" select dm; foreach (var dmd in display_mode_descs) { RhinoApp.WriteLine("CurveColor {0}", dmd.DisplayAttributes.CurveColor.ToKnownColor()); RhinoApp.WriteLine("ObjectColor {0}", dmd.DisplayAttributes.ObjectColor.ToKnownColor()); } return Result.Success; } } ``` ```python # No Python sample available ``` -------------------------------------------------------------------------------- # Skipping current iteration in a For loop Source: https://developer.rhino3d.com/en/guides/rhinoscript/skipping-iterations-for-loop/ This guide demonstrates how to skip the current iteration in a For Loop. ## Problem Both the C++ and C# programming languages have a ```continue``` statement that, when used with a ```For``` loop, skips the remaining statements of that iteration and moves on to next iteration. Does VBScript have anything like this? ## Solution There is no ```continue``` or continue-like statement in VBScript. But using a ```Do While loop``` inside of a ```For Each``` statement, you can achieve the same functionality. For example: ```vbnet For i = 0 To 10 Do If i = 4 Then Exit Do Rhino.Print i Loop While False Next ``` Here is another example: ```vbnet Sub TestContinue Dim arrTests, arrTest arrTests = Array( _ Array(1) _ , Array(1,2,3 ) _ , Array(1,2) _ , Array(1) _ , Array(1,2,3) _ ) For Each arrTest In arrTests Call Rhino.Print("Process: {" & Join(arrTest, ", ") & "}") Do While True ' Continue trick Call Rhino.Print(" Process: " & arrTest(0)) If 0 = UBound(arrTest) Then Exit Do ' Continue Call Rhino.Print(" Process: " & arrTest(1)) If 1 = UBound(arrTest) Then Exit Do ' Continue Call Rhino.Print(" Process: " & arrTest(2)) Exit Do Loop Next End Sub ``` -------------------------------------------------------------------------------- # Sorting VBS Arrays with .NET Source: https://developer.rhino3d.com/en/guides/rhinoscript/sorting-vbs-arrays-with-net/ This guide demonstrates how to use the .NET Framework to sort arrays in RhinoScript. ## Overview One of the big limitations in VBScript is that there is no easy way to sort a list of items. To put a list in alphabetical order requires you to either use the pre-canned RhinoScript methods, such as `SortNumbers`, `SortPoints` or `SortStrings`, or write a sorting function of your own, like the following bubble sort code: ```vbnet For i = (UBound(arrNames) - 1) to 0 Step -1 For j= 0 to i If UCase(arrNames(j)) > UCase(arrNames(j+1)) Then strHolder = arrNames(j+1) arrNames(j+1) = arrNames(j) arrNames(j) = strHolder End If Next Next ``` ## Discussion There is another option and that is to use the .NET Framework to help. The majority of .NET Framework classes are unusable in VBScript. However, there are a large number of .NET classes that have COM-callable wrappers. T his means these classes include a COM interface enabling them to be accessed from VBScript. For example, consider the following script: ```vbnet Set DataList = CreateObject("System.Collections.ArrayList") DataList.Add "B" DataList.Add "C" DataList.Add "E" DataList.Add "D" DataList.Add "A" DataList.Sort() For Each strItem in DataList Rhino.Print strItem Next ``` Notice that we have created an instance of the .NET Framework class `System.Collections.ArrayList`. We then use the `Add` method to add five items to the list. To sort the list, all we need to do is call the `ArrayList`'s `Sort` method. Now, what if you really wanted those values in descending order? In this case, just call `ArrayList`'s `Sort` method after calling the `Sort` method. ```vbnet Set DataList = CreateObject("System.Collections.ArrayList") DataList.Add "B" DataList.Add "C" DataList.Add "E" DataList.Add "D" DataList.Add "A" DataList.Sort() DataList.Reverse() For Each strItem in DataList Rhino.Print strItem Next ``` Also, have you ever tried to remove an item from a VBScript array? It is not easy. But, with the .NET Framework's `ArrayList`, you just need to call the `Remove` method. The following script, an `ArrayList`, sorts it, and then removes the entry for `D`... ```vbnet Set DataList = CreateObject("System.Collections.ArrayList") DataList.Add "B" DataList.Add "C" DataList.Add "E" DataList.Add "D" DataList.Add "A" DataList.Sort() DataList.Remove("D") For Each strItem in DataList Rhino.Print strItem Next ``` Copying items VBScript arrays and an `ArrayList` is just as easy. In the following script, an `ArrayList` is used to sort a list of layer names... ```vbnet LayerNames = Rhino.LayerNames Set DataList = CreateObject("System.Collections.ArrayList") For i = 0 To UBound(LayerNames) DataList.Add LayerNames(i) Next DataList.Sort() For i = 0 To UBound(LayerNames) LayerNames(i) = DataList(i) Next Layer = Rhino.ListBox(LayerNames, "Layer to set current") ``` -------------------------------------------------------------------------------- # Space Morph Source: https://developer.rhino3d.com/en/samples/rhinocommon/space-morph/ Demonstrates how to construct the Twist, Bend, Taper, Maelstrom, Stretch, Sporph, Flow, and Splop space morphs. ```cs partial class Examples { static Rhino.DocObjects.ObjectType SpaceMorphObjectFilter() { Rhino.DocObjects.ObjectType filter = Rhino.DocObjects.ObjectType.Point | Rhino.DocObjects.ObjectType.PointSet | Rhino.DocObjects.ObjectType.Curve | Rhino.DocObjects.ObjectType.Surface | Rhino.DocObjects.ObjectType.PolysrfFilter | Rhino.DocObjects.ObjectType.Mesh | Rhino.DocObjects.ObjectType.Grip | Rhino.DocObjects.ObjectType.Cage; return filter; } public static Rhino.Commands.Result Twist(Rhino.RhinoDoc doc) { ObjectType filter = SpaceMorphObjectFilter(); Rhino.DocObjects.ObjRef objref; Rhino.Commands.Result rc = Rhino.Input.RhinoGet.GetOneObject("Select object to twist", false, filter, out objref); if (rc != Rhino.Commands.Result.Success || objref == null) return rc; Rhino.Geometry.Line axis; rc = Rhino.Input.RhinoGet.GetLine(out axis); if (rc != Rhino.Commands.Result.Success || axis == null) return rc; double angle = Rhino.RhinoMath.UnsetValue; rc = Rhino.Input.RhinoGet.GetNumber("Twist angle in degrees", false, ref angle); if (rc != Rhino.Commands.Result.Success || !Rhino.RhinoMath.IsValidDouble(angle)) return rc; Rhino.Geometry.Morphs.TwistSpaceMorph morph = new Rhino.Geometry.Morphs.TwistSpaceMorph(); morph.TwistAxis = axis; morph.TwistAngleRadians = Rhino.RhinoMath.ToRadians(angle); Rhino.Geometry.GeometryBase geom = objref.Geometry().Duplicate(); if (morph.Morph(geom)) { doc.Objects.Add(geom); doc.Views.Redraw(); } return Rhino.Commands.Result.Success; } public static Rhino.Commands.Result Bend(Rhino.RhinoDoc doc) { ObjectType filter = SpaceMorphObjectFilter(); Rhino.DocObjects.ObjRef objref; Rhino.Commands.Result rc = Rhino.Input.RhinoGet.GetOneObject("Select object to bend", false, filter, out objref); if (rc != Rhino.Commands.Result.Success || objref == null) return rc; Rhino.Geometry.Line axis; rc = Rhino.Input.RhinoGet.GetLine(out axis); if (rc != Rhino.Commands.Result.Success || axis == null) return rc; Rhino.Geometry.Point3d point; rc = Rhino.Input.RhinoGet.GetPoint("Point to bend through", false, out point); if (rc != Rhino.Commands.Result.Success || !point.IsValid) return rc; Rhino.Geometry.Morphs.BendSpaceMorph morph = new Rhino.Geometry.Morphs.BendSpaceMorph(axis.From, axis.To, point, true, false); Rhino.Geometry.GeometryBase geom = objref.Geometry().Duplicate(); if (morph.Morph(geom)) { doc.Objects.Add(geom); doc.Views.Redraw(); } return Rhino.Commands.Result.Success; } public static Rhino.Commands.Result Taper(Rhino.RhinoDoc doc) { ObjectType filter = SpaceMorphObjectFilter(); Rhino.DocObjects.ObjRef objref; Rhino.Commands.Result rc = Rhino.Input.RhinoGet.GetOneObject("Select object to taper", false, filter, out objref); if (rc != Rhino.Commands.Result.Success || objref == null) return rc; Rhino.Geometry.Line axis; rc = Rhino.Input.RhinoGet.GetLine(out axis); if (rc != Rhino.Commands.Result.Success || axis == null) return rc; double radius0 = Rhino.RhinoMath.UnsetValue; rc = Rhino.Input.RhinoGet.GetNumber("Starting radius", false, ref radius0); if (rc != Rhino.Commands.Result.Success || !Rhino.RhinoMath.IsValidDouble(radius0)) return rc; double radius1 = Rhino.RhinoMath.UnsetValue; rc = Rhino.Input.RhinoGet.GetNumber("Ending radius", false, ref radius1); if (rc != Rhino.Commands.Result.Success || !Rhino.RhinoMath.IsValidDouble(radius1)) return rc; Rhino.Geometry.Morphs.TaperSpaceMorph morph = new Rhino.Geometry.Morphs.TaperSpaceMorph(axis.From, axis.To, radius0, radius1, false, false); Rhino.Geometry.GeometryBase geom = objref.Geometry().Duplicate(); if (morph.Morph(geom)) { doc.Objects.Add(geom); doc.Views.Redraw(); } return Rhino.Commands.Result.Success; } public static Rhino.Commands.Result Maelstrom(Rhino.RhinoDoc doc) { ObjectType filter = SpaceMorphObjectFilter(); Rhino.DocObjects.ObjRef objref; Rhino.Commands.Result rc = Rhino.Input.RhinoGet.GetOneObject("Select object to maelstrom", false, filter, out objref); if (rc != Rhino.Commands.Result.Success || objref == null) return rc; Rhino.Geometry.Plane plane = doc.Views.ActiveView.ActiveViewport.ConstructionPlane(); double radius0 = Rhino.RhinoMath.UnsetValue; rc = Rhino.Input.RhinoGet.GetNumber("Starting radius", false, ref radius0); if (rc != Rhino.Commands.Result.Success || !Rhino.RhinoMath.IsValidDouble(radius0)) return rc; double radius1 = Rhino.RhinoMath.UnsetValue; rc = Rhino.Input.RhinoGet.GetNumber("Ending radius", false, ref radius1); if (rc != Rhino.Commands.Result.Success || !Rhino.RhinoMath.IsValidDouble(radius1)) return rc; double angle = Rhino.RhinoMath.UnsetValue; rc = Rhino.Input.RhinoGet.GetNumber("Twist angle in degrees", false, ref angle); if (rc != Rhino.Commands.Result.Success || !Rhino.RhinoMath.IsValidDouble(angle)) return rc; Rhino.Geometry.Morphs.MaelstromSpaceMorph morph = new Rhino.Geometry.Morphs.MaelstromSpaceMorph(plane, radius0, radius1, Rhino.RhinoMath.ToRadians(angle)); Rhino.Geometry.GeometryBase geom = objref.Geometry().Duplicate(); if (morph.Morph(geom)) { doc.Objects.Add(geom); doc.Views.Redraw(); } return Rhino.Commands.Result.Success; } public static Rhino.Commands.Result Stretch(Rhino.RhinoDoc doc) { ObjectType filter = SpaceMorphObjectFilter(); Rhino.DocObjects.ObjRef objref; Rhino.Commands.Result rc = Rhino.Input.RhinoGet.GetOneObject("Select object to stretch", false, filter, out objref); if (rc != Rhino.Commands.Result.Success || objref == null) return rc; Rhino.Geometry.Plane plane = doc.Views.ActiveView.ActiveViewport.ConstructionPlane(); Rhino.Geometry.Line axis; rc = Rhino.Input.RhinoGet.GetLine(out axis); if (rc != Rhino.Commands.Result.Success || axis == null) return rc; Rhino.Geometry.Morphs.StretchSpaceMorph morph = new Rhino.Geometry.Morphs.StretchSpaceMorph(axis.From, axis.To, axis.Length * 1.5); Rhino.Geometry.GeometryBase geom = objref.Geometry().Duplicate(); if (morph.Morph(geom)) { doc.Objects.Add(geom); doc.Views.Redraw(); } return Rhino.Commands.Result.Success; } public static Rhino.Commands.Result Sporph(Rhino.RhinoDoc doc) { ObjectType filter = SpaceMorphObjectFilter(); Rhino.DocObjects.ObjRef objref; Rhino.Commands.Result rc = Rhino.Input.RhinoGet.GetOneObject("Select object to sporph", false, filter, out objref); if (rc != Rhino.Commands.Result.Success || objref == null) return rc; Rhino.Input.Custom.GetObject go0 = new Rhino.Input.Custom.GetObject(); go0.SetCommandPrompt("Source surface"); go0.GeometryFilter = Rhino.DocObjects.ObjectType.Surface; go0.SubObjectSelect = false; go0.EnablePreSelect(false, true); go0.DeselectAllBeforePostSelect = false; go0.Get(); if (go0.CommandResult() != Rhino.Commands.Result.Success) return go0.CommandResult(); Rhino.DocObjects.ObjRef srf0_ref = go0.Object(0); Rhino.Input.Custom.GetObject go1 = new Rhino.Input.Custom.GetObject(); go1.SetCommandPrompt("Source surface"); go1.GeometryFilter = Rhino.DocObjects.ObjectType.Surface; go1.SubObjectSelect = false; go1.EnablePreSelect(false, true); go1.DeselectAllBeforePostSelect = false; go1.Get(); if (go1.CommandResult() != Rhino.Commands.Result.Success) return go1.CommandResult(); Rhino.DocObjects.ObjRef srf1_ref = go1.Object(0); Rhino.Geometry.Morphs.SporphSpaceMorph morph = new Rhino.Geometry.Morphs.SporphSpaceMorph(srf0_ref.Surface(), srf1_ref.Surface()); Rhino.Geometry.GeometryBase geom = objref.Geometry().Duplicate(); if (morph.Morph(geom)) { doc.Objects.Add(geom); doc.Views.Redraw(); } return Rhino.Commands.Result.Success; } public static Rhino.Commands.Result Flow(Rhino.RhinoDoc doc) { ObjectType filter = SpaceMorphObjectFilter(); Rhino.DocObjects.ObjRef objref; Rhino.Commands.Result rc = Rhino.Input.RhinoGet.GetOneObject("Select object to flow", false, filter, out objref); if (rc != Rhino.Commands.Result.Success || objref == null) return rc; Rhino.Input.Custom.GetObject go0 = new Rhino.Input.Custom.GetObject(); go0.SetCommandPrompt("Source curve"); go0.GeometryFilter = Rhino.DocObjects.ObjectType.Curve; go0.SubObjectSelect = false; go0.EnablePreSelect(false, true); go0.DeselectAllBeforePostSelect = false; go0.Get(); if (go0.CommandResult() != Rhino.Commands.Result.Success) return go0.CommandResult(); Rhino.DocObjects.ObjRef crv0_ref = go0.Object(0); Rhino.Input.Custom.GetObject go1 = new Rhino.Input.Custom.GetObject(); go1.SetCommandPrompt("Source curve"); go1.GeometryFilter = Rhino.DocObjects.ObjectType.Curve; go1.SubObjectSelect = false; go1.EnablePreSelect(false, true); go1.DeselectAllBeforePostSelect = false; go1.Get(); if (go1.CommandResult() != Rhino.Commands.Result.Success) return go1.CommandResult(); Rhino.DocObjects.ObjRef crv1_ref = go1.Object(0); Rhino.Geometry.Morphs.FlowSpaceMorph morph = new Rhino.Geometry.Morphs.FlowSpaceMorph(crv0_ref.Curve(), crv1_ref.Curve(), false); Rhino.Geometry.GeometryBase geom = objref.Geometry().Duplicate(); if (morph.Morph(geom)) { doc.Objects.Add(geom); doc.Views.Redraw(); } return Rhino.Commands.Result.Success; } public static Rhino.Commands.Result Splop(Rhino.RhinoDoc doc) { ObjectType filter = SpaceMorphObjectFilter(); Rhino.DocObjects.ObjRef objref; Rhino.Commands.Result rc = Rhino.Input.RhinoGet.GetOneObject("Select object to splop", false, filter, out objref); if (rc != Rhino.Commands.Result.Success || objref == null) return rc; Rhino.Geometry.Plane plane = doc.Views.ActiveView.ActiveViewport.ConstructionPlane(); Rhino.Input.Custom.GetObject go = new Rhino.Input.Custom.GetObject(); go.SetCommandPrompt("Surface to splop on"); go.GeometryFilter = Rhino.DocObjects.ObjectType.Surface; go.SubObjectSelect = false; go.EnablePreSelect(false, true); go.DeselectAllBeforePostSelect = false; go.Get(); if (go.CommandResult() != Rhino.Commands.Result.Success) return go.CommandResult(); Rhino.DocObjects.ObjRef srfref = go.Object(0); double u, v; srfref.SurfaceParameter(out u, out v); Rhino.Geometry.Point2d uv = new Rhino.Geometry.Point2d(u,v); Rhino.Geometry.Morphs.SplopSpaceMorph morph = new Rhino.Geometry.Morphs.SplopSpaceMorph(plane, srfref.Surface(), uv); Rhino.Geometry.GeometryBase geom = objref.Geometry().Duplicate(); if (morph.Morph(geom)) { doc.Objects.Add(geom); doc.Views.Redraw(); } return Rhino.Commands.Result.Success; } } ``` ```python # No Python sample available ``` -------------------------------------------------------------------------------- # Split Brep Source: https://developer.rhino3d.com/en/samples/cpp/split-brep/ Demonstrates how to split a brep with another brep using the RhinoSplitBrep function. ```cpp CRhinoCommand::result CCommandTest::RunCommand( const CRhinoCommandContext& context ) { // Pick the brep to split CRhinoGetObject go; go.SetCommandPrompt( L"Select surface or polysuface to split" ); go.SetGeometryFilter( CRhinoGetObject::surface_object | CRhinoGetObject::polysrf_object ); go.GetObjects( 1, 1 ); if( go.CommandResult() != success ) return go.CommandResult(); const CRhinoObjRef& split_ref = go.Object(0); const CRhinoObject* split_object = split_ref.Object(); if( !split_object ) return failure; const ON_Brep* split = split_ref.Brep(); if( !split ) return failure; // Pick the cutting brep go.SetCommandPrompt( L"Select cutting surface or polysuface" ); go.EnablePreSelect( FALSE ); go.EnableDeselectAllBeforePostSelect( FALSE ); go.GetObjects( 1, 1 ); if( go.CommandResult() != success ) return go.CommandResult(); const ON_Brep* cutter = go.Object(0).Brep(); if( !cutter ) return failure; ON_SimpleArray pieces; double tol = context.m_doc.AbsoluteTolerance(); // Try splitting the brep if( !RhinoBrepSplit(*split, *cutter, tol, pieces) ) RhinoApp().Print( L"Unable to split brep.\n" ); int i, count = pieces.Count(); if( count == 0 | count == 1 ) { if( count == 1 ) delete pieces[0]; return nothing; } CRhinoObjectAttributes attrib = split_object->Attributes(); attrib.m_uuid = ON_nil_uuid; const CRhinoObjectVisualAnalysisMode* vam_list = split_object->m_analysis_mode_list; for( i = 0; i < count; i++ ) { CRhinoBrepObject* brep_object = new CRhinoBrepObject( attrib ); if( brep_object ) { brep_object->SetBrep( pieces[i] ); if( context.m_doc.AddObject(brep_object) ) RhinoCopyAnalysisModes( vam_list, brep_object ); else delete brep_object; } } context.m_doc.DeleteObject( split_ref ); context.m_doc.Redraw(); return success; } ``` -------------------------------------------------------------------------------- # Split Brep Edges Source: https://developer.rhino3d.com/en/samples/cpp/split-brep-edges/ Demonstrates how to split the edges of breps. ```cpp /* Description: Splits a brep edge into two edges. Parameters: brep - [in/out] The brep to modify. edge_index - [in] The index of the edge to split. edge_t - [in] The parameter on the edge to split at. Returns: True if successful, false otherwise. */ static bool SplitBrepEdge( ON_Brep& brep, int edge_index, double edge_t ) { bool rc = true; const ON_BrepEdge& edge = brep.m_E[edge_index]; int trim_count = edge.TrimCount(); ON_SimpleArray trim_t( trim_count ); trim_t.SetCount( trim_count ); int i; for( i = 0; i < trim_count; i++ ) { // Given a trim and parameter on the corresponding 3d edge, // get the corresponding parameter on the 2d trim curve. double t = 0.0; rc = brep.GetTrimParameter( edge.m_ti[i], edge_t, &t ); if( rc ) trim_t[i] = t; else break; } if( rc ) { // Splits an edge into two edges. The input edge // becomes the left portion and a new edge is created // for the right portion. rc = brep.SplitEdge( edge_index, edge_t, trim_t, -1 ); // Delete any unreferenced objects from the brep arrays, // reindexes as needed, and shrinks arrays to minimum required size. brep.Compact(); // Set the brep's vertex tolerances. brep.SetVertexTolerances( true ); } return rc; } CRhinoCommand::result CCommandTest::RunCommand( const CRhinoCommandContext& context ) { // Don't split kinky surfaces... CRhinoKeepKinkySurfaces keep_kinky_srfs; CRhinoGetObject go; go.SetCommandPrompt( L"Select edge to split" ); go.SetGeometryFilter( CRhinoGetObject::curve_object ); go.SetGeometryAttributeFilter( CRhinoGetObject::edge_curve ); go.EnableReferenceObjectSelect( false ); go.GetObjects( 1, 1 ); if( go.CommandResult() != success ) return go.CommandResult(); const CRhinoObjRef objref = go.Object(0); const CRhinoObject* obj = objref.Object(); const ON_BrepEdge* edge = objref.Edge(); const ON_Brep* brep = objref.Brep(); if( 0 == obj | 0 == edge | 0 == brep ) return failure; CRhinoGetPoint gp; gp.SetCommandPrompt( L"Point to split edge" ); gp.Constrain( *edge ); gp.GetPoint(); if( gp.CommandResult() != success ) return gp.CommandResult(); const ON_BrepTrim* trim = 0; double edge_t = 0; edge = gp.PointOnEdge( &edge_t, trim ); if( edge ) { ON_Brep newbrep( *brep ); if( SplitBrepEdge(newbrep, edge->m_edge_index, edge_t) ) { context.m_doc.ReplaceObject( CRhinoObjRef(obj), newbrep ); context.m_doc.Redraw(); } } return success; } ``` -------------------------------------------------------------------------------- # Split BReps with Planes Source: https://developer.rhino3d.com/en/samples/rhinocommon/split-breps-with-planes/ Split a Set of BReps with a Plane ```cs partial class Examples { public static Result SplitBrepsWithPlane(RhinoDoc doc) { //First, collect all the breps to split ObjRef[] obj_refs; var rc = RhinoGet.GetMultipleObjects("Select breps to split", false, ObjectType.Brep, out obj_refs); if (rc != Result.Success || obj_refs == null) return rc; // Get the final plane Plane plane; rc = RhinoGet.GetPlane(out plane); if (rc != Result.Success) return rc; //Iterate over all object references foreach (var obj_ref in obj_refs) { var brep = obj_ref.Brep(); var bbox = brep.GetBoundingBox(false); //Grow the boundingbox in all directions //If the boundingbox is flat (zero volume or even zero area) //then the CreateThroughBox method will fail. var min_point = bbox.Min; min_point.X -= 1.0; min_point.Y -= 1.0; min_point.Z -= 1.0; bbox.Min = min_point; var max_point = bbox.Max; max_point.X += 1.0; max_point.Y += 1.0; max_point.Z += 1.0; bbox.Max = max_point; var plane_surface = PlaneSurface.CreateThroughBox(plane, bbox); if (plane_surface == null) { //This is rare, it will most likely not happen unless either the plane or the boundingbox are invalid RhinoApp.WriteLine("Cutting plane could not be constructed."); } else { var breps = brep.Split(plane_surface.ToBrep(), doc.ModelAbsoluteTolerance); if (breps == null || breps.Length == 0) { RhinoApp.Write("Plane does not intersect brep (id:{0})", obj_ref.ObjectId); continue; } foreach (var brep_piece in breps) { doc.Objects.AddBrep(brep_piece); } doc.Objects.AddSurface(plane_surface); doc.Objects.Delete(obj_ref, false); } } doc.Views.Redraw(); return Result.Success; } } ``` ```python from Rhino import * from Rhino.DocObjects import * from Rhino.Commands import * from Rhino.Input import * from Rhino.Geometry import * from scriptcontext import doc def RunCommand(): #First, collect all the breps to split rc, obj_refs = RhinoGet.GetMultipleObjects("Select breps to split", False, ObjectType.Brep) if rc != Result.Success or obj_refs == None: return rc # Get the final plane rc, plane = RhinoGet.GetPlane() if rc != Result.Success: return rc #Iterate over all object references for obj_ref in obj_refs: brep = obj_ref.Brep() bbox = brep.GetBoundingBox(False) #Grow the boundingbox in all directions #If the boundingbox is flat (zero volume or even zero area) #then the CreateThroughBox method will fail. min_point = bbox.Min min_point.X -= 1.0 min_point.Y -= 1.0 min_point.Z -= 1.0 bbox.Min = min_point max_point = bbox.Max max_point.X += 1.0 max_point.Y += 1.0 max_point.Z += 1.0 bbox.Max = max_point plane_surface = PlaneSurface.CreateThroughBox(plane, bbox) if plane_surface == None: #This is rare, it will most likely not happen unless either the plane or the boundingbox are invalid RhinoApp.WriteLine("Cutting plane could not be constructed.") else: breps = brep.Split(plane_surface.ToBrep(), doc.ModelAbsoluteTolerance) if breps == None or breps.Length == 0: RhinoApp.Write("Plane does not intersect brep (id:{0})", obj_ref.ObjectId) continue for brep_piece in breps: doc.Objects.AddBrep(brep_piece) doc.Objects.AddSurface(plane_surface) doc.Objects.Delete(obj_ref, False) doc.Views.Redraw() return Result.Success if __name__ == "__main__": RunCommand() ``` -------------------------------------------------------------------------------- # Split Curve Into Multiple Segments Source: https://developer.rhino3d.com/en/samples/cpp/split-curve-into-multiple-segments/ Demonstrates how to split a curve into multiple curve segments. ```cpp CRhinoCommand::result CCommandTest::RunCommand( const CRhinoCommandContext& context ) { // Select curve to split CRhinoGetObject go; go.SetCommandPrompt( L"Select curve to split" ); go.SetGeometryFilter( CRhinoGetObject::curve_object ); go.GetObjects( 1, 1 ); if( go.CommandResult() != success ) return go.CommandResult(); // Validate selection const CRhinoObjRef& crv_obj_ref = go.Object(0); const CRhinoObject* crv_obj = crv_obj_ref.Object(); const ON_Curve* crv = crv_obj_ref.Curve(); if( 0 == crv_obj | 0 == crv ) return failure; // Number of segments to create CRhinoGetInteger gi; gi.SetCommandPrompt( L"Number of segments to create" ); gi.SetLowerLimit( 2 ); gi.SetUpperLimit( 100 ); gi.GetInteger(); if( gi.CommandResult() != success ) return gi.CommandResult(); int num_segments = gi.Number(); // Generate an array of curve parameters where // the splitting will occur. ON_SimpleArray curve_t( num_segments ); bool rc = RhinoDivideCurve( *crv, num_segments, 0, false, true, 0, &curve_t ); if( !rc ) { RhinoApp().Print( L"Error dividing curve into segments.\n" ); return failure; } // If the curve is closed, append the ending domain parameter ON_Interval dom = crv->Domain(); if( crv->IsClosed() ) curve_t.Append( dom.m_t[1] ); ON_3dmObjectAttributes atts( crv_obj->Attributes() ); // Do the splitting (or should I say trimming...) int i; for( i = 0; i < curve_t.Count() - 1; i++ ) { // Build an interval to trim ON_Interval interval( curve_t[i], curve_t[i+1] ); if( dom.Includes(interval) ) { // Do the trim ON_Curve* new_crv = ON_TrimCurve( *crv, interval ); if( new_crv ) { // Add the "trimmed" curve CRhinoCurveObject* new_crv_obj = new CRhinoCurveObject( atts ); new_crv_obj->SetCurve( new_crv ); context.m_doc.AddObject( new_crv_obj ); } } } // Delete the original object context.m_doc.DeleteObject( crv_obj_ref ); context.m_doc.Redraw(); return success; } ``` -------------------------------------------------------------------------------- # Split File Path String Source: https://developer.rhino3d.com/en/samples/rhinoscript/split-file-path-string/ Demonstrates how to break a file path string in to its components using RhinoScript. ```vbnet Sub SplitPath(ByVal sPath, ByRef sDrive, ByRef sDir, ByRef sFname, ByRef sExt) Dim fso Set fso = CreateObject("Scripting.FileSystemObject") sDrive = fso.GetDriveName(sPath) sDir = Mid(fso.GetParentFolderName(sPath), Len(sDrive)+1) & "\" sFname = fso.GetBaseName(sPath) sExt = "." & fso.GetExtensionName(sPath) Set fso = Nothing End Sub ``` -------------------------------------------------------------------------------- # Splitting Curves into Segments Source: https://developer.rhino3d.com/en/samples/rhinoscript/splitting-curves-to-segments/ Demonstrates how to split a curve into multiple segments using RhinoScript. ```vbnet Option Explicit Sub CurveSplitter Const rhCurveObject = 4 Dim sCurve sCurve = Rhino.GetObject("Select curve to split", rhCurveObject) If IsNull(sCurve) Then Exit Sub Dim nSegments nSegments = Rhino.GetInteger("Number of segments", 2, 2) If IsNull(nSegments) Then Exit Sub Dim aPoints aPoints = Rhino.DivideCurve(sCurve, nSegments) If Not IsArray(aPoints) Then Exit Sub Rhino.EnableRedraw False Dim i, t0, t1 For i = 0 To UBound(aPoints) - 1 t0 = Rhino.CurveClosestPoint(sCurve, aPoints(i)) t1 = Rhino.CurveClosestPoint(sCurve, aPoints(i+1)) Rhino.TrimCurve sCurve, Array(t0, t1), vbFalse Next Rhino.DeleteObject sCurve Rhino.EnableRedraw True End Sub ``` -------------------------------------------------------------------------------- # Sprite Drawing Source: https://developer.rhino3d.com/en/samples/rhinocommon/sprite-drawing/ Demonstrates how to draw bitmap sprites in Rhino. ```cs partial class Examples { static bool m_draw_single_sprite; static float m_sprite_size = 30; static bool m_draw_world_location = true; public static Rhino.Commands.Result SpriteDrawing(RhinoDoc doc) { var sprite_mode = new Rhino.Input.Custom.OptionToggle(m_draw_single_sprite, "SpriteList", "SingleSprite"); var size_option = new Rhino.Input.Custom.OptionDouble(m_sprite_size); var space_option = new Rhino.Input.Custom.OptionToggle(m_draw_world_location, "Screen", "World"); var go = new Rhino.Input.Custom.GetOption(); go.SetCommandPrompt("Sprite drawing mode"); go.AddOptionToggle("Mode", ref sprite_mode); go.AddOptionDouble("Size", ref size_option); go.AddOptionToggle("DrawSpace", ref space_option); int option_go = go.AddOption("Spin"); int option_file = go.AddOption("FileSprite"); Rhino.Display.DisplayPipeline.PostDrawObjects += DisplayPipeline_PostDrawObjects; Rhino.Display.DisplayPipeline.CalculateBoundingBox += DisplayPipeline_CalculateBoundingBox; doc.Views.Redraw(); while (go.Get() == Rhino.Input.GetResult.Option) { m_draw_single_sprite = sprite_mode.CurrentValue; m_sprite_size = (float)size_option.CurrentValue; m_draw_world_location = space_option.CurrentValue; if (go.OptionIndex() == option_go) { var gs = new Rhino.Input.Custom.GetOption(); gs.SetCommandPrompt("press enter/escape to end"); gs.SetWaitDuration(1); var vp = doc.Views.ActiveView.MainViewport; while (gs.Get() == Rhino.Input.GetResult.Timeout) { vp.Rotate(0.1, Vector3d.ZAxis, Point3d.Origin); doc.Views.Redraw(); } } else if (go.OptionIndex() == option_file) { var dlg = new Rhino.UI.OpenFileDialog(); if (dlg.ShowDialog()) { System.Drawing.Bitmap bmp = new System.Drawing.Bitmap(dlg.FileName); m_sprite = new Rhino.Display.DisplayBitmap(bmp); } doc.Views.Redraw(); } else doc.Views.Redraw(); } Rhino.Display.DisplayPipeline.PostDrawObjects -= DisplayPipeline_PostDrawObjects; Rhino.Display.DisplayPipeline.CalculateBoundingBox -= DisplayPipeline_CalculateBoundingBox; return Rhino.Commands.Result.Success; } static void DisplayPipeline_CalculateBoundingBox(object sender, Rhino.Display.CalculateBoundingBoxEventArgs e) { if (m_draw_single_sprite) { BoundingBox bbox = new BoundingBox(new Point3d(20, 0, 0), new Point3d(20, 0, 0)); e.IncludeBoundingBox(bbox); } else { var items = GetItems(); e.IncludeBoundingBox(items.BoundingBox); } } static void DisplayPipeline_PostDrawObjects(object sender, Rhino.Display.DrawEventArgs e) { var spr = GetSprite(); if (m_draw_single_sprite) { if (m_draw_world_location) e.Display.DrawSprite(spr, new Point3d(20, 0, 0), m_sprite_size, System.Drawing.Color.White, false); else e.Display.DrawSprite(spr, new Point2d(150, 150), m_sprite_size, System.Drawing.Color.White); } else { var items = GetItems(); e.Display.DrawSprites(spr, items, m_sprite_size, false); } } static Rhino.Display.DisplayBitmapDrawList m_items; static Rhino.Display.DisplayBitmapDrawList GetItems() { if (m_items == null) { m_items = new Rhino.Display.DisplayBitmapDrawList(); var points = new System.Collections.Generic.List(); var colors = new System.Collections.Generic.List(); var random = new System.Random(); for (int i = 0; i < 200; i++) { double x = random.NextDouble(); double y = random.NextDouble(); double z = random.NextDouble(); points.Add(new Point3d(-30 + x * 60, -30 + y * 60, -30 + z * 60)); int r = (int)(x * 255); int g = (int)(y * 255); int b = (int)(z * 255); colors.Add(System.Drawing.Color.FromArgb(r, g, b)); } m_items.SetPoints(points, colors); } return m_items; } static Rhino.Display.DisplayBitmap m_sprite; static Rhino.Display.DisplayBitmap GetSprite() { if (m_sprite == null) { var bmp = new System.Drawing.Bitmap(64, 64); using (System.Drawing.Graphics g = System.Drawing.Graphics.FromImage(bmp)) { g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias; g.Clear(System.Drawing.Color.Transparent); var pen = new System.Drawing.Pen(System.Drawing.Color.White, 6); pen.EndCap = System.Drawing.Drawing2D.LineCap.Round; pen.StartCap = pen.EndCap; g.DrawArc(pen, new System.Drawing.Rectangle(16, 16, 32, 32), 0, 360); g.DrawLine(pen, new System.Drawing.Point(8, 8), new System.Drawing.Point(56, 56)); g.DrawLine(pen, new System.Drawing.Point(8, 56), new System.Drawing.Point(56, 8)); pen.Dispose(); } m_sprite = new Rhino.Display.DisplayBitmap(bmp); } return m_sprite; } } ``` ```python # No Python sample available ``` -------------------------------------------------------------------------------- # Straightening Circles Source: https://developer.rhino3d.com/en/samples/rhinoscript/straightening-circles/ Demonstrates how to create lines based on circle geometry using RhinoScript. ```vbnet ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' StraightenCircles.rvb -- September 2008 ' If this code works, it was written by Dale Fugier. ' If not, I don't know who wrote it. ' Works with Rhino 4.0. Option Explicit ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' StraightenCircles ' Creates lines based on the circumferences of circles. ' Lines will be oriented based on the plane of the circle. Sub StraightenCircles Dim obj_list, obj Dim length, plane, origin, xaxis Dim endpt, line obj_list = Rhino.GetObjects("Select circles to straighten", 4, True, True) If IsArray(obj_list) Then Call Rhino.EnableRedraw(False) For Each obj In obj_list If Rhino.IsCircle(obj) Then ' Gather data length = Rhino.CurveLength(obj) plane = Rhino.CurvePlane(obj) origin = plane(0) xaxis = plane(1) ' Calculate xaxis = Rhino.VectorUnitize(xaxis) xaxis = Rhino.VectorScale(xaxis, length) endpt = Rhino.PointAdd(origin, xaxis) line = Rhino.AddLine(origin, endpt) Call Rhino.SelectObject(line) End If Next Call Rhino.EnableRedraw(True) End If End Sub ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Rhino.AddStartupScript Rhino.LastLoadedScriptFile Rhino.AddAlias "StraightenCircles", "_NoEcho _-RunScript (StraightenCircles)" ``` -------------------------------------------------------------------------------- # Superfluous Knots Source: https://developer.rhino3d.com/en/guides/opennurbs/superfluous-knots/ This guide discusses superfluous knots and the openNURBS toolkit. ## Question How is the representation of knot vector in openNURBS different from that in OpenGL's NURBS renderer? In openNURBS, the formula is: $$m = n + p - 2 $$ where m is the number of knots in the knot vector; n is the number of control points; p is the order of the curve. While in OpenGL, the formula is: $$m = n + p$$ So, in OpenGL, there are two additional knots required to draw a NURBS curve. Could you explain how the two knot values are calculated? And, why openNURBS adopted a representation different from most NURBS books? ## Answer The knots are superfluous because they are not used in NURBS evaluation and they make it appear the first and last spans are different from interior spans. If you grab a pencil and paper and work through the details you will understand why dragging around and setting these two knot values is a waste of time and adds needless complications to NURBS evaluation, NURBS degree changing, NURBS knot insertion/deletion, NURBS span conversion to/from bezier, NURBS periodic closure, NURBS curve fitting, etc., algorithms. If you look at the openNURBS evaluation source code, you will see no knot juggling; it's just a simple computationally efficient calculation. One reason some mathematical texts on NURBS, like Carl DeBoor's b-splines, have the extra knots is that it makes proving the theorems easier because it means that the recursive b-spline basis function definition DeBoor uses is well defined for degree zero b-splines. DeBoor's use of the recursive definition of the basis functions is elegant and having the extra knot values along helps make them elegant. There is no good reason for modern computer code or text books focused solely on computer aided applications of NURBS to drag this kind of theoretical baggage along. Having modern code drag these knot values around is akin to having Excel store prime factors of integer cell entries and requiring Excel add-in developers to compute the factorization. Prime factorization is fundamental to understanding how integers work and it is interesting to people that it interests, but it is not required if all you are trying to do is add/subtract/multiply integers in spreadsheet cells. Why the creators of the OpenGL specification, IGES NURBS specification, and STEP NURBS specification decided to store two useless values in their knot vectors is a mystery to us. Rhino and openNURBS are not the only applications and toolkits that don't carry around the extra knots. The older versions of Alias and ACIS that we are familiar with do not have the extra knots. -------------------------------------------------------------------------------- # Supporting High DPI Displays Source: https://developer.rhino3d.com/en/guides/cpp/supporting-high-dpi-displays/ This guide discusses the support of high resolution monitors. ## Overview Super high resolution displays are now common on Windows-based systems, and those using Rhino expect it and 3rd party plugins to display correctly on them. Plugin developers need to make sure their applications are DPI–aware. DPI-aware plugins adjust UI elements to scale appropriately to the system DPI. Plugins that are not DPI–aware, but are running on a high-DPI display setting, can suffer from many visual artifacts, including incorrect scaling of UI elements, clipped text, and blurry images. Plugin developers should run Rhino on high-DPI displays so they can find and fix display issues. Here is how you can configure Windows for high-DPI display: ### Windows 10 1. Right-click on your desktop and click **Display settings**. 1. Use the slider to select the text scaling and click **Apply**. 1. Logout of Windows and log back in. ### Windows 8/8.1 1. Right-click on your desktop and click **Screen resolution**. 1. Click the **Make text and other items larger or smaller**. 1. Use the slider to select the text scaling and click **Apply**. 1. Logout of Windows and log back in. ### Windows 7 1. Right-click on your desktop and click **Screen resolution**. 1. Click **Make text and other items larger or smaller**. 1. Select the text scaling and click **Apply**. 1. Logout of Windows and log back in. ## Common C++ Issues Most high DPI issues that plugins will encounter are due to owner-drawn control. Here, developers have hard-coded sizes or locations, assuming the standard DPI setting. In these cases, custom drawn elements don’t appear correctly, or custom controls don’t work properly. Other issues have to do with the use of bitmaps or icons that are too small at higher DPI settings or that don’t scale well. The Rhino SDK has some new tools that developers can use to help make UI elements DPI-aware. See the ```CRhinoDpi``` class declaration in *RhinoSdkDpi.h* for more information. ### General The ```CRhinoDpi``` class contains several static function to help with DPI-aware issues. ```CRhinoDpi::DpiScale``` returns the display DPI scale factor when Rhino started. Use this value as a multiplier when owner drawing. ```CRhinoDpi::Scale``` scales a value by the current DPI scale factor. ### Icons Windows recommends that icon resources contain the following sizes: 16, 32, 48, and 256. All of the icon files (ICO) used by Rhino have been updated to support these sizes. When loading icons, use ```CRhinoDpi::LoadIcon```. For example: ```cpp virtual HICON Icon() { const int const_icon_size = 24; int icon_size = CRhinoDpi::Scale(const_icon_size); return CRhinoDpi::LoadIcon(AfxGetInstanceHandle(), IDI_ICON, icon_size); } ``` and: ```cpp virtual HICON Icon(const CSize& size) const { return CRhinoDpi::LoadIcon(AfxGetInstanceHandle(), IDI_ICON, size.cx, size.cy); } ``` Note, if the requested icon is not a standard size, the ```CRhinoDpi::LoadIcon``` function scales down a larger image. ### Cursors All of the cursors files (CUR) used by Rhino have been updated to support all of the sizes used by Windows 10. When loading cursor resources, you should so in this manner: ```cpp HCURSOR hCursor = (HCURSOR)::LoadImage( AfxGetInstanceHandle(), MAKEINTRESOURCE(IDC_CURSOR), IMAGE_CURSOR, 0, 0, LR_DEFAULTSIZE | LR_SHARED ); ``` By not specifying a size and by using the ```LR_DEFAULTSIZE``` flag, Windows will automatically load the cursor size that is appropriate for the current DPI setting. ### List Controls If you use a list control that displays item bitmaps, using an image lists, then you will find that the images do not scale properly. The solution is to convert the bitmap strip into individual icon files and then use ```CRhinoDpi::CreateImageList``` to create the image list. For list controls that use an image list that has a single bitmap, you can do this: ```cpp BOOL CMyDialog::OnInitDialog() { // ... const int size = CRhinoDpi::IconSize(CRhinoDpi::IconType::SmallIcon); CRhinoDpi::CreateImageList(m_image_list, AfxGetInstanceHandle(), IDI_MYICON, size); m_list_ctrl.SetImageList(&m_image_list, LVSIL_SMALL); // ... } ``` For an image list that contains multiple bitmaps, you can do this: ```cpp BOOL CMyDialog::OnInitDialog() { // ... const int size = CRhinoDpi::IconSize(CRhinoDpi::IconType::SmallIcon); unsigned int image_ids[4] = { IDI_MYICON0, IDI_MYICON1, IDI_MYICON2, IDI_MYICON3 }; CRhinoDpi::CreateImageList(m_image_list, AfxGetInstanceHandle(), 4, image_ids, size); m_list_ctrl.SetImageList(&m_image_list, LVSIL_SMALL); // ... } ``` ### List Boxes If you've derived your own list boxes, for the sake of owner drawing, then will need to make sure you are setting the item height for each item in the list. Otherwise, items will be scrunched together at higher DPI settings. Use ```CRhinoDpi::ListBoxItemHeight``` to determine this. ```cpp void CMyListBox::PreSubclassWindow() { // If the control was created with the LBS_OWNERDRAWFIXED style, // then this will set the item height for all items. if (GetStyle() & LBS_OWNERDRAWFIXED) { const int itemHeight = CRhinoDpi::ListBoxItemHeight(*(this)); SetItemHeight(0, itemHeight); } CListBox::PreSubclassWindow(); } void CMyListBox::MeasureItem(LPMEASUREITEMSTRUCT lpMeasureItemStruct) { // CListBox::MeasureItem is only called if the control was created // with the LBS_OWNERDRAWVARIABLE style. static int itemHeight = CRhinoDpi::ListBoxItemHeight(*(this)); if (nullptr != lpMeasureItemStruct) lpMeasureItemStruct->itemHeight = itemHeight; } ``` There is also a new ```CRhinoUiListBox``` control that you can use in place of ```CListBox```, that will perform the above calculations for you. See *RhinoSdkUiCheckListbox.h* for details. ### Check List Boxes The standard MFC ```CCheckListBox``` class does correctly handle mouse events at high DPI settings. If you are using this class, then modify your code to use ```CRhinoUiCheckListBox```. See *RhinoSdkUiCheckListbox.h* for details. ### Combo Boxes If you've derived your own combo boxes, for the sake of owner drawing, then will need to make sure you are setting the item height for each item in the list. Otherwise, items will be scrunched together at higher DPI settings. Use ```CRhinoDpi::ComboBoxItemHeight``` to determine this. ```cpp void CMyComboBox::PreSubclassWindow() { // If the control was created with the CBS_OWNERDRAWFIXED style, // then this will set the item height for all items. if (GetStyle() & CBS_OWNERDRAWFIXED) { const int itemHeight = CRhinoDpi::ComboBoxItemHeight(*(this)); // Set the height of all list items SetItemHeight(0, itemHeight); // Set the height of the edit-control or static-text portion (Optional) SetItemHeight(-1, itemHeight); } CComboBox::PreSubclassWindow(); } void CMyComboBox::MeasureItem(LPMEASUREITEMSTRUCT lpMeasureItemStruct) { // CComboBox::MeasureItem is only called if the control was created // with the CBS_OWNERDRAWVARIABLE style. static int itemHeight = CRhinoDpi::ComboBoxItemHeight(*(this)); if (nullptr != lpMeasureItemStruct) lpMeasureItemStruct->itemHeight = itemHeight; } ``` ### Check Boxes If you are owner-drawing check boxes, then there is a good chance their sizes are either hard-coded or they were obtained by calling an incorrect Windows API function. The ```CRhinoDpi::CheckBoxSize``` and ```CRhinoDpi::MenuCheckSize``` static functions will help with this. -------------------------------------------------------------------------------- # Supporting High DPI Displays Source: https://developer.rhino3d.com/en/guides/rhinocommon/supporting-high-dpi-displays/ This guide discusses the support of high resolution monitors. ## Overview Super high resolution displays are now common on Windows-based systems, and those using Rhino expect it and 3rd party plugins to display correctly on them. Plugin developers need to make sure their applications are DPI–aware. DPI-aware plugins adjust UI elements to scale appropriately to the system DPI. Plugins that are not DPI–aware, but are running on a high-DPI display setting, can suffer from many visual artifacts, including incorrect scaling of UI elements, clipped text, and blurry images. Plugin developers should run Rhino on high-DPI displays so they can find and fix display issues. Here is how you can configure Windows for high-DPI display: ### Windows 10 1. Right-click on your desktop and click **Display settings**. 1. Use the slider to select the text scaling and click **Apply**. 1. Logout of Windows and log back in. ### Windows 8/8.1 1. Right-click on your desktop and click **Screen resolution**. 1. Click the **Make text and other items larger or smaller**. 1. Use the slider to select the text scaling and click **Apply**. 1. Logout of Windows and log back in. ### Windows 7 1. Right-click on your desktop and click **Screen resolution**. 1. Click **Make text and other items larger or smaller**. 1. Select the text scaling and click **Apply**. 1. Logout of Windows and log back in. ## Common WinForms Issues Most high DPI issues that plugins will encounter are due to owner-drawn control. Here, developers have hard-coded sizes or locations, assuming the standard DPI setting. In these cases, custom drawn elements don’t appear correctly, or custom controls don’t work properly. Other issues have to do with the use of bitmaps or icons that are too small at higher DPI settings or that don’t scale well. But WinForms has other challenges too. One observation is that if you spend too much time using the Forms designer, hard-coding font sizes and other values, will have DPI display issues. ### General WinForms has its own scaling mechanism which calculates the scaling difference between the system that the form has been designed on and the system it is running on. Thus, **always design your forms at default 96 DPI and then test at higher DPI settings**. All container controls must be set to the same ```AutoScaleMode = Font```. This will handle both DPI changes and changes to the system font size setting; DPI will only handle DPI changes, not changes to the system font size setting. Make sure you **use the default font size*** on all your containers (forms, panels, tab page, user controls etc). 8.25 px. Preferably this should not be set in the *\.Designer.cs* file at all for all containers so that it uses the default font from the container class. All container controls must also be set with ```AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F)```, assuming 96 DPI, in the *\.Designer.cs* file. If you need to set different font sizes on labels, textboxes, etc. set them per control instead of setting the font on the container class because WinForms uses the containers font setting to scale it's contents. Design the interface of your forms using **Anchored**, **Docked**, **AutoSized** controls where possible. Using the built-in layout controls (**FlowLayoutPanel** and **TableLayoutPanel**) can also be helpful. If you have some custom layout logic, always keep in mind that the sizes and the locations of the controls will be different if the form is scaled. Also keep in mind that you should manually scale any constants you use if they denote pixels. ### Rhino-Specific The ```RhinoWindows``` assembly has a new ```RhinoWindows.Forms.Dpi``` class that is similar to the ```CRhinoDpi``` class in the Rhino C++ SDK. The class contains static functions to return the current DPI scale factor and to scale values per the current DPI scale factor. For example: ```cs /// /// MainWindow Constructor /// public MainWindow() { // This call is required by the Windows Form Designer. InitializeComponent(); var width = RhinoWindows.Forms.Dpi.ScaleInt(m_toolbar.ImageScalingSize.Width); var height = RhinoWindows.Forms.Dpi.ScaleInt(m_toolbar.ImageScalingSize.Height); m_toolbar.ImageScalingSize = new Size(width, height); // ... ``` Use the ```DrawingUtilities::LoadBitmapWithScaleDown``` and ```DrawingUtilities::LoadIconWithScaleDown``` static functions, found in the ```Rhino.UI``` assembly, to assist with loading bitmaps and icons from icon resources. For example: ```cs using Rhino.UI; //... var size = RhinoWindows.Forms.Dpi.ScaleInt(32); using (var icon = DrawingUtilities.LoadIconWithScaleDown("logo.ico", (int)size, GetType().Assembly)) m_image = icon.ToBitmap(); // ... ``` If the icon is not a standard size, ```DrawingUtilities::LoadIconWithScaleDown``` scales down a larger image. -------------------------------------------------------------------------------- # Surface from Control Points Source: https://developer.rhino3d.com/en/samples/cpp/surface-from-control-points/ Demonstrates how to create a surface from a grid of control points. ```cpp CRhinoCommand::result CCommandTest::RunCommand( const CRhinoCommandContext& context ) { // Degree 3 surface int degree[2]; degree[0] = 3; degree[1] = 3; // Four columns of four points int point_count[2]; point_count[0] = 4; point_count[1] = 4; ON_3dPointArray point_array( point_count[0] * point_count[1] ); point_array.Append( ON_3dPoint(0,0,0) ); point_array.Append( ON_3dPoint(0,3.33,0) ); point_array.Append( ON_3dPoint(0,6.67,0) ); point_array.Append( ON_3dPoint(0,10,0) ); point_array.Append( ON_3dPoint(6.68,0,-0.0296) ); point_array.Append( ON_3dPoint(6.68,3.33,-0.0296) ); point_array.Append( ON_3dPoint(6.68,6.67,-0.0296) ); point_array.Append( ON_3dPoint(6.68,10,-0.0296) ); point_array.Append( ON_3dPoint(13.3,0,2.77) ); point_array.Append( ON_3dPoint(13.3,3.33,2.77) ); point_array.Append( ON_3dPoint(13.3,6.67,2.77) ); point_array.Append( ON_3dPoint(13.3,10,2.77) ); point_array.Append( ON_3dPoint(17.9,0,7.58) ); point_array.Append( ON_3dPoint(17.9,3.33,7.58) ); point_array.Append( ON_3dPoint(17.9,6.67,7.58) ); point_array.Append( ON_3dPoint(17.9,10,7.58) ); ON_NurbsSurface srf; if( RhinoSrfControlPtGrid( point_count, degree, point_array, &srf) ) { context.m_doc.AddSurfaceObject( srf ); context.m_doc.Redraw(); } return success; } ``` -------------------------------------------------------------------------------- # Surface from Corner Points Source: https://developer.rhino3d.com/en/samples/rhinocommon/surface-from-corner-points/ Demonstrates how to create a surface from a set of corner points. ```cs partial class Examples { public static Result SurfaceFromCorners(RhinoDoc doc) { var surface = NurbsSurface.CreateFromCorners( new Point3d(5, 0, 0), new Point3d(5, 5, 5), new Point3d(0, 5, 0), new Point3d(0, 0, 0)); doc.Objects.AddSurface(surface); doc.Views.Redraw(); return Rhino.Commands.Result.Success; } } ``` ```python from Rhino.Geometry import NurbsSurface, Point3d from scriptcontext import doc surface = NurbsSurface.CreateFromCorners( Point3d(5, 0, 0), Point3d(5, 5, 5), Point3d(0, 5, 0), Point3d(0, 0, 0)); doc.Objects.AddSurface(surface); doc.Views.Redraw(); ``` -------------------------------------------------------------------------------- # Surface from Edge Curves Source: https://developer.rhino3d.com/en/samples/rhinocommon/surface-from-edge-curves/ Create a Surface from Edge Curves ```cs partial class Examples { public static Result EdgeSrf(RhinoDoc doc) { var go = new GetObject(); go.SetCommandPrompt("Select 2, 3, or 4 open curves"); go.GeometryFilter = ObjectType.Curve; go.GeometryAttributeFilter = GeometryAttributeFilter.OpenCurve; go.GetMultiple(2, 4); if (go.CommandResult() != Result.Success) return go.CommandResult(); var curves = go.Objects().Select(o => o.Curve()); var brep = Brep.CreateEdgeSurface(curves); if (brep != null) { doc.Objects.AddBrep(brep); doc.Views.Redraw(); } return Result.Success; } } ``` ```python from Rhino import * from Rhino.Commands import * from Rhino.DocObjects import * from Rhino.Geometry import * from Rhino.Input.Custom import * from scriptcontext import doc def RunCommand(): go = GetObject() go.SetCommandPrompt("Select 2, 3, or 4 open curves") go.GeometryFilter = ObjectType.Curve go.GeometryAttributeFilter = GeometryAttributeFilter.OpenCurve go.GetMultiple(2, 4) if go.CommandResult() != Result.Success: return go.CommandResult() curves = [o.Curve() for o in go.Objects()] brep = Brep.CreateEdgeSurface(curves) if brep != None: doc.Objects.AddBrep(brep) doc.Views.Redraw() return Result.Success if __name__ == "__main__": RunCommand() ``` -------------------------------------------------------------------------------- # Sweep Surfaces with Sweep1 Source: https://developer.rhino3d.com/en/samples/rhinocommon/sweep-surfaces-with-sweep1/ Demonstrates how to sweep along a single rail curve. ```cs partial class Examples { public static Rhino.Commands.Result Sweep1(Rhino.RhinoDoc doc) { Rhino.DocObjects.ObjRef rail_ref; var rc = RhinoGet.GetOneObject("Select rail curve", false, Rhino.DocObjects.ObjectType.Curve, out rail_ref); if(rc!=Rhino.Commands.Result.Success) return rc; var rail_crv = rail_ref.Curve(); if( rail_crv==null ) return Rhino.Commands.Result.Failure; var gx = new Rhino.Input.Custom.GetObject(); gx.SetCommandPrompt("Select cross section curves"); gx.GeometryFilter = Rhino.DocObjects.ObjectType.Curve; gx.EnablePreSelect(false, true); gx.GetMultiple(1,0); if( gx.CommandResult() != Rhino.Commands.Result.Success ) return gx.CommandResult(); var cross_sections = new List(); for( int i=0; iIsClosed(); args.m_bUsePivotPoint = false; int i; for( i = 0; i < gx.ObjectCount(); i++ ) { const CRhinoObjRef& obj_ref = gx.Object(i); const ON_Curve* crv = obj_ref.Curve(); if( crv ) { ON_Curve* dup_crv = crv->DuplicateCurve(); double t = 0; edge.GetClosestPoint( dup_crv->PointAtStart(), &t ); args.m_shape_curves.Append( dup_crv ); args.m_rail_params.Append( t ); args.m_shape_objrefs.Append( obj_ref ); } } // Start and end points args.m_bUsePoints[0] = 0; args.m_bUsePoints[1] = 0; // Point objects picked for endpoints args.m_bClosed = false; args.m_style = 0; args.m_planar_up = ON_zaxis; // Don't need this, but set it anyway.. args.m_simplify = 0; // Simplify method for shape curves args.m_rebuild_count = -1; // Sample point count for rebuilding shapes args.m_refit_tolerance = context.m_doc.AbsoluteTolerance(); args.m_sweep_tolerance = context.m_doc.AbsoluteTolerance(); args.m_angle_tolerance = context.m_doc.AngleToleranceRadians(); args.m_miter_type = 0; // 0: don't miter ON_SimpleArray breps; if( RhinoSweep1(args, breps) ) { for( i = 0; i < breps.Count(); i++ ) { context.m_doc.AddBrepObject( *breps[i] ); delete breps[i]; } } // Clean up delete args.m_rail_curve; for( i = 0; i < args.m_shape_curves.Count(); i++ ) delete args.m_shape_curves[i]; context.m_doc.Redraw(); return success; } ``` -------------------------------------------------------------------------------- # Sweeping Surfaces with Sweep2 Source: https://developer.rhino3d.com/en/samples/cpp/sweeping-surfaces-with-sweep2/ Demonstrates how to use the CArgsRhinoSweep2 class and the RhinoSweep2 function. The definitions of these can be found in rhinoSdkSweep.h. ```cpp // Helper function to calculate rail parameters bool GetShapeParameterOnRail( const ON_Curve& shape, const ON_Curve& rail, double tol, double& t ) { ON_Interval rail_domain = rail.Domain(); // Test start point bool rc = rail.GetClosestPoint( shape.PointAtStart(), &t, tol ); if( rc ) { if( rail.IsClosed() ) { if( fabs(t - rail_domain.Max()) < ON_SQRT_EPSILON ) t = rail_domain.Min(); if( fabs(t - rail_domain.Min()) < ON_SQRT_EPSILON ) t = rail_domain.Min(); } return true; } // Test end point rc = rail.GetClosestPoint( shape.PointAtEnd(), &t, tol ); if( rc ) { if( rail.IsClosed() ) { if( fabs(t - rail_domain.Max()) < ON_SQRT_EPSILON ) t = rail_domain.Min(); if( fabs(t - rail_domain.Min()) < ON_SQRT_EPSILON ) t = rail_domain.Min(); } return true; } // Try intersecting... ON_SimpleArray x; if( 1 == rail.IntersectCurve(&shape, x, tol, 0.0) && x[0].IsPointEvent() ) { t = x[0].m_a[0]; if( rail.IsClosed() ) { if( fabs(t - rail_domain.Max()) < ON_SQRT_EPSILON ) t = rail_domain.Min(); } return true; } return false; } // RhinoSweep2 wrapper function bool MySweep2( const ON_Curve* Rail1, const CRhinoObject* Rail1_obj, const ON_Curve* Rail2, const CRhinoObject* Rail2_obj, ON_SimpleArray sCurves, ON_SimpleArray& Sweep2_Breps ) { if( 0 == Rail1 | 0 == Rail1_obj ) return false; if( 0 == Rail2 | 0 == Rail2_obj ) return false; CRhinoDoc* doc = RhinoApp().ActiveDoc(); if( 0 == doc ) return false; // Define a new class that contains sweep2 arguments CArgsRhinoSweep2 args; // Set the 2 rails CRhinoPolyEdge Edge1, Edge2; Edge1.Create( Rail1, Rail1_obj ); Edge2.Create( Rail2, Rail2_obj ); // Ok, we can at least do some rail direction matching if( !RhinoDoCurveDirectionsMatch(&Edge1, &Edge2) ) Edge2.Reverse(); // Add rails to sweep arguments args.m_rail_curves[0] = &Edge1; args.m_rail_curves[1] = &Edge2; args.m_rail_pick-points[0] = ON_UNSET_POINT; args.m_rail_pick-points[1] = ON_UNSET_POINT; // To create a closed sweep, you need to have: // 1. Two closed rail curves and and a single shape curve, or // 2. Set CArgsRhinoSweep2::m_bClosed = true args.m_bClosed = false; double tol = doc->AbsoluteTolerance(); // Loop through sections to set parameters for( int i = 0; i < sCurves.Count(); i++ ) { const ON_Curve* sCurve = sCurves[i]; if( 0 == sCurve ) continue; // Add to shapes args.m_shape_curves.Append( sCurve ); // Cook up some rail parameters double t0 = 0.0; if( !GetShapeParameterOnRail(*sCurve, Edge1, tol, t0) ) return false; args.m_rail_params[0].Append( t0 ); double t1 = 0.0; if( !GetShapeParameterOnRail(*sCurve, Edge2, tol, t1) ) return false; args.m_rail_params[1].Append( t1 ); } // Set the rest of parameters args.m_simplify = 0; args.m_bSimpleSweep = false; args.m_bSameHeight = false; args.m_rebuild_count = -1; //Sample point count for rebuilding shapes args.m_refit_tolerance = tol; args.m_sweep_tolerance = tol; args.m_angle_tolerance = doc->AngleToleranceRadians(); // Sweep2 return RhinoSweep2(args, Sweep2_Breps) ? true : false; } // Test command that uses the above functions. CRhinoCommand::result CCommandTest::RunCommand( const CRhinoCommandContext& context ) { // Select first rail curve CRhinoGetObject go1; go1.SetCommandPrompt( L"Select first rail curve" ); go1.SetGeometryFilter( CRhinoGetObject::curve_object ); go1.EnablePreSelect( false ); go1.GetObjects( 1, 1 ); if( go1.CommandResult() != success ) return go1.CommandResult(); const CRhinoObject* Rail1_obj = go1.Object(0).Object(); const ON_Curve* Rail1 = go1.Object(0).Curve(); if( 0 == Rail1_obj | 0 == Rail1 ) return failure; // Select second rail curve CRhinoGetObject go2; go2.SetCommandPrompt( L"Select second rail curve" ); go2.SetGeometryFilter( CRhinoGetObject::curve_object ); go2.EnablePreSelect( false ); go2.EnableDeselectAllBeforePostSelect( false ); go2.GetObjects( 1, 1 ); if( go2.CommandResult() != success ) return go2.CommandResult(); const CRhinoObject* Rail2_obj = go2.Object(0).Object(); const ON_Curve* Rail2 = go2.Object(0).Curve(); if( 0 == Rail2_obj | 0 == Rail2 ) return failure; // Select cross section curves CRhinoGetObject gx; gx.SetCommandPrompt( L"Select cross section curves" ); gx.SetGeometryFilter( CRhinoGetObject::curve_object ); gx.EnablePreSelect( false ); gx.EnableDeselectAllBeforePostSelect( false ); gx.GetObjects( 1, 0 ); if( gx.CommandResult() != success ) return gx.CommandResult(); ON_SimpleArray sCurves; int i; for( i = 0; i < gx.ObjectCount(); i++ ) { const ON_Curve* sCurve = gx.Object(i).Curve(); if( sCurve ) sCurves.Append( sCurve ); } // Call MySweep2 function ON_SimpleArray Sweep2_Breps; if( MySweep2(Rail1, Rail1_obj, Rail2, Rail2_obj, sCurves, Sweep2_Breps) ) { // Add to context then delete for( i = 0; i < Sweep2_Breps.Count(); i++ ) { ON_Brep* brep = Sweep2_Breps[i]; if( brep ) { context.m_doc.AddBrepObject( *brep ); delete Sweep2_Breps[i]; // Don't leak... Sweep2_Breps[i] = 0; } } context.m_doc.Redraw(); } return success; } ``` -------------------------------------------------------------------------------- # Synchronize Layer Render Color Source: https://developer.rhino3d.com/en/samples/cpp/synchronize-layer-render-color/ Demonstrates how to synchronize the basic material color of a layer with the layer's color. ```cpp CRhinoCommand::result CCommandTest::RunCommand(const CRhinoCommandContext& context) { CRhinoLayerTable& layer_table = context.m_doc.m_layer_table; CRhinoMaterialTable& material_table = context.m_doc.m_material_table; int num_modified = 0; int i, layer_count = layer_table.LayerCount(); for( i = 0; i < layer_count; i++ ) { const CRhinoLayer& layer = layer_table[i]; if( layer.IsDeleted() | layer.IsReference() ) continue; int material_index = layer.RenderMaterialIndex(); if( material_index < 0 ) { // If material_index < 0, then the layer does not have a // material assigned to it. So, we will create a new material // that is based on Rhino's default material, and add it // to the material table. ON_Material material( RhinoApp().AppSettings().DefaultMaterial() ); material_index = material_table.AddMaterial( material ); if( material_index >= 0 ) { // Now that we have added the new material, // assign it to the layer. ON_Layer new_layer( layer ); new_layer.SetRenderMaterialIndex( material_index ); layer_table.ModifyLayer( new_layer, layer.LayerIndex() ); } } if( material_index < 0 ) continue; const CRhinoMaterial& material = material_table[material_index]; if( layer.Color() == material.Diffuse() ) continue; // Modify the material's basic, or diffuse, color ON_Material new_material( material ); new_material.SetDiffuse( layer.Color() ); material_table.ModifyMaterial( new_material, material.MaterialIndex(), FALSE ); num_modified++; } if( num_modified > 0 ) context.m_doc.Regen(); return CRhinoCommand::success; } ``` -------------------------------------------------------------------------------- # Synchronize Object Render Color Source: https://developer.rhino3d.com/en/samples/cpp/synchronize-object-render-color/ Demonstrates how to synchronize the basic material color of an object with the object's display color. ```cpp CRhinoCommand::result CCommandTest::RunCommand( const CRhinoCommandContext& context ) { int num_modified = 0; CRhinoObjectIterator it( CRhinoObjectIterator::normal_objects, CRhinoObjectIterator::active_objects ); it.IncludeLights( FALSE ); CRhinoObject* obj = 0; for( obj = it.First(); obj; obj = it.Next() ) { // If the object gets its color from its layer, then we will // let the layer handle its material color as well. ON::object-color_source color_source = obj->Attributes().ColorSource(); if( color_source != ON::color_from_object ) continue; int material_index = obj->Attributes().m_material_index; if( material_index < 0 ) { // If material_index < 0, then the object does not have a // material assigned to it. So, we will create a new material // that is based on Rhino's default material, and add it // to the material table. ON_Material material( RhinoApp().AppSettings().DefaultMaterial() ); material_index = context.m_doc.m_material_table.AddMaterial( material ); if( material_index >= 0 ) { // Now that we have added the new material, // assign it to the object. CRhinoObjectAttributes new_attributes( obj->Attributes() ); new_attributes.m_material_index = material_index; // Make sure to set the material source to "from object" new_attributes.SetMaterialSource( ON::material_from_object ); obj->ModifyAttributes( new_attributes ); } } if( material_index < 0 ) continue; const CRhinoMaterial& material = context.m_doc.m_material_table[material_index]; if( obj->Attributes().DrawColor() == material.Diffuse() ) continue; // Modify the material's basic,or diffuse, color ON_Material new_material( material ); new_material.SetDiffuse( obj->Attributes().DrawColor() ); context.m_doc.m_material_table.ModifyMaterial( new_material, material.MaterialIndex(), FALSE ); num_modified++; } if( num_modified > 0 ) context.m_doc.Regen(); return CRhinoCommand::success; } ``` -------------------------------------------------------------------------------- # Tabbed Panels Source: https://developer.rhino3d.com/en/guides/rhinocommon/tabbed-panels/ Discusses creating tabbed panels in RhinoCommon plugins. ## Overview Many Rhino controls are contained in tabbed, dockable panels. You can create tabbed panels in a plug-in using WinForms(Windows), WPF (Windows) or Eto (Windows and Mac). You can find a sample that demonstrate how to create tabbed panels in the [Developer Samples repository](https://github.com/mcneel/rhino-developer-samples) on GitHub. ## Details The tabbed panel systems work differently Rhino 6 (and later) than it did in Rhino 5 and earlier. ### Old Rhino 5 for Windows Behavior - The [registered](https://developer.rhino3d.com/api/RhinoCommon/html/M_Rhino_UI_Panels_RegisterPanel.htm) panel class must be a ```public``` class and contain a default constructor that takes no parameters. - A panel instance is created the first time it is referenced by a visible host container and will survive for the length of a Rhino session. - When a panel is moved to a different host container, the panels parent is changed to the new host and the same panel instance is used. ### Current Rhino for Windows Behavior (Per-Document Panels) - The [registered](https://developer.rhino3d.com/api/RhinoCommon/html/M_Rhino_UI_Panels_RegisterPanel_1.htm) panel class must be a ```public``` class and registered as a ```PanelType.PerDoc``` panel (the default [PanelType](https://developer.rhino3d.com/api/RhinoCommon/html/T_Rhino_UI_PanelType.htm)). - Rhino looks for public constructors in the following order: - Constructor taking a ```RhinoDoc```. - Constructor taking a ```uint``` representing the documents runtime serial number. - Constructor with no arguments. - A panel instance is created each time a new document is created, if the panel is in a visible host container, and disposed of when the document closes. - When a panel is moved to a different host container, the panels parent is changed to the new host and the same panel instance is used. ### Current Rhino for Windows Behavior (System Panels) - The [registered](https://developer.rhino3d.com/api/RhinoCommon/html/M_Rhino_UI_Panels_RegisterPanel_1.htm) panel class must be a ```public``` class and registered as a ```PanelType.System``` panel. - Rhino looks for public constructors in the following order: - Constructor taking a ```RhinoDoc```. - Constructor taking a ```uint``` representing the documents runtime serial number. - Constructor with no arguments. - A panel instance is created the first time it is referenced by a visible host container and will survive for the length of a Rhino session. - When a panel is moved to a different host container, the panels parent is changed to the new host and the same panel instance is used. ### Current Rhino for Mac Behavior Rhino for Mac works the same as Rhino for Windows, with the exception that there will be a panel instance per inspector panel that includes the plug-in panel. ## More Information Rhino includes a [IPanel](https://developer.rhino3d.com/api/RhinoCommon/html/T_Rhino_UI_IPanel.htm) interface which can be implemented to determine when a panel is shown, hidden or closed. Static events on ```RhinoDoc``` should be hooked by your panel class when created and unhooked when disposed of in Rhino. -------------------------------------------------------------------------------- # Tag Object As Flamingo Plant Source: https://developer.rhino3d.com/en/samples/rhinoscript/tag-object-as-flamingo-plant/ Demonstrates how to tag an object as a Flamingo nXt plant in RhinoScript. ```vbnet Option Explicit Call Main() Sub Main() Dim objPlugIn, plantFile, strObject, topiary, location, plant On Error Resume Next Set objPlugIn = Rhino.GetPluginObject("8008880f-8d13-4b2d-92b0-727e12878a4c") If Err Then MsgBox Err.Description Exit Sub End If plantFile = objPlugIn.GetPlantFileName("") If Not IsNull(plantFile) Then strObject = Rhino.GetObject("Select object to tag as plant") If Not IsNull(strObject) And Not strObject = "" Then topiary = False location = Array(0.0, 0.0, 0.0) If objPlugIn.PlantFileNameIsGroundcover(plantFile) Then topiary = Rhino.GetBoolean("Plant topiary", Array( "Topiary", "Off", "On"), Array(topiary)) Else location = Rhino.GetPoint("Plant location") End If If Not IsNull(location) And Not IsNull(topiary) Then Set plant = objPlugIn.NewPlant() plant.FileName = plantFile plant.Location = location plant.Topiary = topiary If objPlugIn.TagObjectAsPlant(strObject, Plant) Then Rhino.Print("Plant " & plantName & " assigned to selected object") End If Set plant = Nothing End If End If End If Set objPlugIn = Nothing End Sub ``` -------------------------------------------------------------------------------- # Test if an Object is a Circle Source: https://developer.rhino3d.com/en/samples/cpp/test-if-object-is-circle/ Demonstrates how to test if an object looks like a circle. ```cpp bool IsCircle( const CRhinoObject* obj ) { bool rc = false; if( obj ) { // Is the object a circle? if( const ON_ArcCurve* arc = ON_ArcCurve::Cast(obj->Geometry()) ) { if( arc->IsCircle() ) rc = true; } // Is the object an curve that just looks like a circle? else if( const ON_Curve* crv = ON_Curve::Cast(obj->Geometry()) ) { ON_NurbsCurve nurb; if( crv->GetNurbForm(nurb) ) { ON_Arc arc; double tol = ::RhinoApp().ActiveDoc()->AbsoluteTolerance(); if( nurb.IsArc(0, &arc, tol) && arc.IsCircle() ) rc = true; } } } return rc; } ``` -------------------------------------------------------------------------------- # Testing for Curves on Surfaces Source: https://developer.rhino3d.com/en/guides/cpp/testing-for-curves-on-surfaces/ This guide discusses how to test to see if a curve lies on a surface using C/C++. ## Problem In your plugin, you are using a surface and an interpolated curve on that surface. You know the curve is interpolated on the surface because that is how you created it (using the *InterpCrfOnSrf* command). Now, what if you import some other 3dm file with curve and surface already in it? How can one check that curve lies completely on surface using C/C++? ## Solution The Rhino C/C++ SDK does not have a function that tests whether or not a curve lies on a surface. But, you can write your own test that should give you the correct answer for most cases. The best approach for writing a function to do this would be to sample many curve points and perform closest point tests against the surface with each curve point. If the distance between the curve points and the surface points are within some tolerance, then chances are the curve is on the surface - at least the sampled points are on the surface. ## Sample The following sample function does just this: ```cpp // Description: // Test to see if a curve lies on a surface. // Parameters: // srf - [in] The surface // crv - [in] The curve // tol - [in] The tolerance // Returns: // True if the curve (probably) lies on the surface. // False otherwise. static bool RhinoIsCurveOnSurface( const ON_Surface& srf, const ON_Curve& crv, double tol ) { if( !srf.IsValid() | !crv.IsValid() ) return false; ON_NurbsCurve nc; if( !crv.GetNurbForm(nc) ) return false; const int span_count = nc.SpanCount(); ON_SimpleArray span_vector( span_count + 1 ); span_vector.SetCount( span_count + 1 ); nc.GetSpanVector( span_vector.Array() ); bool rc = true; double num_samples = nc.Degree() * 2; int i, j; for( i = 0; i < span_count && rc; i++ ) { ON_Interval span( span_vector[i], span_vector[i+1] ); rc = span.IsIncreasing(); if( rc ) { for( j = 0; j <= num_samples && rc; j++ ) { double crv_t = span.ParameterAt( j / num_samples ); ON_3dPoint crv_pt = nc.PointAt( crv_t ); double s = 0.0, t = 0.0; rc = srf.GetClosestPoint( crv_pt, &s, &t ); if( rc ) { ON_3dPoint srf_pt = srf.PointAt( s, t ); rc = ( fabs(crv_pt.DistanceTo(srf_pt)) <= tol ); } } } } return rc; } ``` -------------------------------------------------------------------------------- # Testing for Empty Arrays Source: https://developer.rhino3d.com/en/guides/rhinoscript/testing-for-empty-arrays/ This guide discusses how to determine a VBScript array is empty. ## Problem It is often necessary to analyze an array to determine whether or not it is empty; that is, if it has any space to store elements. Consider the following test... ```vbnet Sub Main() Dim arr arr = Array() If IsArray(arr) Then Rhino.Print("This should not print") End If End Sub ``` ...which does not seem to work. ## Solution When you execute this statement: ```vbnet arr = Array() ``` you are declaring an array that has an upper bounds of -1. Because this variable is an array, it will pass the `IsArray()` test. What the above code needs is an additional test condition to see if the upper bounds of the array is greater than -1: ```vbnet Sub Main() Dim arr arr = Array() If IsArray(arr) And UBound(arr) >= 0 Then Rhino.Print("This should not print") End If End Sub ``` Now the code works as expected. But, what if the code looked like this? ```vbnet Sub Main() Dim arr() If IsArray(arr) And UBound(arr) >= 0 Then Rhino.Print("This should not print") End If End Sub ``` Notice that the above code gives you an "Script out of range: UBound" error. This is because, although the variable is an array, it has not been dimensioned. Thus the call to `UBound()` fails. So, we need a better test - one that will test for both types of array declarations. Consider the following function: ```vbnet Function IsArrayDimmed(arr) IsArrayDimmed = False If IsArray(arr) Then On Error Resume Next Dim ub : ub = UBound(arr) If (Err.Number = 0) And (ub >= 0) Then IsArrayDimmed = True End If End Function ``` Notice how the function above provides error checking. If an error occurs when calling `UBound()`, it is caught. Thus, the function knows when an array has not been dimensioned. Also, if `UBound()` returns a value of -1, we know that the array has no space to store elements. We can test this with the following function: ```vbnet Sub Main() Dim arr0 Dim arr1() Dim arr2(3) Dim arr3 : arr3 = Array() Dim arr4 : arr4 = Array(1,2,3) Rhino.Print "Arr0 dimmed = " & IsArrayDimmed(arr0) Rhino.Print "Arr1 dimmed = " & IsArrayDimmed(arr1) Rhino.Print "Arr2 dimmed = " & IsArrayDimmed(arr2) Rhino.Print "Arr3 dimmed = " & IsArrayDimmed(arr3) Rhino.Print "Arr4 dimmed = " & IsArrayDimmed(arr4) End Sub ``` -------------------------------------------------------------------------------- # Testing for Object Visibility Source: https://developer.rhino3d.com/en/guides/opennurbs/testing-object-visibility/ This guide demonstrates how to detect whether or not an object is visible using openNURBS. ## Question I have created a sample model this has a parent layer and a sublayer. If I add objects to each of these two layer and then turn off the parent layer in Rhino, the objects on both layers do not appear. But, when I read the .3dm file using openNURBS, the objects on the sublayer report as being visible. How can I correctly detect the visibility of an object? ## Answer A Rhino object is considered visible if: 1. The object's mode is not set to hidden. 1. If the object's layer is not hidden. 1. If the object's layer does not have a parent layer that is hidden. ## Example The following example code can be used to detect an object's true visibility when using the openNURBS toolkit: ```cpp static bool IsLayerVisible(const ONX_Model& model, ON_UUID layer_id) { bool rc = false; const ON_ModelComponentReference& model_component_ref = model.ComponentFromId(ON_ModelComponent::Type::Layer, layer_id); if (!model_component_ref.IsEmpty()) { const ON_Layer* layer = ON_Layer::Cast(model_component_ref.ModelComponent()); if (nullptr != layer) { rc = layer->IsVisible(); if (rc && layer->ParentIdIsNotNil()) return IsLayerVisible(model, layer->ParentId()); } } return rc; } static bool IsLayerVisible(const ONX_Model& model, int layer_index) { bool rc = false; const ON_ModelComponentReference& model_component_ref = model.ComponentFromIndex(ON_ModelComponent::Type::Layer, layer_index); if (!model_component_ref.IsEmpty()) { const ON_Layer* layer = ON_Layer::Cast(model_component_ref.ModelComponent()); if (nullptr != layer) { rc = layer->IsVisible(); if (rc && layer->ParentIdIsNotNil()) return IsLayerVisible(model, layer->ParentId()); } } return rc; } static bool IsModelGeometryVisible(const ONX_Model& model, const ON_ModelGeometryComponent* model_geometry) { bool rc = false; if (nullptr != model_geometry) { const ON_3dmObjectAttributes* attributes = model_geometry->Attributes(nullptr); if (nullptr != attributes) { switch (attributes->Mode()) { case ON::normal_object: case ON::idef_object: case ON::locked_object: rc = IsLayerVisible(model, attributes->m_layer_index); break; } } } return rc; } ``` You can test the above static functions by adding the following sample code to the *Example_Read* project included with the openNURBS toolkit: ```cpp ONX_Model model = ...; ONX_ModelComponentIterator it(model, ON_ModelComponent::Type::ModelGeometry); const ON_ModelComponent* model_component = nullptr; for (model_component = it.FirstComponent(); nullptr != model_component; model_component = it.NextComponent()) { const ON_ModelGeometryComponent* model_geometry = ON_ModelGeometryComponent::Cast(model_component); if (nullptr != model_geometry) { bool bVisible = IsModelGeometryVisible(model, model_geometry); if (bVisible) { // TODO... } } } ``` -------------------------------------------------------------------------------- # Text Justify Source: https://developer.rhino3d.com/en/samples/rhinocommon/text-justify/ Demonstrates how to set text justification with a specific font. ```cs partial class Examples { public static Result TextJustify(RhinoDoc doc) { var text_entity = new TextEntity { Plane = Plane.WorldXY, Text = "Hello Rhino!", Justification = TextJustification.MiddleCenter, FontIndex = doc.Fonts.FindOrCreate("Arial", false, false) }; doc.Objects.AddText(text_entity); doc.Views.Redraw(); return Result.Success; } } ``` ```python from scriptcontext import doc from Rhino.Geometry import * text_entity = TextEntity() text_entity.Plane = Plane.WorldXY text_entity.Text = "Hello Rhino!" text_entity.Justification = TextJustification.MiddleCenter text_entity.FontIndex = doc.Fonts.FindOrCreate("Arial", False, False) doc.Objects.AddText(text_entity) doc.Views.Redraw() ``` -------------------------------------------------------------------------------- # The Anatomy of a Package Source: https://developer.rhino3d.com/en/guides/yak/the-anatomy-of-a-package/ This guide explains the structure of a Yak package. ## Package Structure Packages are simply ZIP archives with a .yak extension. Take this simple example... ``` howler-0.4.0-any-any.yak ├── manifest.yml ├── Howler.rhp ├── Howler.rui ├── HowlerCommon.dll ├── HowlerGrasshopper.gha └── misc/ ├── README.md └── LICENSE.txt ``` ### A note on .NET multi-targeting From Rhino 8 onwards, Yak also supports multi-targeted applications so that your Rhino Plugin can be run in either dotnet core or dotnet framework. Note that the `manifest.yml` must now be outside the framework directory, rather than inside of it. ``` howler-0.4.0-rh8-any.yak ├── manifest.yml ├── net48/ │ ├── Howler.rhp │ ├── Howler.rui │ ├── HowlerCommon.dll │ ├── HowlerGrasshopper.gha │ └── misc/ │ ├── README.md │ └── LICENSE.txt └── net7.0/ ├── Howler.rhp ├── Howler.rui ├── HowlerCommon.dll ├── HowlerGrasshopper.gha └── misc/ ├── README.md └── LICENSE.txt ``` ## Requirements 1. Packages **must** have a `manifest.yml` file in the top-level directory. Details about the manifest can be found in the [Manifest Reference Guide](../the-package-manifest). 1. Any plug-ins (`.rhp`, `.gha`, `.ghpy` files) **must** be in the top-level directory, or a [multi-targeting directory](#a-note-on-net-multi-targeting), so that Rhino and Grasshopper can find and load them. 1. Package version numbers **must** either follow [Semantic Versioning 2.0.0](http://semver.org/spec/v2.0.0.html) (e.g. `1.1.0-beta`) or `System.Version` a.k.a. Microsoft's four-digit standard (e.g. `1.2.3.4`). It's recommended to use Semantic Versioning because it allows package authors to specify prerelease versions. These are handy for limited testing, since by default the latest _stable_ version is installed. ## Distributions For a single package version it's possible to upload multiple "distributions" to target different Rhino versions and platforms. This information is encoded in a "distribution tag" that is appended to the filename of the package, e.g. _example-1.0.0-rh7-win.yak_. The distribution tag consists of an "app" identifier and version, and a platform. Currently the only supported apps are `rh` and `any` – Grasshopper ships with Rhino so it doesn't need its own identifier. Unless the app is `any`, an app version must be included in the form `_`. The minor version is optional and is useful if a plug-in relies on an SDK change made in a service release. The platform can be `win`, `mac` or `any` (i.e. cross-platform). A few examples... * `rh7-win` - Rhino 7 for Windows >= 7.0 * `rh6_14-mac` - Rhino 6 for Mac >= 6.14 * `rh6_9-any` - Rhino 6 (both platforms) >= 6.9 * `any-any` - anything goes! (existing behaviour) When installing packages, the package manager checks whether a compatible distribution exists for the requested version. Only package versions that have at least one compatible distribution will show up when the `_PackageManager` command is run in Rhino 7+. The updated server works seamlessly with existing packages and old versions of Rhino. Pre-existing versions on the server (without distributions) will be treated as `any-any` when installing. New package versions that do not include a distribution tag, e.g. those created by previous versions of the CLI, will also be treated as `any-any` when publishing. ## Next Steps Now that you've have seen what is in a package, why not create one? * Create a package from your [Grasshopper plug-in](../creating-a-grasshopper-plugin-package) or [Rhino plug-in](../creating-a-rhino-plugin-package). * [Create a multi-targeted Rhino plug-in package](../creating-a-multi-targeted-rhino-plugin-package) for Rhino 8+. -------------------------------------------------------------------------------- # The Package Manifest Source: https://developer.rhino3d.com/en/guides/yak/the-package-manifest/ What is a 'package manifest' and what should it include? ## Overview Each package should have a manifest file containing a spec that can be distilled into the database when the package is pushed to the server. The manifest should be written in [YAML](http://www.yaml.org), following the structure of the example below. The manifest file should be named `manifest.yml` and should live in the root of the package. (Don't worry, the Yak CLI tool's [`build` command](/guides/yak/yak-cli-reference#build) takes care of this for you!) The manifest's purpose is to help with streamlining (and potentially automating) the process of releasing packages, removing the need for any web forms when publishing packages. **Required Attributes** - [Name](#name) - [Version](#version) - [Authors](#authors) - [Description](#description) **Recommended Attributes** - [URL](#url) - [Keywords](#keywords) - [Icon](#icon) ## Example Here's an example for a Grasshopper plug-in. ```yaml name: plankton version: 0.3.4 authors: - Daniel Piker - Will Pearson description: > Plankton is a flexible and efficient library for handling n-gonal meshes. Plankton is written in C# and implements the halfedge data structure. The structure of the library is loosely based on Rhinocommon's mesh classes and was originally created for use within C#/VB scripting components in Grasshopper. url: "https://github.com/meshmash/Plankton" ``` ## Required Attributes ### Name The short name describing the package. Preferably one world although multiple words can be separated by underscores or hyphens. _**Note:** Package name can only include letters, numbers, dashes, and underscores_ _**Note 2:** Package names adopt the case used in the very first version that was uploaded. Future uploads ignore the case of the package name and all queries are case-insensitive._ ```yaml name: plankton ``` ### Version _Since 0.8: four-digit version numbers allowed_ _Since 0.9: `$version` placeholder_ The version number given to the package. Package version numbers **must** either follow [Semantic Versioning 2.0.0](http://semver.org/spec/v2.0.0.html) (e.g. `1.1.0-beta`) or `System.Version` a.k.a. Microsoft's four-digit standard (e.g. `1.2.3.4`). It's recommended to use Semantic Versioning because it allows package authors to specify prerelease versions. These are handy for limited testing, since by default the latest _stable_ version is installed. To make the authoring process easier, it's possible to replace the version number with `$version` – the version number will be inferred from the contents of the package and substituted during `yak build`. ```yaml version: 0.3.4 ``` ### Authors A list of author(s) of the package. ```yaml authors: - Daniel Piker # list additional package authors below - Will Pearson ``` ### Description Describe the package. Be as in-depth or as brief as you feel is appropriate. ```yaml description: This is an awesome package. ``` If you want to write more, you can use use YAML's [folded style](http://www.yaml.org/spec/1.2/spec.html#id2796251). ```yaml description: > This is such an awesome package that I'm going to write a whole bunch of text describing it! This sentence will be on a new line. ``` ## Recommended Attributes ### URL A webpage for the package. This can be any URL i.e. author contact info, forums, tutorials or any other information about the plugin. ```yaml url: "https://github.com/meshmash/Plankton" ``` ### Keywords A list of keywords that will help users to find the package. ```yaml keywords: - one - two ``` ### Icon An icon file in the package. It should be small (e.g. 64x64) and it must be either a PNG or a JPEG. ```yaml icon: icon.png ``` ## Obsolete Attributes ### Icon URL ⚠️ Replaced by the icon attribute. Specify a **direct** link to an icon that will be used by the Package Manager in Rhino. It should be small (32x32 is ideal) and it must be either a PNG or a JPEG. ```yaml icon_url: "https://example.com/path/to/icon.png" ``` ## Related Topics - [Yak Guides and Tutorials](/guides/yak/) - [Anatomy of a Package](/guides/yak/the-anatomy-of-a-package/) - [Yak CLI Reference](/guides/yak/yak-cli-reference) -------------------------------------------------------------------------------- # The Package Server Source: https://developer.rhino3d.com/en/guides/yak/the-package-server/ This guide introduces the Rhino Package Manager server - https://yak.rhino3d.com We host a public package server for everyone to use. You don't need to configure anything. Both the Yak CLI tool and Rhino already know where to look. Packages shared on the public package server are free to download and install. They may be free to use or require a license – see the [Cloud Zoo](/guides/rhinocommon/cloudzoo/cloudzoo-overview/) and [Zoo](/guides/rhinocommon/rhinocommon-zoo-plugins/) guides for ways to implement licensing in your plug-in using our tools. Below are a few useful facts about our package server. ## Authentication and authorization Authentication, provided by [Rhino Accounts](https://accounts.rhino3d.com), is only required for [publishing packages](../pushing-a-package-to-the-server), not for downloading/installing. Once a package author has published a package, only they can publish future versions using the same package name. Functionality to add "collaborators" will be added in the future. ## Conventions The package server has a few conventions that must be followed. ### Naming Package names are pretty strict. They only allow letters, numbers, hyphens and underscores, e.g. `Hello_World` or `hello-world1`. Package names adopt the case used in the very first version that was uploaded. Future uploads ignore the case of the package name and all queries are case-insensitive. ### Versioning Package version numbers must either follow [Semantic Versioning 2.0.0](http://semver.org/spec/v2.0.0.html) (e.g. `1.1.0-beta`) or `System.Version` a.k.a. Microsoft's four-digit standard (e.g. `1.2.3.4`). It's recommended to use Semantic Versioning because it allows package authors to specify prerelease versions. These are handy for limited testing, since by default the latest _stable_ version is installed. Four-digit version numbers were added in v0.8 (August 2019) to support existing plug-ins that use `System.Version`-style version numbers. They do not support pre-release or build metadata. #### Partial version numbers and normalisation When a package is created you may notice that the version number in the filename is different to the one in `manifest.yml`. This is due to version number normalisation. This same process happens behind the scenes on the package server and is done to avoid ambiguity between semantically equivalent versions. There are two cases where version numbers can be semantically equivalent. * Semantic versions that only differ in build metadata[^1], i.e. `1.0.0+build.1` and `1.0.0+build.2` * A four-digit version and semantic version that are identical except for a "0" fourth digit (no prerelease or build metadata), e.g. `1.2.3` and `1.2.3.0` Normalisation is different to expansion of partial (e.g. `1.0`) version numbers. Partial versions numbers are expanded and stored in their full form. For example, if you upload a package as `1.0`, it will actually be saved as `1.0.0`. Subsequently, any requests (REST, CLI, etc.) for version `1.0` will be automatically redirected. [^1]: [_"Build metadata MUST be ignored when determining version precedence. Thus two versions that differ only in the build metadata, have the same precedence."_](https://semver.org/#spec-item-10) -------------------------------------------------------------------------------- # Tight Bounding Boxes for Breps Source: https://developer.rhino3d.com/en/samples/rhinocommon/tight-bounding-boxes-for-breps/ Demonstrates how to generate tight bounding boxes for Brep objects. ```cs partial class Examples { public static Result TightBoundingBox(RhinoDoc doc) { ObjRef obj_ref; var rc = RhinoGet.GetOneObject( "Select surface to split", true, ObjectType.Surface, out obj_ref); if (rc != Result.Success) return rc; var surface = obj_ref.Surface(); if (surface == null) return Result.Failure; obj_ref = null; rc = RhinoGet.GetOneObject( "Select cutting curve", true, ObjectType.Curve, out obj_ref); if (rc != Result.Success) return rc; var curve = obj_ref.Curve(); if (curve == null) return Result.Failure; var brep_face = surface as BrepFace; if (brep_face == null) return Result.Failure; var split-brep = brep_face.Split( new List {curve}, doc.ModelAbsoluteTolerance); if (split-brep == null) { RhinoApp.WriteLine("Unable to split surface."); return Result.Nothing; } var meshes = Mesh.CreateFromBrep(split-brep); foreach (var mesh in meshes) { var bbox = mesh.GetBoundingBox(true); switch (bbox.IsDegenerate(doc.ModelAbsoluteTolerance)) { case 3: case 2: return Result.Failure; case 1: // rectangle // box with 8 corners flattened to rectangle with 4 corners var rectangle_corners = bbox.GetCorners().Distinct().ToList(); // add 1st point as last to close the loop rectangle_corners.Add(rectangle_corners[0]); doc.Objects.AddPolyline(rectangle_corners); doc.Views.Redraw(); break; case 0: // box var brep_box = new Box(bbox).ToBrep(); doc.Objects.AddBrep(brep_box); doc.Views.Redraw(); break; } } return Result.Success; } } ``` ```python from scriptcontext import doc import rhinoscriptsyntax as rs from Rhino import RhinoApp from Rhino.Geometry import Curve, Mesh, Box from Rhino.Input import RhinoGet from Rhino.DocObjects import ObjectType from Rhino.Commands import Result from System.Collections.Generic import List def RunCommand(): rc, obj_ref = RhinoGet.GetOneObject("Select surface to split", True, ObjectType.Surface) if rc != Result.Success: return rc brep_face = obj_ref.Surface() if brep_face == None: return Result.Failure rc, obj_ref = RhinoGet.GetOneObject("Select cutting curve", True, ObjectType.Curve) if rc != Result.Success: return rc curve = obj_ref.Curve() if curve == None: return Result.Failure curves = List[Curve]([curve]) split_brep = brep_face.Split(curves, doc.ModelAbsoluteTolerance) if split_brep == None: RhinoApp.WriteLine("Unable to split surface.") return Result.Nothing meshes = Mesh.CreateFromBrep(split_brep) print(type(meshes)) for mesh in meshes: bbox = mesh.GetBoundingBox(True) bbox_type = bbox.IsDegenerate(doc.ModelAbsoluteTolerance) if bbox_type == 1: # rectangle # box with 8 corners flattened to rectangle with 4 corners box_corners = bbox.GetCorners() rectangle_corners = [] for corner_point in box_corners: if corner_point not in rectangle_corners: rectangle_corners.append(corner_point) # add 1st point as last to close the loop rectangle_corners.append(rectangle_corners[0]) doc.Objects.AddPolyline(rectangle_corners) doc.Views.Redraw() elif bbox_type == 0: # box brep_box = Box(bbox).ToBrep() doc.Objects.AddBrep(brep_box) doc.Views.Redraw() else: # bbox invalid, point, or line return Result.Failure return Result.Success if __name__ == "__main__": RunCommand() ``` -------------------------------------------------------------------------------- # Toggling the Status Bar Source: https://developer.rhino3d.com/en/guides/cpp/toggling-status-bar/ This brief guide demonstrates how to show or hide the Rhino status bar using C/C++. ## Overview When you run the Options command, all Rhino's options or application settings that you see are maintained by a `CRhinoAppSettings` class stored on the Rhino application object. This class is a container class as it holds several other `CRhinoApp_xxx_Settings` classes that help to organize all the options. The process for modifying any Rhino option is: 1. Find the container class in `CRhinoAppSettings` that holds the option you want to change. 1. Make a copy of that container class. 1. Change the appropriate members. 1. Replace Rhino's copy of that container class with yours by calling one of `CRhinoAppSettings`' `Set_xxx_Settings()` member functions. ## Sample The following sample source code demonstrates how to show or hide Rhino's status bar using the Rhino C/C++ SDK... ```cpp void ShowRhinoStatusBar( BOOL bShow ) { // Copy the CRhinoAppAppearanceSettings class CRhinoAppAppearanceSettings settings = RhinoApp().AppSettings().AppearanceSettings( true ); if( settings.m_show_statusbar != bShow ) { // Modify the desired setting settings.m_show_statusbar = bShow; // Replace the CRhinoAppAppearanceSettings with the modified version RhinoApp().AppSettings().SetAppearanceSettings( settings ); } } ``` -------------------------------------------------------------------------------- # Tracking Camera Changes with Conduits Source: https://developer.rhino3d.com/en/guides/cpp/tracking-camera-changes-with-conduits/ This guide demonstrates how to use a conduit that uses notifiers to track camera information using C/C++. ## Problem How can you get a windows message from Rhino viewport, specifically when rotating some object in viewport with right click? The goal is to get the parameters from the camera viewport when there is an event, like mouse move or right click. You need those camera settings parameters so that you can set them in another view. ## Solution This can be done through what is called a Conduit Notification. Basically, you setup a `CRhinoDisplayConduit`, attach it to a specific viewport (or all), and then "listen" for specific notifications by overriding `CRhinoDisplayConduit::NotifyConduit`. You would most likely be interested in `CN_PIPELINEOPENED`, `CN_PIPELINECLOSED`, and `CN_PROJECTIONCHANGED` events. On those events you can query the current viewport (camera) for any settings you want, and then react accordingly. ## Sample The following sample plugin command demonstrates a conduit that uses notifiers to track camera information. It also demonstrates how to create a HUD (Heads Up Display) used to display the tracked results and overlay them onto the viewport using transparent bitmap drawing. ```cpp #include "stdafx.h" //////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////// // // BEGIN TestCameraTracker command // class CCameraTrackingConduit : public CRhinoDisplayConduit { public: CCameraTrackingConduit(); bool ExecConduit( CRhinoDisplayPipeline&, // pipeline executing this conduit UINT, // current channel within the pipeline bool& // channel termination flag ); void NotifyConduit(EConduitNotifiers, CRhinoDisplayPipeline&); public: void StartTracking(const CRhinoView*); void StopTracking(void); // Conduit specific attributes... public: ON_3dPoint m_CameraLocation; ON_3dVector m_CameraDirection; ON_3dPoint m_CameraTarget; double m_CameraLensLength; double m_CameraNear; double m_CameraFar; // TODO: Place more tracking variables here based on what you want to track... private: static const COLORREF m_TransColor = RGB(255,0,255); CRhinoUiDib m_HUD; void CreateHUD(const CRhinoDisplayPipeline*); bool BuildHUD(void); }; ///////////////////////////////////////////////////////////////////////////// // CCameraTrackingConduit::CCameraTrackingConduit() : CRhinoDisplayConduit( CSupportChannels::SC_CALCBOUNDINGBOX | CSupportChannels::SC_POSTPROCESSFRAMEBUFFER ) { } ///////////////////////////////////////////////////////////////////////////// // void CCameraTrackingConduit::CreateHUD(const CRhinoDisplayPipeline* dp) { if ( dp != NULL ) { CSize fs = dp->GetFrameSize(); m_HUD.CreateDib( fs.cx, fs.cy, 32, true ); } } ///////////////////////////////////////////////////////////////////////////// // bool CCameraTrackingConduit::BuildHUD(void) { LPBYTE HudBits = m_HUD.FindDIBBits(); bool success = false; if ( HudBits != NULL ) { CDC* pDC = m_HUD; // Our HUD is both a bitmap and a GDI device context, // so we can use GDI routines to draw to it (ie. Text operations) // Fill entire HUD with transparent color...we only want what we're about // to write into the HUD to show up...everything else is transparent so that // we can just overlay it onto the frame buffer...hence, it's a HUD (Heads Up Display) pDC->FillSolidRect( 0, 0, m_HUD.Width(), m_HUD.Height(), m_TransColor ); ON_wString text; CSize exts; int nW = m_HUD.Width(); int nH = m_HUD.Height(); pDC->SetTextColor( RGB( 255, 0, 0 ) ); pDC->SetBkMode( TRANSPARENT ); text.Format( "Camera Location: %5.3f, %5.3f, %5.3f", m_CameraLocation.x, m_CameraLocation.y, m_CameraLocation.z ); exts = pDC->GetTextExtent( text.Array(), text.Length() ); pDC->TextOut( nW - exts.cx - 5, exts.cy, text.Array(), text.Length() ); text.Format( "Camera Direction: %5.3f, %5.3f, %5.3f", m_CameraDirection.x, m_CameraDirection.y, m_CameraDirection.z ); exts = pDC->GetTextExtent( text.Array(), text.Length() ); pDC->TextOut( nW - exts.cx - 5, exts.cy*2, text.Array(), text.Length() ); text.Format( "Camera Target: %5.3f, %5.3f, %5.3f", m_CameraTarget.x, m_CameraTarget.y, m_CameraTarget.z ); exts = pDC->GetTextExtent( text.Array(), text.Length() ); pDC->TextOut( nW - exts.cx - 5, exts.cy*3, text.Array(), text.Length() ); text.Format( "Lens Length: %5.3f", m_CameraLensLength ); exts = pDC->GetTextExtent( text.Array(), text.Length() ); pDC->TextOut( nW - exts.cx - 5, exts.cy*4, text.Array(), text.Length() ); text.Format( "Camera Near & Far: %5.3f, %5.3f",m_CameraNear, m_CameraFar ); exts = pDC->GetTextExtent( text.Array(), text.Length() ); pDC->TextOut( nW - exts.cx - 5, exts.cy*5, text.Array(), text.Length() ); // Debugging...check to see what's in HUD by pasting into someting like // PhotoShop afte the next line is executed... //m_HUD.CopyToClipboard( NULL ); success = true; } return success; } ///////////////////////////////////////////////////////////////////////////// // bool CCameraTrackingConduit::ExecConduit(CRhinoDisplayPipeline& dp, UINT nChannel, bool& bTerminate) { // Is this is not viewport/pipeline we're currently tracking, then bail out now ... if ( !IsBound( dp ) ) return true; switch ( nChannel ) { // You might want to track the scene's current extents...or something similar... case CSupportChannels::SC_CALCBOUNDINGBOX: { break; } // The tracking is conveyed via a Heads Up Display that gets overlaid onto // the frame buffer. This channel either let's us replace the frame buffer // entirely, or simply modify it in some form...We're just going to build // up our HUD and then draw/overlay it on top of the current frame buffer // via transparent bitmap drawing... case CSupportChannels::SC_POSTPROCESSFRAMEBUFFER: { if ( BuildHUD() ) { dp.PushDepthTesting( false ); dp.DrawBitmap( m_HUD, 0, 0, m_TransColor ); dp.PopDepthTesting(); } break; } } return true; } ///////////////////////////////////////////////////////////////////////////// // void CCameraTrackingConduit::NotifyConduit(EConduitNotifiers Notify, CRhinoDisplayPipeline& dp) { // This conduit notifier only acts on existing pipelines, and assumes they're // created and exist...some notify events are based on pipelines coming online // and being deleted...so if we don't do the following, then we cannot assume // the current pipeline or any of its members are valid (without more checking). // Since we're only interested in a few events, we filter on them here... // // Note: If you want to process/handle other types of notification events, then // make sure you add them to the condition below... if ( (Notify != CN_FRAMESIZECHANGED) && (Notify != CN_PROJECTIONCHANGED) && (Notify != CN_PIPELINECLOSED) && (Notify != CN_PIPELINEDELETED) ) return; const CRhinoViewport& View = dp.GetRhinoVP(); // We might need specifics from each const ON_Viewport& vp = View.VP(); // one of these, so we assign them to // their own variable for simplicity... // Is this the viewport we're currently tracking? ... if ( IsBound( dp ) ) { switch ( Notify ) { // Since our HUD's size mirrors the size of the view we're tracking, we need // adjust our HUD if/when the view's frame size changes. case CN_FRAMESIZECHANGED: { CreateHUD( &dp ); break; } // Something changed the tracked view's projection, camera, or frustum settings...so // update our tracking variables accordingly. // Note: This only occurs when the view is being manipulated and NOT because some piece // of code somewhere changed a setting. This notification only occurs during frame // updates. case CN_PROJECTIONCHANGED: { m_CameraLocation = vp.CameraLocation(); m_CameraDirection = vp.CameraDirection(); m_CameraTarget = vp.TargetPoint(); m_CameraNear = vp.FrustumNear(); m_CameraFar = vp.FrustumFar(); vp.GetCamera35mmLensLength( &m_CameraLensLength ); // TODO: Assign more tracking variables here based on what you want to track... break; } // Not sure what you might want to do here, but this notification happens at the // END of EVERY frame update...which tends to be a good place to put/do certain things... // Note: The pipeline is closed by the time you receive this event, so writing anything // to the display at this point is out of the question... case CN_PIPELINECLOSED: { break; } // The pipeline/view we're tracking just got deleted...so we need to // stop tracking it... // Note: This can happen either because the view was deleted, the file was closed, // or the display mode was changed to one that uses a different type of pipeline. // Since it's hard to distinguish between all of those, we'll just disable ourself // and error on the side of being safe. case CN_PIPELINEDELETED: { StopTracking(); break; } } } } ///////////////////////////////////////////////////////////////////////////// // void CCameraTrackingConduit::StartTracking(const CRhinoView* pView) { if ( pView != NULL ) { const ON_Viewport& vp = pView->Viewport().VP(); CreateHUD( pView->DisplayPipeline() ); Bind( vp ); Enable(); } } ///////////////////////////////////////////////////////////////////////////// // void CCameraTrackingConduit::StopTracking() { UnbindAll(); Disable(); } #pragma region TestCameraTracker command class CCommandTestCameraTracker : public CRhinoCommand { public: CCommandTestCameraTracker() {} ~CCommandTestCameraTracker() {} UUID CommandUUID() { // {3D16E807-F816-4C86-92A2-C7B40C883E03} static const GUID TestCameraTrackerCommand_UUID = { 0x3D16E807, 0xF816, 0x4C86, { 0x92, 0xA2, 0xC7, 0xB4, 0x0C, 0x88, 0x3E, 0x03 } }; return TestCameraTrackerCommand_UUID; } const wchar_t* EnglishCommandName() { return L"TestCameraTracker"; } CRhinoCommand::result RunCommand( const CRhinoCommandContext& ); public: CCameraTrackingConduit m_Conduit; }; // The one and only CCommandTestCameraTracker object static class CCommandTestCameraTracker theTestCameraTrackerCommand; CRhinoCommand::result CCommandTestCameraTracker::RunCommand( const CRhinoCommandContext& context ) { // Are we already tracking a view? ... if ( !m_Conduit.IsEnabled() ) { // No, so start tracking the active viewport... CRhinoView* pActiveView = ::RhinoApp().ActiveView(); if ( pActiveView != NULL ) { m_Conduit.StartTracking( pActiveView ); // Only track the camera of a single, specific view... } } else // We're already tracking something so stop...this makes the command act as a toggle. m_Conduit.StopTracking(); // Force redraw of all views... context.m_doc.Regen(); return CRhinoCommand::success; } #pragma endregion // // END TestCameraTracker command // //////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////// ``` -------------------------------------------------------------------------------- # Transform Breps Source: https://developer.rhino3d.com/en/samples/rhinocommon/transform-breps/ Demonstrates how to move (or transform) a user-specified Brep object. ```cs partial class Examples { public static Rhino.Commands.Result TransformBrep(Rhino.RhinoDoc doc) { Rhino.DocObjects.ObjRef rhobj; var rc = RhinoGet.GetOneObject("Select brep", true, Rhino.DocObjects.ObjectType.Brep, out rhobj); if(rc!= Rhino.Commands.Result.Success) return rc; // Simple translation transformation var xform = Rhino.Geometry.Transform.Translation(18,-18,25); doc.Objects.Transform(rhobj, xform, true); doc.Views.Redraw(); return Rhino.Commands.Result.Success; } } ``` ```python import Rhino import scriptcontext def TransformBrep(): rc, objref = Rhino.Input.RhinoGet.GetOneObject("Select brep", True, Rhino.DocObjects.ObjectType.Brep) if rc!=Rhino.Commands.Result.Success: return # Simple translation transformation xform = Rhino.Geometry.Transform.Translation(18,-18,25) scriptcontext.doc.Objects.Transform(objref, xform, True) scriptcontext.doc.Views.Redraw() if __name__=="__main__": TransformBrep() ``` -------------------------------------------------------------------------------- # Transform Screen to World Coordinates Source: https://developer.rhino3d.com/en/samples/cpp/transform-screen-to-world-coordinates/ Demonstrates how to transform screen coordinates to world coordinates. ```cpp CRhinoCommand::result CCommandTest::RunCommand( const CRhinoCommandContext& context ) { CRhinoCommand::result rc = failure; // Get the active view CRhinoView* view = RhinoApp().ActiveView(); if( view ) { // Get the current cursor position POINT point; if( GetCursorPos(&point ) ) { // Convert the screen coordinates to client coordinates view->ScreenToClient( &point ); // Obtain the view's screen-to-world transformation ON_Xform screen_to_world; view->ActiveViewport().VP().GetXform( ON::screen_cs, ON::world_cs, screen_to_world ); // Create a 3-D point ON_3dPoint pt( point.x, point.y, 0 ); // Transform it pt.Transform( screen_to_world ); // Add it to the document context.m_doc.AddPointObject( pt ); context.m_doc.Redraw(); rc = success; } } return rc; } ``` -------------------------------------------------------------------------------- # Transform World to Screen Coordinates Source: https://developer.rhino3d.com/en/samples/cpp/transform-world-to-screen-coordinates/ Demonstrates how to transform world coordinates to screen coordinates. ```cpp CRhinoCommand::result CCommandTest::RunCommand( const CRhinoCommandContext& context ) { // Pick a point CRhinoGetPoint gp; gp.SetCommandPrompt( L"Pick point" ); gp.GetPoint(); if( gp.CommandResult() != CRhinoCommand::success ) return gp.CommandResult(); // Get the view the point was picked in CRhinoView* view = gp.View(); if( 0 == view ) return CRhinoCommand::failure; // Obtain the view's world-to-screen transformation ON_Xform world_to_screen; view->ActiveViewport().VP().GetXform( ON::world_cs, ON::screen_cs, world_to_screen ); // Get the picked point ON_3dPoint picked_pt = gp.Point(); // Create a 3-D point ON_3dPoint screen_pt = picked_pt; // Transform it screen_pt.Transform( world_to_screen ); // Create a Windows 2-D point from the transformed point POINT pt2d; pt2d.x = (int)screen_pt.x; pt2d.y = (int)screen_pt.y; // TODO... RhinoApp().Print( L"Screen point = %d, %d\n", pt2d.x, pt2d.y ); return CRhinoCommand::success; } ``` -------------------------------------------------------------------------------- # Transforming Breps Source: https://developer.rhino3d.com/en/guides/cpp/transforming-breps/ This brief guide demonstrates two ways of transforming Breps using C/C++ ## Samples ### The Short Way ```cpp CRhinoCommand::result CCommandTest::RunCommand( const CRhinoCommandContext& context ) { CRhinoGetObject go; go.SetCommandPrompt( L"Select brep" ); go.SetGeometryFilter( ON::brep_object ); go.GetObjects(1,1); if( go.CommandResult() != success ) return go.CommandResult(); CRhinoObjRef ref = go.Object(0); // Simple translation transformation ON_Xform xform; xform.Translation( ON_3dVector(18,-18,-25) ); context.m_doc.TransformObject( ref, xform ); context.m_doc.Redraw(); return success; } ``` ### The Long Way ```cpp CRhinoCommand::result CCommandTest::RunCommand( const CRhinoCommandContext& context ) { CRhinoGetObject go; go.SetCommandPrompt( L"Select brep" ); go.SetGeometryFilter( ON::brep_object ); go.GetObjects(1,1); if( go.CommandResult() != success ) return go.CommandResult(); const CRhinoObjRef& ref = go.Object(0); const CRhinoObject* obj = ref.Object(); if( !obj ) return failure; const ON_Brep* brep = ref.Brep(); if( !brep ) return failure; ON_Brep* dupe = brep->Duplicate(); if( !dupe ) return failure; // Simple translation transformation ON_Xform xform; xform.Translation( ON_3dVector(18,-18,-25) ); if( !dupe->Transform( xform ) ) { RhinoApp().Print( L"Unable to transform object.\n" ); delete dupe; return failure; } ON_3dmObjectAttributes attribs = obj->Attributes(); context.m_doc.AddBrepObject( *dupe, &attribs ); // Since CRhinoDoc::AddBrepObject() make a copy of the input // brep, we are responsible for deleting the original. Otherwise // we will leak memory; delete dupe; // Delete the selected object context.m_doc.DeleteObject( ref ); context.m_doc.Redraw(); return success; } ``` -------------------------------------------------------------------------------- # Traversing Instance Definitions Source: https://developer.rhino3d.com/en/guides/opennurbs/traverse-instance-definitions/ This brief guide describes how to read instance definitions using the openNURBS toolkit. If you are developing software to read .3dm files, you might also read instance, or block, definitions, in addition to standard geometry. Instances are named groups of objects that act as a single object in your model. They are useful for repeated objects such as symbols or components. Instances save you time since you can reuse the components instead of re-drawing them each time. An advantage of using instances for repeated content is that using instances requires less memory. The definition of a instance object is represented by the ```ON_InstanceDefinition``` class. An instance definition object maintains list of object ids. An ```ON_InstanceRef``` is a reference to an instance definition, long with transformation to apply to the definition. If you are referencing the `Example_read` sample included with the openNURBS toolkit, then after the 3DM file has been read, you can traverse a model's instance references as follows: ```cpp ONX_Model model = ... ON_wString writer; ON_TextLog dump(writer); dump.SetIndentSize(2); ONX_ModelComponentIterator it(model, ON_ModelComponent::Type::InstanceDefinition); const ON_ModelComponent* model_component = nullptr; for (model_component = it.FirstComponent(); nullptr != model_component; model_component = it.NextComponent()) { const ON_InstanceDefinition* idef = ON_InstanceDefinition::Cast(model_component); if (nullptr != idef) DumpInstanceDefinition(model, idef->Id(), dump, true); } wprintf(static_cast(writer)); ``` In this example, ```DumpInstanceDefinition``` is a recursive function, as it is possible to construct instance references whose definition geometry contains one or more instance references. ```cpp static void DumpInstanceDefinition(ONX_Model& model, const ON_UUID& idef_id, ON_TextLog& dump, bool bRoot) { const ON_ModelComponentReference& idef_component_ref = model.ComponentFromId(ON_ModelComponent::Type::InstanceDefinition, idef_id); const ON_InstanceDefinition* idef = ON_InstanceDefinition::Cast(idef_component_ref.ModelComponent()); if (idef) { dump.Print(L"Instance definition %d = %s\n", idef->Index(), static_cast(idef->Name())); const ON_SimpleArray& geometry_id_list = idef->InstanceGeometryIdList(); const int geometry_id_count = geometry_id_list.Count(); if (geometry_id_count > 0) { dump.PushIndent(); for (int i = 0; i < geometry_id_count; i++) { const ON_ModelComponentReference& model_component_ref = model.ComponentFromId(ON_ModelComponent::Type::ModelGeometry, geometry_id_list[i]); const ON_ModelGeometryComponent* model_geometry = ON_ModelGeometryComponent::Cast(model_component_ref.ModelComponent()); if (nullptr != model_geometry) { const ON_Geometry* geometry = model_geometry->Geometry(nullptr); if (nullptr != geometry) { const ON_InstanceRef* iref = ON_InstanceRef::Cast(geometry); if (iref) { DumpInstanceDefinition(model, iref->m_instance_definition_uuid, dump, false); } else { ON_wString type = ObjectTypeToString(geometry->ObjectType()); dump.Print(L"Object %d = %s\n", i, static_cast(type)); } } } } dump.PopIndent(); } } } static ON_wString ObjectTypeToString(ON::object_type type) { ON_wString rc = L"Unknown"; switch (type) { case ON::object_type::point_object: rc = L"point"; break; case ON::pointset_object: rc = L"pointset"; break; case ON::curve_object: rc = L"curve"; break; case ON::surface_object: rc = L"surface"; break; case ON::brep_object: rc = L"brep"; break; case ON::mesh_object: rc = L"mesh"; break; case ON::layer_object: rc = L"layer"; break; case ON::material_object: rc = L"material"; break; case ON::light_object: rc = L"light"; break; case ON::annotation_object: rc = L"annotation"; break; case ON::userdata_object: rc = L"userdata"; break; case ON::instance_definition: rc = L"instance_definition"; break; case ON::instance_reference: rc = L"instance_reference"; break; case ON::text_dot: rc = L"text_dot"; break; case ON::grip_object: rc = L"grip"; break; case ON::detail_object: rc = L"detail"; break; case ON::hatch_object: rc = L"hatch"; break; case ON::morph_control_object: rc = L"morph_control"; break; case ON::subd_object: rc = L"subd"; break; case ON::cage_object: rc = L"cage"; break; case ON::phantom_object: rc = L"phantom"; break; case ON::clipplane_object: rc = L"clipplane"; break; case ON::extrusion_object: rc = L"extrusion"; break; } return rc; } ``` -------------------------------------------------------------------------------- # Triangulating Polygons Source: https://developer.rhino3d.com/en/guides/cpp/triangulating-polygons/ This guide demonstrates how to triangulate polygons using C/C++. ## Overview Rhino's mesh representation, `ON_Mesh`, only support three and four sided faces. This can pose a problem when trying to write an import plugin for a mesh file format that supports n-sided polygons. If you search the Internet, you can probably find a number of algorithms that will triangulate n-sided polygons so they can be used with `ON_Mesh`. The Rhino C/C++ SDK also includes a tool for doing this. The `RhinoTriangulate3dPolygon` SDK function will triangulate an n-sided polygon. The polygon must project onto a plane and the projected polygon must be a simple closed curve. For more information on the `RhinoTriangulate3dPolygon`, see the comments in *rhinoSdkMeshUtilities.h*. ## Sample The following sample code demonstrates how to triangulate a closed planar polygon that has more than three sides using the `RhinoTriangulate3dPolygon` function. Although this sample demonstrates the function on polyline curves, the code could be easily converted to work on mesh vertices. ```cpp CRhinoCommand::result CCommandTest::RunCommand(const CRhinoCommandContext& context) { CRhinoGetObject go; go.SetCommandPrompt(L"Select closed planar polygon to triangulate"); go.SetGeometryFilter(CRhinoGetObject::curve_object); go.SetGeometryFilter(CRhinoGetObject::closed_curve); go.EnableSubObjectSelect(FALSE); go.GetObjects(1, 1); if (go.CommandResult() != success) return go.CommandResult(); ON_3dPointArray vertices; const CRhinoObjRef& ref = go.Object(0); const ON_PolylineCurve* pc = ON_PolylineCurve::Cast(ref.Curve()); if (pc) { vertices = pc->m_pline; } else { const ON_NurbsCurve* nc = ON_NurbsCurve::Cast(ref.Curve()); if (nc) nc->IsPolyline(&vertices); } if (vertices.Count() < 5) { RhinoApp().Print(L"Curve not polygon with at least four sides.\n"); return CRhinoCommand::nothing; } int* triangles = (int*)onmalloc((vertices.Count() - 3) * sizeof(int) * 3); if (nullptr == triangles) return CRhinoCommand::failure; // out of memory memset(triangles, 0, (vertices.Count() - 3) * sizeof(int) * 3); int rc = RhinoTriangulate3dPolygon(vertices.Count() - 1, 3, (const double*)vertices.Array(), 3, triangles); if (0 == rc) { for (int i = 0; i < vertices.Count() - 3; i++) { ON_Polyline pline; pline.Append(vertices[triangles[i * 3]]); pline.Append(vertices[triangles[i * 3 + 1]]); pline.Append(vertices[triangles[i * 3 + 2]]); pline.Append(pline[0]); context.m_doc.AddCurveObject(pline); } context.m_doc.Redraw(); } onfree(triangles); // don't leak return CRhinoCommand::success; } ``` -------------------------------------------------------------------------------- # Trim Curve with Circle Source: https://developer.rhino3d.com/en/samples/rhinoscript/trim-curve-with-circle/ Demonstrates how to trim a closed curve with a circle using RhinoScript. ```vbnet Option Explicit Sub CircleTrimmer ' Local variable declarations Dim curve, circle, ccx, ccx_t(1), interval ' Select closed curve to split curve = Rhino.GetObject("Select closed curve to split", 4) If IsNull(curve) Then Exit Sub If Not Rhino.IsCurveClosed(curve) Then Exit Sub ' Select circle to split with circle = Rhino.GetObject("Select circle to split with", 4) If IsNull(circle) Then Exit Sub If Not Rhino.IsCircle(circle) Then Exit Sub ' Intersect the two curves ccx = Rhino.CurveCurveIntersection(curve, circle) If IsNull(ccx) Then Rhino.Print "Curve and circle do not intersect" Exit Sub End If ' Make sure there are only two intersection events If UBound(ccx) <> 1 Then Rhino.Print "Unable to split curve" Exit Sub End If ' Get two intersection parameters on the curve ccx_t(0) = ccx(0,5) ccx_t(1) = ccx(1,5) ' If the input curve is closed and the interval is decreasing, ' then the portion of the curve across the start and end of the ' curve is returned. If ccx_t(0) < ccx_t(1) Then interval = Array(ccx_t(1), ccx_t(0)) Else interval = Array(ccx_t(0), ccx_t(1)) End If ' Trim the curve Rhino.TrimCurve curve, interval End Sub ``` -------------------------------------------------------------------------------- # Trimming Curves Source: https://developer.rhino3d.com/en/guides/rhinoscript/trimming-curves/ This guide demonstrates how to trim curves using RhinoScript. ## Problem Imagine you need to trim a lot of lines where they intersect. How is this done? What is a "domain?" ## Solution If you can remember back to your pre-calculus days, a domain is most often defined as the set of values for which a function is defined. As curves in Rhino have starting and ending points, they also have starting (minimum) and ending (maximum) domain values (parameters). You can obtain a curve's minimum and maximum domain values using the `CurveDomain` function. To trim a curve using TrimCurve, you must provide an interval, or sub-domain, of the curve that you want to keep. For example, if you have a curve with a minimum domain value of 0 and a maximum domain value of 5 and you wanted everything from t=2 to the end of the curve trimmed away, then you'd do something like this: ```vbnet domain = Rhino.CurveDomain(curve) Call Rhino.TrimCurve(curve, Array(domain(0), 2), True) ``` Remember, the interval argument defines what you want to keep, not what you want to trim. If two curves intersect, `CurveCurveIntersection` will return the parameter on the curve where the intersection event took place. Using this parameter, you can begin to build an interval to pass to `TrimCurve`. The following example script demonstrates how to interactively trim a curve using what was discussed above... ```vbnet Sub TestTrimCurve Const rhCurve = 4 ' Pick the cutting curve Dim cutter : cutter = Rhino.GetObject("Select cutting curve", rhCurve) If IsNull(cutter) Then Exit Sub ' Pick the curve to trim Dim curve : curve = Rhino.GetCurveObject("Select curve to trim") If IsNull(curve) Then Exit Sub ' Calculate the intersection of the two curves Dim ccx : ccx = Rhino.CurveCurveIntersection(curve(0), cutter) If IsNull(ccx) Then Rhino.Print "Curves do not intersect." Exit Sub End If Dim trim_t : trim_t = ccx(0, 5) ' intersection parameter Dim pick_t : pick_t = curve(4) ' pick parameter Dim domain : domain = CurveDomain(curve(0)) ' curve domain ' TrimCurve's interval argument defines what to keep. ' So, figure out what side of the curve to keep. Dim interval If (trim_t < pick_t) Then interval = Array(domain(0), trim_t) Else interval = Array(trim_t, domain(1)) End If ' Trim the curve Rhino.TrimCurve curve(0), interval End Sub ``` -------------------------------------------------------------------------------- # Tweak Colors Source: https://developer.rhino3d.com/en/samples/rhinocommon/tweak-colors/ Demonstrates how to set the default paint colors in Rhino. ```cs partial class Examples { public static Rhino.Commands.Result TweakColors(Rhino.RhinoDoc doc) { Rhino.ApplicationSettings.AppearanceSettings.SetPaintColor(Rhino.ApplicationSettings.PaintColor.NormalStart, System.Drawing.Color.AliceBlue); Rhino.ApplicationSettings.AppearanceSettings.SetPaintColor(Rhino.ApplicationSettings.PaintColor.NormalEnd, System.Drawing.Color.AliceBlue); Rhino.ApplicationSettings.AppearanceSettings.SetPaintColor(Rhino.ApplicationSettings.PaintColor.NormalBorder, System.Drawing.Color.LightBlue); Rhino.ApplicationSettings.AppearanceSettings.SetPaintColor(Rhino.ApplicationSettings.PaintColor.HotStart, System.Drawing.Color.LightBlue); Rhino.ApplicationSettings.AppearanceSettings.SetPaintColor(Rhino.ApplicationSettings.PaintColor.HotEnd, System.Drawing.Color.LightBlue, true); return Rhino.Commands.Result.Success; } } ``` ```python # No Python sample available ``` -------------------------------------------------------------------------------- # Tween Curve Source: https://developer.rhino3d.com/en/samples/rhinocommon/tween-curve/ Demonstrates how to tween two curves. ```cs partial class Examples { public static Rhino.Commands.Result TweenCurve(Rhino.RhinoDoc doc) { Rhino.Input.Custom.GetObject go = new Rhino.Input.Custom.GetObject(); go.SetCommandPrompt("Select two curves"); go.GeometryFilter = Rhino.DocObjects.ObjectType.Curve; go.GetMultiple(2, 2); if (go.CommandResult() != Rhino.Commands.Result.Success) return go.CommandResult(); Rhino.Geometry.Curve curve0 = go.Object(0).Curve(); Rhino.Geometry.Curve curve1 = go.Object(1).Curve(); if (null != curve0 && null != curve1) { Rhino.Geometry.Curve[] curves = Rhino.Geometry.Curve.CreateTweenCurves(curve0, curve1, 1); if (null != curves) { for (int i = 0; i < curves.Length; i++) doc.Objects.AddCurve(curves[i]); doc.Views.Redraw(); return Rhino.Commands.Result.Success; } } return Rhino.Commands.Result.Failure; } } ``` ```python # No Python sample available ``` -------------------------------------------------------------------------------- # Two View Layout Source: https://developer.rhino3d.com/en/samples/cpp/two-view-layout/ Demonstrates how to create a two-view viewport layout. ```cpp CRhinoCommand::result CCommand2View::RunCommand( const CRhinoCommandContext& context ) { ON_3dmView views[2]; double def_size = 15.0; ON_BoundingBox bbox; bbox.m_min.Set( -def_size, -def_size, -def_size ); bbox.m_max.Set( def_size, def_size, def_size ); ON_3dPoint target = ON_origin; const CRhinoAppViewSettings& view_settings = RhinoApp().AppSettings().ViewSettings(); // top view { views[0].m_name = L"Top"; views[0].m_target = target; ON_3dVector dir( 0.0, 0.0, -100.0 ); views[0].m_vp.SetCameraLocation( views[0].m_target - dir ); views[0].m_vp.SetCameraDirection( dir ); views[0].m_vp.SetCameraUp( ON_yaxis ); views[0].m_vp.SetProjection( ON::parallel_view ); views[0].m_vp.SetScreenPort( 0, 100, 100, 0, 0, 1 ); views[0].m_vp.Extents( atan(12.0 / view_settings.m_camera_lense_length), bbox ); views[0].m_cplane.m_plane = ON_xy_plane; views[0].m_position.m_wnd_left = 0.0; views[0].m_position.m_wnd_right = 0.5; views[0].m_position.m_wnd_top = 0.0; views[0].m_position.m_wnd_bottom = 1.0; } // perspective view { views[1].m_name = L"Perspective"; views[1].m_target = target; ON_3dVector dir( -43.30, 75.00, -50.00 ); views[1].m_vp.SetCameraLocation( views[1].m_target - dir ); views[1].m_vp.SetCameraDirection( dir ); views[1].m_vp.SetCameraUp( ON_zaxis ); views[1].m_vp.SetProjection( ON::perspective_view ); views[1].m_vp.SetScreenPort( 0, 100, 100, 0, 0, 1 ); views[1].m_vp.Extents( atan(12.0 / view_settings.m_camera_lense_length), bbox ); views[0].m_cplane.m_plane = ON_xy_plane; views[1].m_position.m_wnd_left = 0.5; views[1].m_position.m_wnd_right = 1.0; views[1].m_position.m_wnd_top = 0.0; views[1].m_position.m_wnd_bottom = 1.0; } context.m_doc.ReplaceModelViews( 2, views ); context.m_doc.Redraw(); return CRhinoCommand::success; } ``` -------------------------------------------------------------------------------- # Type Casting Rhino Objects Source: https://developer.rhino3d.com/en/guides/cpp/type-casting-rhino-objects/ This guide discusses type casting Rhino C/C++ SDK objects. ## Problem Given a Rhino object, how can one convert it to another Rhino object? For example, if you have a `CRhinoObject` pointer, how can I convert it to a `CRhinoCurveObject` pointer? Or, how can one get the curve geometry? ## Solution All Rhino C/C++ SDK classes derived from `ON_Object` provide conversions between pointers to related classes using a static `ON_Object::Cast` function. If you have a pointer to some base class that inherits from `ON_Object`, and you want to convert it to a pointer of a derived class, than simply call the derived class `Cast` function. For example: ```cpp const CBase* a = ...; const CDerived* b = CDerived::Cast( a ); ``` ## Samples ```cpp CRhinoGetObject go; go.SetCommandPrompt( L"Select something" ); go.GetObjects( 1, 1 ); if( CRhinoCommand::success == go.CommandResult() ) { // Get the one (and only) object reference CRhinoObjRef obj_ref = go.Object(0); // Get the Rhino object const CRhinoObject* obj = obj_ref.Object(); if( obj ) { // Try casting as a Rhino point object const CRhinoPointObject* point_obj = CRhinoPointObject::Cast( obj ); if( point_obj ) { // Get the point object's point geometry const ON_Point& point = point_obj->Point(); // todo... } // Try casting as a Rhino curve object const CRhinoCurveObject* curve_obj = CRhinoCurveObject::Cast( obj ); if( curve_obj ) { // Get the curve object's curve geometry const ON_Curve* curve = curve_obj->Curve(); // todo... } // Try casting as a Rhino brep object const CRhinoBrepObject* brep_obj = CRhinoBrepObject::Cast( obj ); if( brep_obj ) { // Get the brep object's brep geometry const ON_Brep* brep = brep_obj->Brep(); // todo... } // Try casting as a Rhino mesh object const CRhinoMeshObject* mesh_obj = CRhinoMeshObject::Cast( obj ); if( mesh_obj ) { // Get the mesh object's mesh geometry const ON_Mesh* mesh = mesh_obj->Mesh(); // todo... } // etc... } } ``` and... ```cpp CRhinoGetObject go; go.SetCommandPrompt( L"Select something" ); go.GetObjects( 1, 1 ); if( CRhinoCommand::success == go.CommandResult() ) { // Get the one (and only) object reference CRhinoObjRef obj_ref = go.Object(0); // Get the Rhino object's geometry const ON_Geometry* geo = obj_ref.Geometry(); if( geo ) { // Try casting as a point object const ON_Point* point = ON_Point::Cast( geo ); if( point ) { // todo... } // Try casting as a curve object const ON_Curve* curve = ON_Curve::Cast( geo ); if( curve ) { // todo... } // Try casting as a brep object const ON_Brep* brep = ON_Brep::Cast( geo ); if( brep ) { // todo... } // Try casting as a mesh object const ON_Mesh* mesh = ON_Mesh::Cast( geo ); if( mesh ) { // todo... } // etc... } } ``` -------------------------------------------------------------------------------- # Un-tag an Object as a Flamingo Plant Source: https://developer.rhino3d.com/en/samples/rhinoscript/untag-object-as-a-flamingo-plant/ Demonstrates how to un-tag an object as a Flamingo nXt plant using RhinoScript. ```vbnet Option Explicit Call Main() Sub Main() Dim objPlugIn, strObject, plant On Error Resume Next Set objPlugIn = Rhino.GetPluginObject("8008880f-8d13-4b2d-92b0-727e12878a4c") If Err Then MsgBox Err.Description Exit Sub End If strObject = Rhino.GetObject("Select object") If Not IsNull(strObject) And Not strObject = "" Then If objPlugIn.UnTagObjectAsPlant(strObject) Then Rhino.Print("Plant tags removed from object") End If End If Set objPlugIn = Nothing End Sub ``` -------------------------------------------------------------------------------- # Uncommon Numeric Conversions Source: https://developer.rhino3d.com/en/guides/rhinoscript/uncommon-numeric-conversions/ This brief guide demonstrates some useful (or not so useful) numeric conversions in RhinoScript. ## Hexadecimal This function converts numbers to their hexadecimal representation... ```vbnet Function CHex(intNumber) Dim strChars, intSign strChars = "0123456789ABCDEF" intSign = Sgn(intNumber) intNumber = Fix(Abs(CDbl(intNumber))) If (intNumber = 0) Then CHex = "0" Exit Function End If While (intNumber > 0) CHex = Mid(strChars, 1 + (intNumber - 16 * Fix(intNumber / 16)), 1) & CHex intNumber = Fix(intNumber / 16) Wend If (intSign = -1) Then CHex = "-" & CHex End Function ``` and can be used to... ```vbnet Rhino.Print CHex(2008) '7D8 ``` ## Binary The following function converts numbers to their binary representation... ```vbnet Function CBinary(intNumber, intBits) Dim strBinary, intMask, i strBinary = intMask = 1 For i = 1 To intBits If (intNumber And intMask) Then strBinary = "1" & strBinary Else strBinary = "0" & strBinary End If intMask = intMask * 2 Next CBinary = strBinary End Function ``` and can be used to... ```vbnet Rhino.Print CBinary(2008, 16) '0000011111011000 ``` ## Roman Numerals The following function converts numbers to their Roman numeral representation... ```vbnet Function CRoman(intNumber) Dim v, w, x, y, arrOnes, arrTens, arrHund, arrThou arrOnes = Array(,"I","II","III","IV","V","VI","VII","VIII","IX") arrTens = Array(,"X","XX","XXX","XL","L","LX","LXX","LXXX","XC") arrHund = Array(,"C","CC","CCC","CD","D","DC","DCC","DCCC","CM") arrThou = Array(,"M","MM","MMM","MMMM","MMMMM") v = ((intNumber - (intNumber Mod 1000)) / 1000) intNumber = (intNumber Mod 1000) w = ((intNumber - (intNumber Mod 100)) / 100) intNumber = (intNumber Mod 100) x = ((intNumber - (intNumber Mod 10)) / 10) y = (intNumber Mod 10) CRoman = arrThou(v) & arrHund(w) & arrTens(x) & arrOnes(y) End Function ``` and can be used to... ```vbnet Rhino.Print CRoman(2008) 'MMVIII ``` ## Roman Numeral to Base 10 The following function converts Roman numeral representations to their base 10 representation... ```vbnet Function CUnRoman(strRoman) Dim intvalue, strChar, i intValue = 0 If InStr(strRoman, "CM") Then intValue = intValue + 900 strRoman = Replace(strRoman, "CM", ) End If If InStr(strRoman, "CD") Then intValue = intValue + 400 strRoman = Replace(strRoman, "CD", ) End If If InStr(strRoman, "XC") Then intValue = intValue + 90 strRoman = Replace(strRoman, "XC", ) End If If InStr(strRoman, "XL") Then intValue = intValue + 40 strRoman = Replace(strRoman, "XL", ) End If If InStr(strRoman, "IX") Then intValue = intValue + 9 strRoman = Replace(strRoman, "IX", ) End If If InStr(strRoman, "IV") Then intValue = intValue + 4 strRoman = Replace(strRoman, "IV", ) End If For i = 1 To Len(strRoman) strChar = Mid(strRoman, i, 1) Select Case strChar Case "I" intValue = intValue + 1 Case "V" intValue = intValue + 5 Case "X" intValue = intValue + 10 Case "L" intValue = intValue + 50 Case "C" intValue = intValue + 100 Case "D" intValue = intValue + 500 Case "M" intValue = intValue + 1000 End Select Next CUnRoman = intValue End Function ``` and can be used to... ```vbnet Rhino.Print CUnRoman(MMVIII) '2008 ``` -------------------------------------------------------------------------------- # Unifying Mesh Normals Source: https://developer.rhino3d.com/en/guides/cpp/unifying-mesh-normals/ This brief guide demonstrates how to unify the normal direction of mesh faces using C/C++. ## Problem You have found that `RhinoUnifyMeshNormals` C/C++ functions seems to behave differently than the *UnifyMeshNormals* command. How can one achieve the same functionality in a plugin? ## Solution In addition to calling the `RhinoUnifyMeshNormals` function, the *UnifyMeshNormals* command also recomputes the mesh's vertex normals, based on the new face normals, using `ON_Mesh::ComputeVertexNormals`. ## Sample The following sample demonstrates this. ```cpp CRhinoCommand::result CCommandTest::RunCommand( const CRhinoCommandContext& context ) { CRhinoGetObject go; go.SetCommandPrompt( L"Select mesh to unify normals" ); go.SetGeometryFilter( CRhinoGetObject::mesh_object ); go.GetObjects( 1, 1 ); CRhinoCommand::result rc = go.CommandResult(); if( rc != CRhinoCommand::success ) return rc; const CRhinoObjRef ref = go.Object(0); const ON_Mesh* mesh = ref.Mesh(); if( 0 == mesh ) return CRhinoCommand::failure; int count = 0; ON_Mesh* new_mesh = RhinoUnifyMeshNormals( *mesh, 0, false, &count ); if( new_mesh && new_mesh->IsValid() ) { new_mesh->ComputeVertexNormals(); CRhinoMeshObject* new_obj = new CRhinoMeshObject(); new_obj->SetMesh( new_mesh ); context.m_doc.ReplaceObject( ref, new_obj ); context.m_doc.Redraw(); RhinoApp().Print( L"Reversed the orientation of %d faces.\n", count ); rc = CRhinoCommand::success; } else { if( 0 != count ) { RhinoApp().Print( L"Unable to unify mesh normals.\n" ); rc = CRhinoCommand::failure; } else { RhinoApp().Print( L"All face normals are already oriented in the same direction.\n" ); rc = CRhinoCommand::nothing; } } return rc; } ``` -------------------------------------------------------------------------------- # Unroll Surface Source: https://developer.rhino3d.com/en/samples/rhinocommon/unroll-surface/ Unrolling a developable surface ```cs partial class Examples { public static Rhino.Commands.Result UnrollSurface(Rhino.RhinoDoc doc) { const ObjectType filter = Rhino.DocObjects.ObjectType.Brep | Rhino.DocObjects.ObjectType.Surface; Rhino.DocObjects.ObjRef objref; Result rc = Rhino.Input.RhinoGet.GetOneObject("Select surface or brep to unroll", false, filter, out objref); if (rc != Rhino.Commands.Result.Success) return rc; Rhino.Geometry.Unroller unroll=null; Rhino.Geometry.Brep brep = objref.Brep(); if (brep != null) unroll = new Rhino.Geometry.Unroller(brep); else { Rhino.Geometry.Surface srf = objref.Surface(); if (srf != null) unroll = new Rhino.Geometry.Unroller(srf); } if (unroll == null) return Rhino.Commands.Result.Cancel; unroll.AbsoluteTolerance = 0.01; unroll.RelativeTolerance = 0.01; Rhino.Input.Custom.GetObject go = new Rhino.Input.Custom.GetObject(); go.SetCommandPrompt("Select points, curves, and dots to unroll with surface"); go.GeometryFilter = Rhino.DocObjects.ObjectType.Point | Rhino.DocObjects.ObjectType.Curve | Rhino.DocObjects.ObjectType.TextDot; go.AcceptNothing(true); go.GetMultiple(0, 0); if (go.CommandResult() != Rhino.Commands.Result.Success) return go.CommandResult(); for (int i = 0; i < go.ObjectCount; i++) { objref = go.Object(i); Rhino.Geometry.GeometryBase g = objref.Geometry(); Rhino.Geometry.Point pt = g as Rhino.Geometry.Point; Rhino.Geometry.Curve crv = g as Rhino.Geometry.Curve; Rhino.Geometry.TextDot dot = g as Rhino.Geometry.TextDot; if (pt != null) unroll.AddFollowingGeometry(pt.Location); else if (crv != null) unroll.AddFollowingGeometry(crv); else if (dot != null) unroll.AddFollowingGeometry(dot); } unroll.ExplodeOutput = false; Rhino.Geometry.Curve[] curves; Rhino.Geometry.Point3d[] points; Rhino.Geometry.TextDot[] dots; Rhino.Geometry.Brep[] breps = unroll.PerformUnroll(out curves, out points, out dots); if (breps == null || breps.Length < 1) return Rhino.Commands.Result.Failure; for (int i = 0; i < breps.Length; i++) doc.Objects.AddBrep(breps[i]); for (int i = 0; i < curves.Length; i++) doc.Objects.AddCurve(curves[i]); doc.Objects.AddPoints(points); for (int i = 0; i < dots.Length; i++) doc.Objects.AddTextDot(dots[i]); doc.Views.Redraw(); return Rhino.Commands.Result.Success; } } ``` ```python import Rhino import scriptcontext def UnrollSurface(): filter = Rhino.DocObjects.ObjectType.Brep | Rhino.DocObjects.ObjectType.Surface rc, objref = Rhino.Input.RhinoGet.GetOneObject("Select surface or brep to unroll", False, filter) if rc!=Rhino.Commands.Result.Success: return rc; unroll = Rhino.Geometry.Unroller(objref.Geometry()) go = Rhino.Input.Custom.GetObject() go.SetCommandPrompt("Select points, curves, and dots to unroll with surface") go.GeometryFilter = Rhino.DocObjects.ObjectType.Point | Rhino.DocObjects.ObjectType.Curve | Rhino.DocObjects.ObjectType.TextDot go.AcceptNothing(True) go.GetMultiple(0, 0) if go.CommandResult()!=Rhino.Commands.Result.Success: return go.CommandResult() for i in range(go.ObjectCount): objref = go.Object(i); g = objref.Geometry(); unroll.AddFollowingGeometry(g) unroll.ExplodeOutput = False breps, curves, points, dots = unroll.PerformUnroll() if not breps: return Rhino.Commands.Result.Failure for brep in breps: scriptcontext.doc.Objects.AddBrep(brep) for curve in curves: scriptcontext.doc.Objects.AddCurve(curve) for point in points: scriptcontext.doc.Objects.AddPoint(point) for dot in dots: scriptcontext.doc.Objects.AddTextDot(dot) scriptcontext.doc.Views.Redraw() return Rhino.Commands.Result.Success if __name__=="__main__": UnrollSurface() ``` -------------------------------------------------------------------------------- # Unroll Surface and Mesh Source: https://developer.rhino3d.com/en/samples/rhinocommon/unroll-surface-and-mesh/ Unroll developable surface and associated mesh ```cs partial class Examples { public static Rhino.Commands.Result UnrollSurface2(Rhino.RhinoDoc doc) { const ObjectType filter = Rhino.DocObjects.ObjectType.Brep | Rhino.DocObjects.ObjectType.Surface; Rhino.DocObjects.ObjRef objref; Result rc = Rhino.Input.RhinoGet.GetOneObject("Select surface or brep to unroll", false, filter, out objref); if (rc != Rhino.Commands.Result.Success) return rc; Rhino.Geometry.Unroller unroll=null; Rhino.Geometry.Brep brep = objref.Brep(); Rhino.Geometry.Mesh mesh=null; if (brep != null) { unroll = new Rhino.Geometry.Unroller(brep); mesh = brep.Faces[0].GetMesh(Rhino.Geometry.MeshType.Render); } else { Rhino.Geometry.Surface srf = objref.Surface(); if (srf != null) { unroll = new Rhino.Geometry.Unroller(srf); } } if (unroll == null || mesh==null) return Rhino.Commands.Result.Cancel; unroll.AddFollowingGeometry(mesh.Vertices.ToPoint3dArray()); unroll.ExplodeOutput = false; Rhino.Geometry.Curve[] curves; Rhino.Geometry.Point3d[] points; Rhino.Geometry.TextDot[] dots; unroll.PerformUnroll(out curves, out points, out dots); // change the mesh vertices to the flattened form and add it to the document if( points.Length == mesh.Vertices.Count ) { for( int i=0; im_my_string = L"How, now, brown cow?"; if ( pMesh->AttachUserData(pMyUserData1) ) { // It worked. The mesh class will manage the user // data and delete it at the appropriate time. RhinoApp().Print("User data attached.\n"): } else { // It didn't work. This usually means that // the parent object already has this kind // of user data attached. RhinoApp().Print("User data not attached.\n"); delete pMyUserData; // Don't leak... } pMyUserData = nullptr; ``` The user data's *UUID* is used to retrieve the user data from a parent object... ```cpp ON_Mesh* pMesh = ...; ON_UserData1* pUserData = pMesh->GetUserData(CMyUserData1::Id()); CMyUserData1* pMyUserData1 = static_cast(pUserData); if (pMyUserData1) { RhinoApp().Print("Got user data.\n"): } else { RhinoApp().Print("My user data is not on the mesh.\n"): } ``` ### CMyUserData2 Make sure you completely understand the *CMyUserData1* sample above before studying this sample. To have your user data saved in files, you have to add several IO related functions and add full openNURBS object support to your class... ```cpp class CMyUserData2 : public ON_UserData { // openNURBS classes that are saved in .3dm files require // an ON_OBJECT_DECLARE call in their declaration. ON_OBJECT_DECLARE(CMyUserData2); public: static ON_UUID Id(); CMyUserData2(); ~CMyUserData2(); // override virtual ON_UserData::Archive() bool Archive() const override; // override virtual ON_UserData::Write() bool Write(ON_BinaryArchive& binary_archive) const override; // override virtual ON_UserData::Read() bool Read(ON_BinaryArchive& binary_archive) override; ON_wString m_my_string; ON_3dPoint m_my_point; }; ``` Again, the `CMyUserData2` class must always be on the heap (constructed by calling the C/C++ new operator). To support file IO, the definition of `CMyUserData2` must contain the `ON_OBJECT_DECLARE` macro. The cpp file where the class is implemented must contain the `ON_OBJECT_IMPLEMENT` macro shown here: ```cpp ON_OBJECT_IMPLEMENT(CMyUserData2,ON_UserData,"0D8FA7AB-F8A4-..."); ``` The construction code for `CMyUserData2` looks like: ```cpp ON_UUID CMyUserData2::Id() { return ON_CLASS_ID(CMyUserData2); } CMyUserData2::CMyUserData2() { m_userdata_uuid = CMyUserData2::Id(); m_application_uuid = MyPlugIn().PlugInID(); m_userdata_copycount = 1; // enable copying // initialize your data members here m_my_point.Set(0.0, 0.0, 0.0); } ``` The `CMyUserData2::Archive()`, `CMyUserData2::Read()`, `CMyUserData2::Write()` functions look something like this... ```cpp bool CMyUserData2::Archive() const { // If false is returned, nothing will be saved in 3dm archives. return true; } bool CMyUserData2::Write(ON_BinaryArchive& binary_archive) const { int minor_version = 0; bool rc = binary_archive.BeginWrite3dmChunk( TCODE_ANONYMOUS_CHUNK, 1, minor_version ); if (!rc) return false; // Write class members like this for (;;) { // version 1.0 fields rc = binary_archive.WriteString(m_my_string); if (!rc) break; rc = binary_archive.WritePoint(m_my_point); if (!rc) break; break; } if (!binary_archive.EndWrite3dmChunk()) rc = false; return rc; } bool CMyUserData2::Read(ON_BinaryArchive& binary_archive) { int major_version = 0; int minor_version = 0; bool rc = binary_archive.BeginRead3dmChunk( TCODE_ANONYMOUS_CHUNK, &major_version, &minor_version ); if (!rc) return false; // Read class members like this for (;;) { rc == ( 1 == major_version ); if (!rc) break; // version 1.0 fields rc = binary_archive.ReadString(m_my_string); if (!rc) break; rc = binary_archive.ReadPoint(m_my_point); if (!rc) break; break; } // If BeginRead3dmChunk() returns true, // then EndRead3dmChunk() must be called, // even if a read operation failed. if ( !binary_archive.EndRead3dmChunk() ) rc = false; return rc; } ``` ## User Data operator= and Copy Construction In general, you do not need to explicitly override the default copy constructor and `operator=` that C/C++ generates for you. Incorrectly implemented copy constructors and `operator=`s are a common source of bugs. The C/C++ defaults will work perfectly if every member of your class is a built-in data type (`int`, `double`, ...) or a class that properly handles copy construction and `operator=` (`ON_NurbsCurve`, `ON_3dPoint`, `ON_Simple`/`ClassArray<>`, ...). The main reason you have to explicitly implement a copy constructor and `operator=` is to handle fields that involve explicit heap allocation and deallocation. In the sample below, the field `m_i_array` is an array of integers that is on the heap. ```cpp class CMyUserData3 : public ON_UserData { ON_OBJECT_DECLARE(CMyUserData3); public: static ON_UUID Id(); static ON_UUID ApplicationId(); static CMyUserData3* Get(const ON_Object*); CMyUserData3(); ~CMyUserData3(); CMyUserData3(const CMyUserData3& src); CMyUserData3& operator=(const CMyUserData3& src); //... int m_i_count = 0; int* m_i_array = 0; }; ON_OBJECT_IMPLEMENT(CMyUserData3, ON_UserData, "0D8FA7AB-F8A4-..."); ON_UUID CMyUserData3::Id() { return ON_CLASS_ID(CMyUserData3); } ON_UUID CMyUserData3::ApplicationId() { return MyPlugin().PlugInID(); } CMyUserData3* CMyUserData3::Get(const ON_Object* object) { return CMyUserData3::Cast(object ? object->GetUserData(CMyUserData3::Id()) : nullptr); } CMyUserData3::CMyUserData3() { m_userdata_uuid = CMyUserData3::Id(); m_application_uuid = CMyUserData3::ApplicationId(); m_userdata_copycount = 1; // enable copying } CMyUserData3::~CMyUserData3() { // virtual function if (m_i_array) onfree(m_i_array); } ``` When the copy constructor is called, the memory for "this" is not initialized. You must call the base class copy constructor, initialize all your class's fields, and then copy your class's fields... ```cpp CMyUserData3::CMyUserData3(const CMyUserData3& src ) { m_userdata_uuid = CMyUserData3::Id(); m_application_uuid = CMyUserData3::ApplicationId(); m_transform_count = 0; // DO NOT SET OTHER ON_UserData fields // In particular, do not set m_userdata_copycount // Copy you class's fields m_i_count = 0; m_i_array = 0; if (src.m_i_count > 0 && src.m_i_array != 0) { m_i_array = (int*)onmalloc(src.m_i_count * sizeof(m_i_array[0])); if (m_i_array != 0) { memcpy(m_i_array, src.m_i_array, src.m_i_count*sizeof(m_i_array[0]) ); m_i_count = src.m_i_count; } } } ``` When `operator=` is called, "this" has already been constructed and may already have been used. You must destroy any existing information, call the the base class `operator=`, and then copy your class's fields... ```cpp CMyUserData3& CMyUserData3::operator=(const CMyUserData3& src) { if (this != &src) { // Destroy your class's existing information // (Otherwise you will leak memory if "this" // has been used before.) m_i_count = 0; if (m_i_array != 0) { onfree(m_i_array); m_i_array = 0 } // Use the base class operator=() to correctly // copy all ON_UserData fields. ON_UserData::operator=(src); // Copy your class's fields if (src.m_i_count > 0 && src.m_i_array != 0) { m_i_array = (int*)onmalloc(src.m_i_count * sizeof(m_i_array[0])); if (m_i_array != 0) { memcpy(m_i_array, src.m_i_array, src.m_i_count*sizeof(m_i_array[0]) ); m_i_count = src.m_i_count; } } } return *this; } ``` ## Sharing User Data Between Plugins The best way to share user data between plugins is to have access to the plugins data controlled by a shared *DLL* - a *DLL* that is used by all interested plugins. The user data class definitions and declarations, along with any helper functions used to access this data, are then added to and exported from this *DLL*. The easiest way to make *DLLs* for Rhino plugins is to simply run the *Rhino Plugin Wizard* from Visual Studio. When the wizard finishes, simply delete the plugins object (*cpp* and *h* files) and the command file. Then change the output file extension from *rhp* to *dll*. Now, you have a *MFC DLL* that links with the Rhino C/C++ SDK. You can find a fully detailed example in the Rhino developper samples Github repository: [SampleSharedUserDataCoreLib](https://github.com/mcneel/rhino-developer-samples/tree/8/cpp/SampleSharedUserDataCoreLib), [SampleSharedUserData1](https://github.com/mcneel/rhino-developer-samples/tree/8/cpp/SampleSharedUserData1), [SampleSharedUserData2](https://github.com/mcneel/rhino-developer-samples/tree/8/cpp/SampleSharedUserData2) One important piece of information to keep in mind is that when you create a class derived from `ON_UserData` and you expect this data to be serialized in a *3dm* file, then you must assign the owning plugins's *UUID* to the `ON_UserData::m_application_uuid` data member. This is how Rhino knows what plugins to load when it encounters plugin user data when reading a *3dm* file. Note, it is not important what plugins's *UUID* is assigned to the user data because all of the plugins are going to dynamically load the *DLL* when they are loaded anyway. But, some plugin must be "in charge." For example: ```cpp ON_UUID CPlugInUserData::PlugInId() { // Copied from PlugIn1PlugIn.cpp // {F8B054CD-19A3-4D46-AB7E-DB3E8EF8CF5B} static const GUID PlugIn1PlugIn_UUID = { 0xF8B054CD, 0x19A3, 0x4D46, { 0xAB, 0x7E, 0xDB, 0x3E, 0x8E, 0xF8, 0xCF, 0x5B } }; return PlugIn1PlugIn_UUID; } CPlugInUserData::CPlugInUserData() { m_userdata_uuid = CPlugInUserData::Id(); /* CRITICAL: m_application_uuid must be assigned the uuid of the plugin that will be responsible for reading and writing our user data. In this example, we'll use PlugIn1 as our primary plugin. */ m_application_uuid = CPlugInUserData::PlugInId(); m_userdata_copycount = 1; // enable copying // initialize your data members here m_point.Set(0.0, 0.0, 0.0); m_string = L""; } ``` ## Related Topics - [Attaching User Data to Brep Components](/samples/cpp/attaching-user-data-to-brep-components) -------------------------------------------------------------------------------- # User Data Source: https://developer.rhino3d.com/en/samples/rhinocommon/user-data/ RhinoCommon object plugin user data ```cs // You must define a Guid attribute for your user data derived class // in order to support serialization. Every custom user data class // needs a custom Guid [Guid("7098A105-CD3C-4192-A9AC-0F21017098DC")] public class MyCustomData : Rhino.DocObjects.Custom.UserData { public int IntegerData{ get; set; } public string StringData {get; set;} // Your UserData class must have a public parameterless constructor public MyCustomData(){} public MyCustomData(int i, string s) { IntegerData = i; StringData = s; } public override string Description { get { return "Some Custom Properties"; } } public override string ToString() { return String.Format("integer={0}, string={1}", IntegerData, StringData); } protected override void OnDuplicate(Rhino.DocObjects.Custom.UserData source) { MyCustomData src = source as MyCustomData; if (src != null) { IntegerData = src.IntegerData; StringData = src.StringData; } } // return true if you have information to save public override bool ShouldWrite { get { // make up some rule as to if this should be saved in the 3dm file if (IntegerData > 0 && !string.IsNullOrEmpty(StringData)) return true; return false; } } protected override bool Read(Rhino.FileIO.BinaryArchiveReader archive) { Rhino.Collections.ArchivableDictionary dict = archive.ReadDictionary(); if (dict.ContainsKey("IntegerData") && dict.ContainsKey("StringData")) { IntegerData = (int)dict["IntegerData"]; StringData = dict["StringData"] as String; } return true; } protected override bool Write(Rhino.FileIO.BinaryArchiveWriter archive) { // you can implement File IO however you want... but the dictionary class makes // issues like versioning in the 3dm file a bit easier. If you didn't want to use // the dictionary for writing, your code would look something like. // // archive.Write3dmChunkVersion(1, 0); // archive.WriteInt(IntegerData); // archive.WriteString(StringData); var dict = new Rhino.Collections.ArchivableDictionary(1, "MyCustomData"); dict.Set("IntegerData", IntegerData); dict.Set("StringData", StringData); archive.WriteDictionary(dict); return true; } } partial class Examples { public static Rhino.Commands.Result Userdata(RhinoDoc doc) { Rhino.DocObjects.ObjRef objref; var rc = Rhino.Input.RhinoGet.GetOneObject("Select Face", false, Rhino.DocObjects.ObjectType.Surface, out objref); if (rc != Rhino.Commands.Result.Success) return rc; var face = objref.Face(); // See if user data of my custom type is attached to the geomtry // We need to use the underlying surface in order to get the user data // to serialize with the file. var ud = face.UnderlyingSurface().UserData.Find(typeof(MyCustomData)) as MyCustomData; if (ud == null) { // No user data found; create one and add it int i = 0; rc = Rhino.Input.RhinoGet.GetInteger("Integer Value", false, ref i); if (rc != Rhino.Commands.Result.Success) return rc; ud = new MyCustomData(i, "This is some text"); face.UnderlyingSurface().UserData.Add(ud); } else { RhinoApp.WriteLine("{0} = {1}", ud.Description, ud); } return Rhino.Commands.Result.Success; } } ``` ```python # No Python sample available ``` -------------------------------------------------------------------------------- # Using .NET Classes Source: https://developer.rhino3d.com/en/guides/rhinoscript/using-net-classes-in-vbs/ This guide discusses several .NET classes that work with RhinoScript. ## Overview Unlike other programming languages, VBScript lacks support of complex data structures. Sometimes this makes life a bit difficult. We need to code our own algorithms to achieve simple tasks like sorting or reversing an array. .NET has support for complex data structures which come with built-in functions for these simple tasks. So if we can use .NET data structure then we can eliminate the reinvention of the wheel for some of these tasks. Well, the good news is that some .NET libraries are exposed to COM and can be used in VBScript. Let's take a look at the most useful of these... ## ArrayList An [ArrayList](http://msdn.microsoft.com/en-us/library/system.collections.arraylist(v=vs.100).aspx) is an array whose size is dynamically increased when needed. Here is an example of its use: ```vbnet Set objArrayList = CreateObject("System.Collections.ArrayList") objArrayList.Add 6 objArrayList.Add 8 objArrayList.Add 2 objArrayList.Add 4 objArrayList.Add 1 objArrayList.Add 5 objArrayList.Add 3 objArrayList.Add 3 objArrayList.Add 7 'Convert to VBScript Array Rhino.Print Join(objArrayList.ToArray, ",") 'Return the element at a given index Rhino.Print objArrayList(0) 'Return the number of elements Rhino.Print objArrayList.Count 'Verifies an element exists or not. Rhino.Print objArrayList.Contains(5) 'Sort the elements objArrayList.Sort Rhino.Print Join(objArrayList.ToArray, ",") 'Reverse the order of all elements objArrayList.Reverse Rhino.Print Join(objArrayList.ToArray, ",") 'Remove a specific element objArrayList.Remove 8 'Remove element by index objArrayList.RemoveAt 6 'Remove all elements objArrayList.Clear ``` For VBScript `Dictionary` objects we don’t need to redim while appending or removing elements. Also, an element can be directly searched without iterating through each of them. Both of these features are available with `ArrayList` as well but one of the most important dictionary object features, Key-Value pair, is not in `ArrayList`. `SortedList` is an alternative if you want the power of both `ArrayList` and `Dictionary`. ## SortedList A [SortedList](http://msdn.microsoft.com/en-us/library/system.collections.sortedlist.aspx) a is sorted Dictionary. Every time we add or remove a key value pair in the dictionary, it automatically gets sorted by Key. You can also access elements based on its index (just like arrays) which makes this even more powerful. Here is an example: ```vbnet Set objSortedList = CreateObject("System.Collections.SortedList") objSortedList.Add "Point", 1 objSortedList.Add "Point Cloud", 2 objSortedList.Add "Curve", 4 objSortedList.Add "Surface", 8 objSortedList.Add "Polysurface", 16 objSortedList.Add "Mesh", 32 'Return the number of elements Rhino.Print objSortedList.Count 'Return a value by its key Rhino.Print objSortedList("Surface") 'Access all elements by index For i = 0 To objSortedList.Count - 1 Rhino.Print CStr(objSortedList.GetByIndex(i)) Next 'Verify a key exists Rhino.Print objSortedList.ContainsKey("Polysurface") 'Verify a value exists Rhino.Print objSortedList.ContainsValue(16) 'Return the index by key (zero based index) Rhino.Print objSortedList.IndexOfKey("Polysurface") 'Return the index by value Rhino.Print objSortedList.IndexOfValue(16) 'Remove an element by key objSortedList.Remove "Polysurface" 'Remove an element by index objSortedList.RemoveAt 0 'Remove all elements objSortedList.Clear ``` ## Stack A [Stack](http://msdn.microsoft.com/en-us/library/system.collections.stack.aspx) is a simple last-in, first-out (LIFO) non-generic collection of objects or data. By last-in, first-out, what we mean is if a set of objects is put into a stack, the last object that was put in will be the first object to be taken out. For example, in a restaurants with an automatic plate dispenser, the stack of plates are added to from the top. As each person takes a plate, the next plate in the stack becomes available. Here is an example of using `Stack`: ```vbnet Set objStack = CreateObject("System.Collections.Stack") 'Add elements to stack objStack.Push "Item_1" objStack.Push "Item_2" objStack.Push "Item_3" objStack.Push "Item_4" 'Iterate through each element For Each Item In objStack Rhino.Print Item Next 'Convert to VBScript array Rhino.Print Join(objStack.ToArray, ",") 'Return the number of elements Rhino.Print objStack.Count 'Verify an element exists Rhino.Print objStack.Contains("Item_2") 'Pop the last element Rhino.Print objStack.Pop 'Return the last in element without popping Rhino.Print objStack.Peek Rhino.Print objStack.Pop Rhino.Print objStack.Pop 'Remove all elements objStack.Clear ``` ## Queue Unlike a `Stack`, a [Queue](http://msdn.microsoft.com/en-us/library/system.collections.queue(v=vs.100).aspx) represents a first-in, first-out collection of objects or data. Here is an example: ```vbnet Set objQueue = CreateObject("System.Collections.Queue") 'Add elements to queue objQueue.Enqueue "Item_1" objQueue.Enqueue "Item_2" objQueue.Enqueue "Item_3" objQueue.Enqueue "Item_4" 'Iterate through each element For Each Item In objQueue Rhino.Print Item Next 'Convert to VBScript array Rhino.Print Join(objQueue.ToArray, ",") 'Return the number of elements Rhino.Print objQueue.Count 'Verify an element exists Rhino.Print objQueue.Contains("Item_2") 'Return the first element Rhino.Print objQueue.Dequeue 'Return the first element without removing it Rhino.Print objQueue.Peek Rhino.Print objQueue.Dequeue Rhino.Print objQueue.Dequeue 'Removes all elements objQueue.Clear ``` ## Related Topics - [ArrayList on MSDN](http://msdn.microsoft.com/en-us/library/system.collections.arraylist(v=vs.100).aspx) - [SortedList on MSDN](http://msdn.microsoft.com/en-us/library/system.collections.sortedlist.aspx) - [Stack on MSDN](http://msdn.microsoft.com/en-us/library/system.collections.stack.aspx) - [Queue on MSDN](http://msdn.microsoft.com/en-us/library/system.collections.queue(v=vs.100).aspx) -------------------------------------------------------------------------------- # Using ActiveX Controls Source: https://developer.rhino3d.com/en/guides/cpp/using-activex-controls/ This brief guide discusses how to use ActiveX controls in C/C++ plugins. ## Problem ActiveX controls placed in a simple dialog box will crash Rhino. ## Solution ActiveX, or OLE, controls work in Rhino plugins, as C/C++ plugin are simply regular MFC DLLs. For more information on MFC DLLs, read [MFC Technical Note 33](http://msdn.microsoft.com/en-us/library/hw85e4bb(v=VS.80).aspx) and [MFC Technical Note 58](http://msdn.microsoft.com/en-us/library/ft1t4bbc(v=VS.80).aspx) for more information. Also, you will need to call this function: ```cpp void AfxEnableControlContainer(); ``` in your `CWinApp`-derived object's `InitInstance()` member to enable support for containment of OLE controls. ## Related Topics - [MFC ActiveX Controls (on MSDN)](https://msdn.microsoft.com/en-us/library/k194shk8(v=VS.80).aspx) - [MFC Technical Note 33 (on MSDN)](http://msdn.microsoft.com/en-us/library/hw85e4bb(v=VS.80).aspx) - [MFC Technical Note 58](http://msdn.microsoft.com/en-us/library/ft1t4bbc(v=VS.80).aspx) -------------------------------------------------------------------------------- # Using NuGet Source: https://developer.rhino3d.com/en/guides/rhinocommon/using-nuget/ This guide describes how developers can use the NuGet packages available for RhinoCommon and Grasshopper. ## Why NuGet? In [previous](/guides/rhinocommon/your-first-plugin-windows/) [guides](/guides/rhinocommon/your-first-plugin-mac/) you’ve seen how to set up a project to develop a RhinoCommon Plugin or Grasshopper component. These guides relied on the Visual Studio Project Wizards that we publish to quickly get you going on plugin development. The wizards automatically reference the necessary assemblies to make RhinoCommon and Grasshopper SDKs available in your Visual Studio project. While this project setup should be fine for a number of cases, there might be some reasons to switch the RhinoCommon and Grasshopper assembly references to those which are published by [McNeel on NuGet](https://www.nuget.org/profiles/McNeel)... ### Advantages There are several potential advantages to using NuGet packages for RhinoCommon SDKs: * It's great for projects with multiple developers (or developers with multiple computers). No more references to `Grasshopper.dll` that include `C:\Users\\AppData\...`. * NuGet runs on Windows and Mac and is baked into Visual Studio (for Windows). * Are you using Continuous Integration (CI)? Your build servers can automatically download the correct version of the SDK before compiling and publishing your shiny new release. * You're probably already using it to install packages like [Json.NET](https://www.nuget.org/packages/newtonsoft.json). * You can target a lower version of RhinoCommon than you have installed to ensure full compatibility across all Rhino versions ### Potential Pitfalls NuGet makes it easy to compile plug-ins against versions of Rhino other than those installed on your computer. This is handy for backwards-compatible and/or cross-platform development. However, the fact that your Rhino installation and your RhinoCommon/Grasshopper references are "out of sync" can cause problems. * NuGet packages will need to be updated separately to Rhino * You _may_ have trouble debugging your plug-in if it was built against a version of RhinoCommon that is newer than the one included with Rhino[^a] ## Getting Started Note: You may wish to read Microsoft's guide to installing NuGet packages in Visual Studio in addition to this one. And how to install a Nuget Package in Visual Studio Code. We have a few NuGet packages available and you can install them like you would any other. The [RhinoCommon] package includes * *RhinoCommon.dll* * *Eto.dll* * *Rhino.UI.dll* The [Grasshopper] package depends[^1] on the RhinoCommon package _with the same version_ and includes * *Grasshopper.dll* * *GH_IO.dll* We also have a [RhinoWindows](https://www.nuget.org/packages/RhinoWindows) package. We're currently publishing new package versions for every public release of Rhino for Windows, including preleases (WIP, release candidate and beta). If you're developing for Rhino WIP for Mac, choose the latest 6.* package – RhinoCommon and Grasshopper are cross-platform! If you're searching for a version of one of our packages that corresponds to a prerelease version of Rhino then make sure you check "include prerelease". These packages are marked with a prerelease suffix in the version number, such as `-wip` or `-rc`. ### Step-by-Step (Windows) To switch to NuGet packages, follow these steps: 1. In Visual Studio, find the *Solution Explorer* and right-click on the *References* section of your project. Select *Manage NuGet Packages...* Alternatively, the same can be done through the Visual Studio *Project* menu, and choosing *Manage NuGet Packages...* ![Manage NuGet Packages - Windows](https://developer.rhino3d.com/images/using-nuget-01.png) 2. In the NuGet tab which appears, click on *Browse*. In the search box, type in *RhinoCommon*. You should see an entry for RhinoCommon and one for Grasshopper. If you are writing a Rhino Plugin or Grasshopper Add-on for Rhino WIP, ensure you check *Include prerelease*. If your project is a **RhinoCommon Plug-in**, select the [RhinoCommon] package. For Rhino WIP choose the *Latest prerelease* and click *Install*. NuGet will install[^2] *RhinoCommon.dll*, *Rhino.UI* and *Eto.dll*. If your project is a **Grasshopper Add-on**, select the [Grasshopper] package. For Grasshopper Add-ons in Rhino WIP choose the *Latest prerelease* and click *Install*. NuGet will install[^2] *Grasshopper.dll* and *GH_IO.dll* as well as the corresponding version of the RhinoCommon assemblies. ![Choose NuGet Packages - Windows](https://developer.rhino3d.com/images/using-nuget-02.png) 3. *(Optional)* The references created by these packages have `CopyLocal` set to `true`. Normally, it is a best practice to make sure that the references are not copied to the output directory, since they are included with Rhino. You can do this by selecting any of the following references if they exist in your project - *RhinoCommon*, *Eto*, *Rhino.UI*, *Grasshopper*, *GH_IO* - and, in the *Properties* window, set `CopyLocal` to `false`. The reason this step is *optional* is that we've included some MSBuild witchcraft that will ensure that `CopyLocal` is set to `false` when compiling your project, regardless of what it says in the *Properties* window. ![Copy Local](https://developer.rhino3d.com/images/using-nuget-03.png) ### Step-by-Step (Mac) Further Reading [NuGet in Visual Studio Code](https://code.visualstudio.com/docs/csharp/package-management) 1. In Visual Studio Code, open the Command Palette _(⌘ ⇧ P)_ and search nuget, choose *Add NuGet Package*. Alternatively, the same can be done through the Solution Explorer by right clicking the Project you wish to add the package to, and choosing *Add NuGet Package...* [C# Dev Kit](https://marketplace.visualstudio.com/items?itemName=ms-dotnettools.csdevkit)) ![Manage NuGet Packages - Mac](https://developer.rhino3d.com/images/using-nuget-04.png) 2. The command palette will appear at the top of Visual Studio Code. In the search box, type in *RhinoCommon* and press enter. You should see an entry for RhinoCommon and one for Grasshopper. ![Manage NuGet Packages - Mac](https://developer.rhino3d.com/images/using-nuget-05.png) 3. Now you can choose the version of the NuGet package you wish to target ![Choose NuGet Packages - Mac](https://developer.rhino3d.com/images/using-nuget-06.png) If you are writing a Rhino Plugin or Grasshopper Add-on for Rhino WIP, you will need to install a pre-release NuGet Package. At the time of writing time the C# Dev Kit does not include a way to show pre-release versions. You must use _dotnet add package RhinoCommon_ in the terminal, _([The RhinoCommon NuGet Page](https://www.nuget.org/packages/rhinocommon) has a handy copy paste to make this easier)_. Or you can use the [NuGet Gallery Plugin](https://marketplace.visualstudio.com/items?itemName=patcx.vscode-nuget-gallery) which offers this functionality. If your project is a **RhinoCommon Plug-in**, select the [RhinoCommon] package. NuGet will install *RhinoCommon.dll*, *Rhino.UI* and *Eto.dll* once you have selected a version. If your project is a **Grasshopper Add-on**, select the [Grasshopper] package. NuGet will install[^1] *Grasshopper.dll* and *GH_IO.dll* as well as the corresponding version of the RhinoCommon assemblies once you have selected a version. 4. Confirm your NuGet Packages installed correctly Check your Project in the Solution Explorer, you should see a new entry under dependencies > Packages ![Choose NuGet Packages - Mac](https://developer.rhino3d.com/images/using-nuget-07.png) ## Related topics - [Your First Plugin (Windows)](/guides/rhinocommon/your-first-plugin-windows) - [Your First Plugin (Mac)](/guides/rhinocommon/your-first-plugin-mac) **Footnotes** [^1]: This means that if you install the Grasshopper NuGet package, the matching RhinoCommon package will be installed automatically. [^2]: If your project already references one of the above assemblies, don't worry! NuGet will handle it. [^a]: Your plug-in will not load if it uses parts of the API which don't exist in the running version of Rhino. [RhinoCommon]: https://www.nuget.org/packages/rhinocommon [Grasshopper]: https://www.nuget.org/packages/grasshopper -------------------------------------------------------------------------------- # Using RhinoCommon from Python Source: https://developer.rhino3d.com/en/guides/rhinopython/using-rhinocommon-from-python/ This brief guide cover using RhinoCommon from Python. ## Overview Along with the RhinoScript style functions you will be able to use all of the classes in the .NET Framework, including the classes available in RhinoCommon. As a matter of fact, if you look at the source for the rhinoscriptsyntax functions, they are just python scripts that use RhinoCommon. This allows you to do some pretty amazing things inside of a python script. Many of the features that once could only be done in a .NET plugin can now be done in a python script For example, you can implement some custom drawing while a user is picking a point with the following script. This script draws a Red and Blue line connected to the point under the mouse cursor while the user is picking a point. ```py import Rhino import System.Drawing def GetPointDynamicDrawFunc( sender, args ): pt1 = Rhino.Geometry.Point3d(0,0,0) pt2 = Rhino.Geometry.Point3d(10,10,0) args.Display.DrawLine(pt1, args.CurrentPoint, System.Drawing.Color.Red, 2) args.Display.DrawLine(pt2, args.CurrentPoint, System.Drawing.Color.Blue, 2) # Create an instance of a GetPoint class and add a delegate for the DynamicDraw event gp = Rhino.Input.Custom.GetPoint() gp.DynamicDraw += GetPointDynamicDrawFunc gp.Get() ``` -------------------------------------------------------------------------------- # Using the sizeof operator with TCHAR and wchar_t Source: https://developer.rhino3d.com/en/guides/cpp/using-sizeof-with-tchar-wchar-t/ This guide outlines some common mistakes using sizeof when dealing with UNICODE strings. ## Discussion The `sizeof` keyword gives the amount of storage, in bytes, associated with a variable or a type. It does not return number of elements in an array, such as an array of characters. Mistakes using the `sizeof` operator when dealing with UNICODE strings is very common. For example: ```cpp char szBuffer[24]; GetWindowText( hWnd, szBuffer, sizeof(szBuffer) ); // OK wchar_t wszBuffer[24]; GetWindowText( hWnd, wszBuffer, sizeof(wszBuffer) ); // WRONG ``` In the above examples, the first block of code works because the char data type just happens to take up only one byte of storage. Thus, the `sizeof` operator, when used on an array of chars just happens to work. The other example is incorrect for a `wchar_t` requires two bytes of storage. Thus, you have just told the `GetWindowText` that the buffer you have passed in is bigger than it really is. This can crash your plugin. The following is the proper method for using the sizeof operator when using UNICODE text strings... ```cpp wchar_t wszBuffer[24]; GetWindowText( hWnd, wszBuffer, sizeof(wszBuffer)/sizeof(wchar_t) ); ``` If you find yourself writing the above statement frequently, you might consider adding the following macro to your project: ```cpp #define _countof(array) (sizeof(array)/sizeof(array[0])) wchar_t wszBuffer[24]; GetWindowText( hWnd, wszBuffer, _countof(wszBuffer) ); ``` The `TCHAR` data type definition is based on whether or not your plugins compile as MBCS or as UNICODE. Rhino 6 plugins are compiled as UNICODE. Thus, a TCHAR in Rhino 6 will be a `wchar_t`. To be safe in all cases, you should use the following convention when dealing with TCHARs: ```cpp TCHAR tchBuffer[24]; GetWindowText( hWnd, tchBuffer, sizeof(tchBuffer)/sizeof(TCHAR) ); ``` In doing this, your code will be safe when compiled as either MBCS or UNICODE. -------------------------------------------------------------------------------- # VBScript RegExp Objects Source: https://developer.rhino3d.com/en/guides/rhinopython/python-regexp-objects/ This guide discusses the VBScript RegExp object. ## Overview The VBScript `RegExp` object matches strings against special text patterns, called regular expressions. The `InStr` function can be used to search a string to see if it contains another string. The `RegExp` object provides a far more sophisticated string searching facility by using regular expressions. A regular expression is a string that describes a match pattern. The match pattern provides a template that can be used to test another string, the search string, for a matching sub-string. In its simplest form, the match pattern string is just a sequence of characters that must be matched. For example, the pattern `"fred"` matches this exact sequence of characters and only this sequence. More sophisticated regular expressions can match against items such as file names, path names, and Internet URLs. Thus, the `RegExp` object is frequently used to validate data for correct form and syntax. ## Using RegExp To test a search string against a match pattern, create a `RegExp` object and set the match pattern. Then use the `.Test` method to test for a match. For example: ```vbnet Dim oRE, bMatch Set oRE = New RegExp oRE.Pattern = "fred" bMatch = oRE.Test("His name was fred brown") ``` Regular expression objects are created using `the` New keyword. This is an anomaly of VBScript, because it is the only object, apart from user-defined objects, that is created in this manner. Once created in this way, the methods and properties of the object are accessed normally. The `.Pattern` property defines the match pattern, and the `.Test` method tests the supplied string against this match pattern, returning True if the match pattern is found within the supplied string. In the preceding example, `bMatch` is set to True as there is clearly a match. The `.Execute` method of the `RegExp` object also checks for a pattern match, but it returns a `Matches` collection object that contains detailed information on each pattern match. The `Matches` object is a normal collection object containing a `.Count` property and a default `.Item` property. Each item in the `Matches` object is a `Match` object that describes a specific pattern match. After the `.Execute` method is invoked, the returned `Matches` object contains a `Match` object for each match located in the search string. Each `Match` item has three properties. The `.Value` property contains the actual text in the search string that was matched. The `.FirstIndex` property contains the index of the first character of the match in the search string. The `.Length` property contains the length of the matched string. Unlike other VBScript string indexes, the `.FirstIndex` property uses 0 as the index of the first character in the string. Therefore, always add one to this value before using it with other VBScript string functions. By default, the `.Execute` method only matches the first occurrence of the pattern. If the `.Global` property is set to True, the method matches all the occurrences in the string and returns a `Match` object for each match. For example: ```vbnet Dim oRE, oMatches Set oRE = New RegExp oRE.Pattern = "two" oRE.Global = True Set oMatches = oRE.Execute("two times three equals three times two") For Each oMatch In oMatches Rhino.Print "Match: " & oMatch.Value & " At: " & CStr(oMatch.FirstIndex + 1) Next ``` This example lists all the matches against the pattern `"two"` in the specified string. Simple match patterns, such as those shown in the previous example, provide no additional functionality over that provided by the `InStr` function. Much more powerful searches are available, however, by using the special regular expression features of the search pattern. These are most easily explored using the sample script shown below: ```vbnet Sub TestRegExp(sPattern, sSearch) ' Do the regular expression match Dim oRE, oMatches Set oRE = New RegExp oRE.Global = True oRE.IgnoreCase = True oRE.Pattern = sPattern Set oMatches = oRE.Execute(sSearch) ' Now process all the matches (if any) Dim oMatch Rhino.Print "Pattern String: " & Chr(34) & sPattern & Chr(34) Rhino.Print "Search String: " & Chr(34) & sSearch & Chr(34) & VbCrLf Rhino.Print "Matches: " & CStr(oMatches.Count) Rhino.Print " " & sSearch For Each oMatch In oMatches Rhino.Print " " & String(oMatch.FirstIndex, " ") & String(oMatch.Length, "^") Next End Sub ``` To use the script, call the subroutine two arguments. Enter a match pattern as the first argument and a search string as the second argument. If either argument contains spaces or special shell characters, enclose the argument in double quotes. The script uses the `.Execute` method of the `RegExp` object to locate all matches and then displays these matches graphically. Use this script to experiment with the various advanced regular expression features described in the following paragraphs. (Several of the preceding `Rhino.Print` statements use the expression `Chr( )`. This simply evaluates to a double quote character.) ## Regular Expression syntax Within the match pattern, letters, digits, and most punctuation simply match a corresponding character in the search string. A sequence of these characters matches the equivalent sequence in the search string. However, some characters within the match pattern have special meaning. For example, the `"."` (period) character matches any character except a new-line character. Thus, the match pattern `"a.c"` matches `"abc"` or `"adc"` or `"a$c"`. The match pattern `".."` matches any sequence of two characters. The special character `"^"` matches the start of the string. Thus, the match pattern `"^abc"` matches the string `"abc"`, but not `"123abc"` because this string does not begin with `"abc"`. Similarly, the special character `"$"` matches the end of the string, and so the pattern `"red$"` matches the search string `"fred"`, but not `"fred brown"`. Using both these characters allows a regular expression to match complete strings. For example, the pattern `"abc"` matches `"123 abc that"` and any other string containing `"abc"`, whereas the pattern `"^abc$"` only matches the exact string `"abc"`. The three characters `*`, `+`, and `?` are called modifiers. These characters modify the preceding character. The `*` modifier matches the preceding character zero or more times, the `+` modifier matches the preceding character one or more times, and the `"?"` modifier matches the preceding character zero or one time. For example, the pattern "a+" matches any sequence of one or more `"a"` characters, whereas the pattern `"ab*c"` matches `"abc"`, `"abbbbbc"`, and `"ac"`, but not `"adc"` or `"ab"`. A list of characters enclosed in brackets is called a range and matches a single character in the search string with any of the characters in brackets. For example, the pattern `"[0123456789]"` matches any digit character in the search string. Ranges such as this, where the characters are sequential, can be abbreviated as `"[0-9]"`. For example, the pattern `"[0-9a-zA-Z_]"` matches a digit, letter (either upper or lower case), or the underscore character. If the first character of the range is `"^"`, the range matches all those characters that are not listed. For example, `"[^0-9]"` matches any non-digit character. Ranges can be combined with modifiers. For example, the pattern `"[0-9]+"` matches any sequence of one or more digit characters. The pattern `"[a-zA-Z][a-zA-Z_0-9]*"` matches a valid VBScript variable name, because it only matches sequences that start with a letter and are followed by zero or more letters, digits, or underscore characters. To match a character an exact number of times, follow the character in the pattern by a count of matches required enclosed in braces. For example, the pattern `"a{3}"` matches exactly three `"a"` characters, and it is equivalent to the pattern `"aaa"`. If a comma follows the count, the character is matched at least that number of times. For example, `"a{5,}"` matches five or more `"a"` characters. Finally, use two counts to specify a lower and upper bound. For example, the pattern `"a{4,8}"` matches between four and eight `"a"` characters. As with other modifiers, the pattern count modifier can be combined with ranges. For example, the pattern `"[0-9]{4}"` matches exactly four digits. Use parentheses in the match pattern to group individual items together. Modifiers can then be applied to the entire group of items. For example, the pattern `"(abc)+"` matches any number of repeats of the sequence `"abc"`. The pattern `"(a[0-9]c){1,2}"` matches strings such as `"a0c"` and `"a4ca5c"`. There is a difference between matching the pattern `"abc"` and the pattern `"(abc)+"` using the `RegExp` object. Matching the pattern `"abc"` against the search string `"abcabcabc"` generates three individual matches and results in three `Match` objects in the Matches collection. Matching the pattern `"(abc)+"` against the same string generates one match that matches the entire string. The `"I"` vertical bar character separates lists of alternate sub-expressions. For example, the pattern `"abIac"` matches the strings `"ab"` or `"ac"`. The vertical bar separates entire regular expressions. The pattern `"^abIac$"` matches either the string `"ab"` at the start of the search string or the string `"ac"` at the end of the search string. Use parentheses to specify alternates within a larger pattern. For example, the pattern `"^(abIac)$"` matches the exact strings `"ab"` or `"ac"` only. To match any of the special characters literally in a pattern, they must be preceded by a back-slash character, which is known as an escape. For example, to match a literal asterisk, use `"\*"` in the pattern. To match a back-slash itself, use two back-slash characters in the match pattern. The following characters must be escaped when used literally within a match pattern: ```regex . (period) * + ? \ ( ) [ ] { } ^ $ ``` There are also several additional escape sequences that provide useful shorthand for more complex patterns: - The `"\d"` escape matches any digit, and it is equivalent to `"[0-9]"`. The `"\D"` escape matches any non-digit, and it is equivalent to `"[^0-9]"`. - The `"\w"` escape matches any word character and is equivalent to `"[0-9a-zA-Z_]"`, and the `"\W"` escape matches any non-word character. - The `"\b"` escape matches a word boundary, which is the boundary between any word character and a non-word character, whereas `"\B"` matches a non-word boundary. - The `"\s"` escape matches any whitespace character, including space, tab, new-line, and so on, whereas the `"\S"` escape matches any non-whitespace character. Finally, certain escapes can match non-printing characters, as follows: - The `"\f"` escape matches a form-feed character. - The `"\n"` escape matches a new-line character. - The `"\r"` escape matches a carriage-return character. - The `"\t"` escape matches a tab character. - The `"\v"` escape matches a vertical tab character. - The `"\onn"` escape matches a character whose octal code is `nn`. - The `"\xnn"` escape matches a character whose hexadecimal code is `nn`. The special escape `"\n"`, where n is a digit, matches the search text previously matched by a sub-expression in parentheses. Each sub-expression in a match pattern is numbered from left to right, and the escape `"\n"` matches the nth sub-expression. For example, the pattern `"(..)\1"` matches any two character sequence that repeats twice, such as `"abab"`. The first sub-expression matches the first two characters, `"ab"`, and the `"\1"` escape then matches these same two characters again. ## RegExp Example Regular expression match patterns provide a powerful way to validate the syntax of strings. For example, a script can obtain strings from a formatted text file and parse it into recognizable tokens. A regular expression can be used for this purpose. This script is simplistic in that it simply breaks apart each argument at the `"="` sign - no attempt is made to check if the name and value parts are valid. The following regular expression matches these command line arguments: ```regex [^=]+=.* ``` This match pattern is interpreted as follows: The range `"[^=]"` matches any character that is not an `"="` sign. The `"+"` modifier following the range means that the pattern matches any set of one or more characters that are not `"="` signs. The next `"="` sign in the pattern matches a literal `"="` sign (that is, the separator between the name and value parts). Finally, the `".*"` pattern matches any sequence of zero or more characters. Parentheses can be used to make the meaning more clear. For example: ```regex ([^=]+)=(.*) ``` This example is identical to the previous example, but the parentheses help make the individual parts of the pattern more understandable. Typically, the name in the previous example should be restricted to a valid name. We define valid names as a letter followed by zero or more letters, digits, or underscores. This yields a new regular expression as follows: ```regex ([a-zA-Z]\w*)=(.*) ``` Here, the pattern `"[a-zA-Z]"` matches any single letter. Then the pattern `"\w*"` matches zero or more letters, digits, or underscores, using the `"\w"` escape as a shorthand for `"[0-9a-zA-Z]"`. The previous regular expression does not allow spaces before or after the name. For example, only the first of these strings matches the expression: ``` curve=123456 curve = 575888 curve = 5544 ``` The following regular expression corrects this by adding optional leading and trailing whitespace around the name: ```regex (\s*)([a-zA-Z]\w*)(\s*)=(.*) ``` The new addition, `"\s*"`, matches zero or more whitespace characters. ## The .Replace Method Matching patterns against strings allows a script to determine if a string follows a specific syntax, as shown in the previous examples. The `RegExp` object can also be used to assist in modifying the search string. This facility is provided by the `.Replace` method, which takes two arguments: the search string and a replacement string, and returns a new string in which all matches in the search string are replaced by the replacement string. For example: ```vbnet Set oRE = New RegExp oRE.Global = True oRE.Pattern = "a" Wscript.Echo oRE.Replace("all a's are replaced", "X") ``` In this example, any `"a"` character in the search string is replaced by an `"X"` character. The pattern and replacement strings do not need to be the same length. For example: ```vbnet oRE.Pattern = "\s+" Wscript.Echo oRE.Replace("compress all whitespace ", " ") ``` Here, all sequences of whitespace are replaced by a single space character. Within the replacement string, the special character sequence `"$n"` is allowed. If present, this sequence is replaced by the text matched by the nth sub-expression in the pattern. For example: ```vbnet oRE.Pattern = "^\s*(\w+)\s*=\s*(\w+)\s*$" sSearch = " this = that" Rhino.Print oRE.Replace(sSearch, "$1,$2") ``` This example works as follows: The pattern contains both the start of string `"^"` and end of string `"$"` characters. This means that the pattern either matches the entire search string or not at all. The pattern matches any whitespace and sequence of one or more word characters, more whitespace, an `"="` sign, more whitespace, another set of word characters, and finally more whitespace. Both the word patterns are in parentheses, marking them as sub-expressions 1 and 2 respectively. When the `.Replace` method is invoked, it matches successfully against the entire search string. This means that the replacement string replaces the entire contents of the search string, because this entire string matched the regular expression. Thus, the string returned by `.Replace` is simply the replacement string. The replacement string is `"$1,$2"`. As noted, the two sub-expressions in the pattern contain the two words matched in the search string, which in this case are `"this"` and `"that"`. Thus, the replacement string becomes `"this,that"`, and this is the value returned. The result of this processing is that the `RegExp` object has validated the search string against the match pattern and extracted the required elements from the search string. -------------------------------------------------------------------------------- # VBScript RegExp Objects Source: https://developer.rhino3d.com/en/guides/rhinoscript/vbscript-regexp-objects/ This guide discusses the VBScript RegExp object. ## Overview The VBScript `RegExp` object matches strings against special text patterns, called regular expressions. The `InStr` function can be used to search a string to see if it contains another string. The `RegExp` object provides a far more sophisticated string searching facility by using regular expressions. A regular expression is a string that describes a match pattern. The match pattern provides a template that can be used to test another string, the search string, for a matching sub-string. In its simplest form, the match pattern string is just a sequence of characters that must be matched. For example, the pattern `"fred"` matches this exact sequence of characters and only this sequence. More sophisticated regular expressions can match against items such as file names, path names, and Internet URLs. Thus, the `RegExp` object is frequently used to validate data for correct form and syntax. ## Using RegExp To test a search string against a match pattern, create a `RegExp` object and set the match pattern. Then use the `.Test` method to test for a match. For example: ```vbnet Dim oRE, bMatch Set oRE = New RegExp oRE.Pattern = "fred" bMatch = oRE.Test("His name was fred brown") ``` Regular expression objects are created using `the` New keyword. This is an anomaly of VBScript, because it is the only object, apart from user-defined objects, that is created in this manner. Once created in this way, the methods and properties of the object are accessed normally. The `.Pattern` property defines the match pattern, and the `.Test` method tests the supplied string against this match pattern, returning True if the match pattern is found within the supplied string. In the preceding example, `bMatch` is set to True as there is clearly a match. The `.Execute` method of the `RegExp` object also checks for a pattern match, but it returns a `Matches` collection object that contains detailed information on each pattern match. The `Matches` object is a normal collection object containing a `.Count` property and a default `.Item` property. Each item in the `Matches` object is a `Match` object that describes a specific pattern match. After the `.Execute` method is invoked, the returned `Matches` object contains a `Match` object for each match located in the search string. Each `Match` item has three properties. The `.Value` property contains the actual text in the search string that was matched. The `.FirstIndex` property contains the index of the first character of the match in the search string. The `.Length` property contains the length of the matched string. Unlike other VBScript string indexes, the `.FirstIndex` property uses 0 as the index of the first character in the string. Therefore, always add one to this value before using it with other VBScript string functions. By default, the `.Execute` method only matches the first occurrence of the pattern. If the `.Global` property is set to True, the method matches all the occurrences in the string and returns a `Match` object for each match. For example: ```vbnet Dim oRE, oMatches Set oRE = New RegExp oRE.Pattern = "two" oRE.Global = True Set oMatches = oRE.Execute("two times three equals three times two") For Each oMatch In oMatches Rhino.Print "Match: " & oMatch.Value & " At: " & CStr(oMatch.FirstIndex + 1) Next ``` This example lists all the matches against the pattern `"two"` in the specified string. Simple match patterns, such as those shown in the previous example, provide no additional functionality over that provided by the `InStr` function. Much more powerful searches are available, however, by using the special regular expression features of the search pattern. These are most easily explored using the sample script shown below: ```vbnet Sub TestRegExp(sPattern, sSearch) ' Do the regular expression match Dim oRE, oMatches Set oRE = New RegExp oRE.Global = True oRE.IgnoreCase = True oRE.Pattern = sPattern Set oMatches = oRE.Execute(sSearch) ' Now process all the matches (if any) Dim oMatch Rhino.Print "Pattern String: " & Chr(34) & sPattern & Chr(34) Rhino.Print "Search String: " & Chr(34) & sSearch & Chr(34) & VbCrLf Rhino.Print "Matches: " & CStr(oMatches.Count) Rhino.Print " " & sSearch For Each oMatch In oMatches Rhino.Print " " & String(oMatch.FirstIndex, " ") & String(oMatch.Length, "^") Next End Sub ``` To use the script, call the subroutine two arguments. Enter a match pattern as the first argument and a search string as the second argument. If either argument contains spaces or special shell characters, enclose the argument in double quotes. The script uses the `.Execute` method of the `RegExp` object to locate all matches and then displays these matches graphically. Use this script to experiment with the various advanced regular expression features described in the following paragraphs. (Several of the preceding `Rhino.Print` statements use the expression `Chr( )`. This simply evaluates to a double quote character.) ## Regular Expression syntax Within the match pattern, letters, digits, and most punctuation simply match a corresponding character in the search string. A sequence of these characters matches the equivalent sequence in the search string. However, some characters within the match pattern have special meaning. For example, the `"."` (period) character matches any character except a new-line character. Thus, the match pattern `"a.c"` matches `"abc"` or `"adc"` or `"a$c"`. The match pattern `".."` matches any sequence of two characters. The special character `"^"` matches the start of the string. Thus, the match pattern `"^abc"` matches the string `"abc"`, but not `"123abc"` because this string does not begin with `"abc"`. Similarly, the special character `"$"` matches the end of the string, and so the pattern `"red$"` matches the search string `"fred"`, but not `"fred brown"`. Using both these characters allows a regular expression to match complete strings. For example, the pattern `"abc"` matches `"123 abc that"` and any other string containing `"abc"`, whereas the pattern `"^abc$"` only matches the exact string `"abc"`. The three characters `*`, `+`, and `?` are called modifiers. These characters modify the preceding character. The `*` modifier matches the preceding character zero or more times, the `+` modifier matches the preceding character one or more times, and the `"?"` modifier matches the preceding character zero or one time. For example, the pattern "a+" matches any sequence of one or more `"a"` characters, whereas the pattern `"ab*c"` matches `"abc"`, `"abbbbbc"`, and `"ac"`, but not `"adc"` or `"ab"`. A list of characters enclosed in brackets is called a range and matches a single character in the search string with any of the characters in brackets. For example, the pattern `"[0123456789]"` matches any digit character in the search string. Ranges such as this, where the characters are sequential, can be abbreviated as `"[0-9]"`. For example, the pattern `"[0-9a-zA-Z_]"` matches a digit, letter (either upper or lower case), or the underscore character. If the first character of the range is `"^"`, the range matches all those characters that are not listed. For example, `"[^0-9]"` matches any non-digit character. Ranges can be combined with modifiers. For example, the pattern `"[0-9]+"` matches any sequence of one or more digit characters. The pattern `"[a-zA-Z][a-zA-Z_0-9]*"` matches a valid VBScript variable name, because it only matches sequences that start with a letter and are followed by zero or more letters, digits, or underscore characters. To match a character an exact number of times, follow the character in the pattern by a count of matches required enclosed in braces. For example, the pattern `"a{3}"` matches exactly three `"a"` characters, and it is equivalent to the pattern `"aaa"`. If a comma follows the count, the character is matched at least that number of times. For example, `"a{5,}"` matches five or more `"a"` characters. Finally, use two counts to specify a lower and upper bound. For example, the pattern `"a{4,8}"` matches between four and eight `"a"` characters. As with other modifiers, the pattern count modifier can be combined with ranges. For example, the pattern `"[0-9]{4}"` matches exactly four digits. Use parentheses in the match pattern to group individual items together. Modifiers can then be applied to the entire group of items. For example, the pattern `"(abc)+"` matches any number of repeats of the sequence `"abc"`. The pattern `"(a[0-9]c){1,2}"` matches strings such as `"a0c"` and `"a4ca5c"`. There is a difference between matching the pattern `"abc"` and the pattern `"(abc)+"` using the `RegExp` object. Matching the pattern `"abc"` against the search string `"abcabcabc"` generates three individual matches and results in three `Match` objects in the Matches collection. Matching the pattern `"(abc)+"` against the same string generates one match that matches the entire string. The `"I"` vertical bar character separates lists of alternate sub-expressions. For example, the pattern `"abIac"` matches the strings `"ab"` or `"ac"`. The vertical bar separates entire regular expressions. The pattern `"^abIac$"` matches either the string `"ab"` at the start of the search string or the string `"ac"` at the end of the search string. Use parentheses to specify alternates within a larger pattern. For example, the pattern `"^(abIac)$"` matches the exact strings `"ab"` or `"ac"` only. To match any of the special characters literally in a pattern, they must be preceded by a back-slash character, which is known as an escape. For example, to match a literal asterisk, use `"\*"` in the pattern. To match a back-slash itself, use two back-slash characters in the match pattern. The following characters must be escaped when used literally within a match pattern: ```regex . (period) * + ? \ ( ) [ ] { } ^ $ ``` There are also several additional escape sequences that provide useful shorthand for more complex patterns: - The `"\d"` escape matches any digit, and it is equivalent to `"[0-9]"`. The `"\D"` escape matches any non-digit, and it is equivalent to `"[^0-9]"`. - The `"\w"` escape matches any word character and is equivalent to `"[0-9a-zA-Z_]"`, and the `"\W"` escape matches any non-word character. - The `"\b"` escape matches a word boundary, which is the boundary between any word character and a non-word character, whereas `"\B"` matches a non-word boundary. - The `"\s"` escape matches any whitespace character, including space, tab, new-line, and so on, whereas the `"\S"` escape matches any non-whitespace character. Finally, certain escapes can match non-printing characters, as follows: - The `"\f"` escape matches a form-feed character. - The `"\n"` escape matches a new-line character. - The `"\r"` escape matches a carriage-return character. - The `"\t"` escape matches a tab character. - The `"\v"` escape matches a vertical tab character. - The `"\onn"` escape matches a character whose octal code is `nn`. - The `"\xnn"` escape matches a character whose hexadecimal code is `nn`. The special escape `"\n"`, where n is a digit, matches the search text previously matched by a sub-expression in parentheses. Each sub-expression in a match pattern is numbered from left to right, and the escape `"\n"` matches the nth sub-expression. For example, the pattern `"(..)\1"` matches any two character sequence that repeats twice, such as `"abab"`. The first sub-expression matches the first two characters, `"ab"`, and the `"\1"` escape then matches these same two characters again. ## RegExp Example Regular expression match patterns provide a powerful way to validate the syntax of strings. For example, a script can obtain strings from a formatted text file and parse it into recognizable tokens. A regular expression can be used for this purpose. This script is simplistic in that it simply breaks apart each argument at the `"="` sign - no attempt is made to check if the name and value parts are valid. The following regular expression matches these command line arguments: ```regex [^=]+=.* ``` This match pattern is interpreted as follows: The range `"[^=]"` matches any character that is not an `"="` sign. The `"+"` modifier following the range means that the pattern matches any set of one or more characters that are not `"="` signs. The next `"="` sign in the pattern matches a literal `"="` sign (that is, the separator between the name and value parts). Finally, the `".*"` pattern matches any sequence of zero or more characters. Parentheses can be used to make the meaning more clear. For example: ```regex ([^=]+)=(.*) ``` This example is identical to the previous example, but the parentheses help make the individual parts of the pattern more understandable. Typically, the name in the previous example should be restricted to a valid name. We define valid names as a letter followed by zero or more letters, digits, or underscores. This yields a new regular expression as follows: ```regex ([a-zA-Z]\w*)=(.*) ``` Here, the pattern `"[a-zA-Z]"` matches any single letter. Then the pattern `"\w*"` matches zero or more letters, digits, or underscores, using the `"\w"` escape as a shorthand for `"[0-9a-zA-Z]"`. The previous regular expression does not allow spaces before or after the name. For example, only the first of these strings matches the expression: ``` curve=123456 curve = 575888 curve = 5544 ``` The following regular expression corrects this by adding optional leading and trailing whitespace around the name: ```regex (\s*)([a-zA-Z]\w*)(\s*)=(.*) ``` The new addition, `"\s*"`, matches zero or more whitespace characters. ## The .Replace Method Matching patterns against strings allows a script to determine if a string follows a specific syntax, as shown in the previous examples. The `RegExp` object can also be used to assist in modifying the search string. This facility is provided by the `.Replace` method, which takes two arguments: the search string and a replacement string, and returns a new string in which all matches in the search string are replaced by the replacement string. For example: ```vbnet Set oRE = New RegExp oRE.Global = True oRE.Pattern = "a" Wscript.Echo oRE.Replace("all a's are replaced", "X") ``` In this example, any `"a"` character in the search string is replaced by an `"X"` character. The pattern and replacement strings do not need to be the same length. For example: ```vbnet oRE.Pattern = "\s+" Wscript.Echo oRE.Replace("compress all whitespace ", " ") ``` Here, all sequences of whitespace are replaced by a single space character. Within the replacement string, the special character sequence `"$n"` is allowed. If present, this sequence is replaced by the text matched by the nth sub-expression in the pattern. For example: ```vbnet oRE.Pattern = "^\s*(\w+)\s*=\s*(\w+)\s*$" sSearch = " this = that" Rhino.Print oRE.Replace(sSearch, "$1,$2") ``` This example works as follows: The pattern contains both the start of string `"^"` and end of string `"$"` characters. This means that the pattern either matches the entire search string or not at all. The pattern matches any whitespace and sequence of one or more word characters, more whitespace, an `"="` sign, more whitespace, another set of word characters, and finally more whitespace. Both the word patterns are in parentheses, marking them as sub-expressions 1 and 2 respectively. When the `.Replace` method is invoked, it matches successfully against the entire search string. This means that the replacement string replaces the entire contents of the search string, because this entire string matched the regular expression. Thus, the string returned by `.Replace` is simply the replacement string. The replacement string is `"$1,$2"`. As noted, the two sub-expressions in the pattern contain the two words matched in the search string, which in this case are `"this"` and `"that"`. Thus, the replacement string becomes `"this,that"`, and this is the value returned. The result of this processing is that the `RegExp` object has validated the search string against the match pattern and extracted the required elements from the search string. -------------------------------------------------------------------------------- # VBScript Statements Source: https://developer.rhino3d.com/en/guides/rhinoscript/vbscript-statements/ This guide presents an overview of VBScript statements. ## Overview Many scripting and programming languages, such as JScript, C#, and C++, make no attempt to match the code that is run with the actual physical lines typed into the text editor. This is because they not recognize the end of a line of code until it sees the termination character (in these cases, the semicolon). Thus, the actual physical lines of type taken up by the code are irrelevant. By contrast, VBScript uses the carriage return instead of a special line termination character. To end a statement in VBScript, you do not have to type in a semicolon or other special character; you simply press Enter. For example, this code will generate a syntax error: ```vbnet Set objFSO = CreateObject("Scripting.FileSystemObject") ``` This will not: ```vbnet Set objFSO = CreateObject("Scripting.FileSystemObject") ``` ## Details In general, the lack of a required statement termination character simplifies script writing in VBScript. There is, however, one complication: To enhance readability, it is recommended that you limit the length of any single line of code to 80 characters. What happens, then, if you have a line of code that contains 100 characters? Although it might seem like the obvious solution, you cannot split a statement into multiple lines simply by entering a carriage return. For example, the following code snippet returns a run-time error in VBScript because a statement was split by using Enter. ```vbnet strMessageToDisplay = strUserFirstName & " " & strUserMiddleInitial & " " & strUserLastName Rhino.Print strMessageToDisplay ``` You cannot split a statement into multiple lines in VBScript by pressing Enter because VBScript sees a carriage return as marking the end of a statement. In the preceding example, VBScript interprets the first line as the first statement in the script. Next, it interprets the second line as the second statement in the script, and the error occurs because strUserLastName is not a valid VBScript statement. Instead, use the underscore (`_`) to indicate that a statement is continued on the next line. In the revised version of the script, a blank space and an underscore indicate that the statement that was started on line 1 is continued on line 2. To make it more apparent that line 2 is a continuation of line 1, line 2 is also indented four spaces. (This was done for the sake of readability, but you do not have to indent continued lines.) ```vbnet strMessageToDisplay = strUserFirstName & " " & strUserMiddleInitial & " " _ & strUserLastName Rhino.Print strMessageToDisplay ``` Line continuation is more complex when you try to split a statement inside a set of quotation marks. For example, suppose you split this statement using a blank space and an underscore: ```vbnet strMessage = "If you ask me anything I don't know, _ I'm not going to answer." Rhino.Print strMessage ``` If you run this script, you will encounter a run-time error because the line continuation character has been placed inside a set of quotation marks (and is therefore considered part of the string). To split this statement: 1. Close the first line with quotation marks, and then insert the blank space and the underscore. 1. Use an ampersand at the beginning of the second line. This indicates that line two is a continuation of the interrupted string in line 1. 1. Add quotation marks before continuing the statement. These quotation marks indicate that this line should be included as part of the quoted string started on the previous line. Without the quotation marks, the script engine would interpret the continued line as a VBScript statement. Because this is not a valid VBScript statement, an error would occur. The revised statement looks like this: ```vbnet strMessage = "If you ask me anything I don't know, " _ & " I'm not going to answer." Rhino.Print strMessage ``` When splitting statements in this fashion, be careful to insert spaces in the proper location. -------------------------------------------------------------------------------- # Viewport Resolution Source: https://developer.rhino3d.com/en/samples/rhinocommon/viewport-resolution/ Print Active Viewport Resolution ```cs partial class Examples { public static Result ViewportResolution(RhinoDoc doc) { var active_viewport = doc.Views.ActiveView.ActiveViewport; RhinoApp.WriteLine("Name = {0}: Width = {1}, Height = {2}", active_viewport.Name, active_viewport.Size.Width, active_viewport.Size.Height); return Result.Success; } } ``` ```python from scriptcontext import doc activeViewport = doc.Views.ActiveView.ActiveViewport print("Name = {0}: Width = {1}, Height = {2}".format( activeViewport.Name, activeViewport.Size.Width, activeViewport.Size.Height)) ``` -------------------------------------------------------------------------------- # Visual Analysis Modes Source: https://developer.rhino3d.com/en/samples/rhinocommon/visual-analysis-modes/ Demonstrates how to set the visual analysis mode to Z analysis for user-specified objects. ```cs partial class Examples { public static Rhino.Commands.Result AnalysisMode_on(RhinoDoc doc) { // make sure our custom visual analysis mode is registered var zmode = Rhino.Display.VisualAnalysisMode.Register(typeof(ZAnalysisMode)); const ObjectType filter = Rhino.DocObjects.ObjectType.Surface | Rhino.DocObjects.ObjectType.PolysrfFilter | Rhino.DocObjects.ObjectType.Mesh; Rhino.DocObjects.ObjRef[] objs; var rc = Rhino.Input.RhinoGet.GetMultipleObjects("Select objects for Z analysis", false, filter, out objs); if (rc != Rhino.Commands.Result.Success) return rc; int count = 0; for (int i = 0; i < objs.Length; i++) { var obj = objs[i].Object(); // see if this object is alreay in Z analysis mode if (obj.InVisualAnalysisMode(zmode)) continue; if (obj.EnableVisualAnalysisMode(zmode, true)) count++; } doc.Views.Redraw(); RhinoApp.WriteLine("{0} objects were put into Z-Analysis mode.", count); return Rhino.Commands.Result.Success; } public static Rhino.Commands.Result AnalysisMode_off(RhinoDoc doc) { var zmode = Rhino.Display.VisualAnalysisMode.Find(typeof(ZAnalysisMode)); // If zmode is null, we've never registered the mode so we know it hasn't been used if (zmode != null) { foreach (Rhino.DocObjects.RhinoObject obj in doc.Objects) { obj.EnableVisualAnalysisMode(zmode, false); } doc.Views.Redraw(); } RhinoApp.WriteLine("Z-Analysis is off."); return Rhino.Commands.Result.Success; } } /// /// This simple example provides a false color based on the world z-coordinate. /// For details, see the implementation of the FalseColor() function. /// public class ZAnalysisMode : Rhino.Display.VisualAnalysisMode { Interval m_z_range = new Interval(-10,10); Interval m_hue_range = new Interval(0,4*Math.PI / 3); private const bool m_show_isocurves = true; public override string Name { get { return "Z-Analysis"; } } public override Rhino.Display.VisualAnalysisMode.AnalysisStyle Style { get { return AnalysisStyle.FalseColor; } } public override bool ObjectSupportsAnalysisMode(Rhino.DocObjects.RhinoObject obj) { if (obj is Rhino.DocObjects.MeshObject || obj is Rhino.DocObjects.BrepObject) return true; return false; } protected override void UpdateVertexColors(Rhino.DocObjects.RhinoObject obj, Mesh[] meshes) { // A "mapping tag" is used to determine if the colors need to be set Rhino.Render.MappingTag mt = GetMappingTag(obj.RuntimeSerialNumber); for (int mi = 0; mi < meshes.Length; mi++) { var mesh = meshes[mi]; if( mesh.VertexColors.Tag.Id != this.Id ) { // The mesh's mapping tag is different from ours. Either the mesh has // no false colors, has false colors set by another analysis mode, has // false colors set using different m_z_range[]/m_hue_range[] values, or // the mesh has been moved. In any case, we need to set the false // colors to the ones we want. System.Drawing.Color[] colors = new System.Drawing.Color[mesh.Vertices.Count]; for (int i = 0; i < mesh.Vertices.Count; i++) { double z = mesh.Vertices[i].Z; colors[i] = FalseColor(z); } mesh.VertexColors.SetColors(colors); // set the mesh's color tag mesh.VertexColors.Tag = mt; } } } public override bool ShowIsoCurves { get { // Most shaded analysis modes that work on breps have the option of // showing or hiding isocurves. Run the built-in Rhino ZebraAnalysis // to see how Rhino handles the user interface. If controlling // iso-curve visability is a feature you want to support, then provide // user interface to set this member variable. return m_show_isocurves; } } /// /// Returns a mapping tag that is used to detect when a mesh's colors need to /// be set. /// /// Rhino.Render.MappingTag GetMappingTag(uint serialNumber) { Rhino.Render.MappingTag mt = new Rhino.Render.MappingTag(); mt.Id = this.Id; // Since the false colors that are shown will change if the mesh is // transformed, we have to initialize the transformation. mt.MeshTransform = Transform.Identity; // This is a 32 bit CRC or the information used to set the false colors. // For this example, the m_z_range and m_hue_range intervals control the // colors, so we calculate their crc. uint crc = RhinoMath.CRC32(serialNumber, m_z_range.T0); crc = RhinoMath.CRC32(crc, m_z_range.T1); crc = RhinoMath.CRC32(crc, m_hue_range.T0); crc = RhinoMath.CRC32(crc, m_hue_range.T1); mt.MappingCRC = crc; return mt; } System.Drawing.Color FalseColor(double z) { // Simple example of one way to change a number into a color. double s = m_z_range.NormalizedParameterAt(z); s = Rhino.RhinoMath.Clamp(s, 0, 1); return System.Drawing.Color.FromArgb((int)(s * 255), 0, 0); } } ``` ```python # No Python sample available ``` -------------------------------------------------------------------------------- # What are VBScript and RhinoScript? Source: https://developer.rhino3d.com/en/guides/rhinoscript/what-are-vbscript-rhinoscript/ This guide explains what VBScript and RhinoScript are. ## Overview VBScript (short for Visual Basic Scripting Edition) is an easy-to-use scripting language developed by Microsoft that enables authors to create powerful tools using a subset of the Visual Basic language. VBScript is implemented as a fast, portable interpreter for web browsers and applications that use ActiveX controls and OLE Automation servers. If you already know Visual Basic or Visual Basic for Applications (VBA), VBScript will be familiar. Even if you do not know Visual Basic, it should not take you long to get up-to-speed. VBScript is a relatively easy language to learn and use. RhinoScript is a scripting tool based on Microsoft's VBScript language. With RhinoScript, you can quickly add functionality to Rhino, or automate repetitive tasks. Besides providing support for VBScript, RhinoScript also runs as an OLE automation server that exposes the internal workings of Rhino to automation. ## Related Topics - [VBScript User's Guide and Language Reference on MSDN](http://msdn.microsoft.com/en-us/library/t0aew7h6(VS.85).aspx) - [VBScript Data Types](/guides/rhinoscript/vbscript-datatypes) - [VBScript Variables](/guides/rhinoscript/vbscript-variables) - [VBScript Constants](/guides/rhinoscript/vbscript-constants) - [VBScript Operators](/guides/rhinoscript/vbscript-operators) - [VBScript Statements](/guides/rhinoscript/vbscript-statements) - [VBScript Conditionals](/guides/rhinoscript/vbscript-conditionals) - [VBScript Looping](/guides/rhinoscript/vbscript-looping) - [VBScript Procedures](/guides/rhinoscript/vbscript-procedures) - [VBScript Code Conventions](/guides/rhinoscript/vbscript-code-conventions) -------------------------------------------------------------------------------- # What is a Grasshopper Component? Source: https://developer.rhino3d.com/en/guides/grasshopper/what-is-a-grasshopper-component/ This guide gives an overview of custom Grasshopper components. This guide also explains the hierarchy of all assemblies involved with the Grasshopper plugin. This is important for component developers so they know which Assembly References they need to have in order to compile a Grasshopper Component Library. It also provides some background information which is useful when communicating with other developers. ## Rhino Plugin Architecture Grasshopper is a .NET ([RhinoCommon](/guides/rhinocommon/what-is-rhinocommon/)) plugin for Rhino 6 for Windows and later ([a version for Rhino 5 for Mac is currently in beta](http://www.grasshopper3d.com/page/grasshopper-for-mac)). It was written using Microsoft Visual Studio Professional using both VB.NET and C# source compiled against the .NET Framework. It is recommended, though not required, that you target the same framework when developing Grasshopper Component Libraries. Our aim is to keep Grasshopper dependencies as conservative as possible. However, it is possible that we will switch to a higher version number of Rhino or .NET if this new version fixes crucial bugs or exposes useful functions. ## .NET Component Library The Grasshopper project type is *Class Library*, meaning it cannot be run as a stand-alone application. *Grasshopper.dll* is loaded by a [Rhino plugin](/guides/general/what-is-a-rhino-plugin/) called *GrasshopperPlugin.rhp*. ## Assembly References As a Class Library, Grasshopper references namespaces in addition to *RhinoCommon.dll*, some of these are standard namespaces provided by the .NET Framework, others are 3rd-party assemblies and others still are written by McNeel developers but are shipped separately for technical reasons. Some of these assemblies need to be referenced by Component developers, while others can be safely ignored. The following table lists all assemblies referenced by *Grasshopper.dll*:
Assembly Author Purpose Required
RhinoCommon.dll McNeel Rhinoceros .NET SDK Yes
GH_IO.dll McNeel Grasshopper Input/Output library required to read and write Grasshopper files. Yes
GH_Util.dll McNeel Grasshopper utility library containing some peripheral algorithms. Optional
QWhale.*.dll Quantum Whale Syntax highlighter functionality. Contains a total of 5 dlls. Optional
System Microsoft Base .NET Namespace. Yes
System.Drawing Microsoft .NET namespace involved with drawing shapes and text. Yes
System.Windows.Forms Microsoft .NET namespace involved with dialogs and controls. Yes
System.Collections.Generic Microsoft .NET namespace containing useful list classes. Yes
## Related topics - [Developer Prerequities](/guides/general/rhino-developer-prerequisites/) - [What is a Rhino Plugin?](/guides/general/what-is-a-rhino-plugin/) - [What is RhinoCommon?](/guides/rhinocommon/what-is-rhinocommon/) - [Your First Component](/guides/grasshopper/your-first-component-windows) -------------------------------------------------------------------------------- # What is a LAN Zoo Plugin? Source: https://developer.rhino3d.com/en/guides/rhinocommon/what-is-a-zoo-plugin/ This guide describes what a LAN Zoo Plugin is and what is does. The LAN Zoo keeps your licenses on your private LAN server and lets you share them among the Rhino users on your network. A LAN Zoo plugin is a software module, developed by a 3rd party, that extends the functionality of the LAN Zoo by allowing it to validate product licenses. ![zoo with plugins](https://developer.rhino3d.com/images/what-is-a-zoo-plugin-01.png) When Rhino and Rhino-based products are installed as workgroup nodes, instead of standalone nodes, licensing works like this: - When a workgroup node starts, it requests a license from the LAN Zoo. - An unused license is assigned to the node. - When a node shuts down, the license is returned to the LAN Zoo's license pool. #### What is required to build a plugin? LAN Zoo plugins are .NET Framework 4.8 assemblies. Thus, to create a plugin for the LAN Zoo, you will need one of the following development tools: 1. Microsoft Visual C# 1. Microsoft Visual Basic .NET Also, all plugins that use the LAN Zoo license system must be signed with an Authenticode certificate issued by McNeel Plugin Security. These certificates are free, but must be requested by each developer. Developers must agree to the Terms of Use before a certificate is issued. For more information on plugin signing, see [Digitally Signing Plugins for LAN Zoo](/guides/rhinocommon/digitally-signing-plugins-for-zoo). ## Next Steps Check out the [Creating LAN Zoo Plugins](/guides/rhinocommon/creating-zoo-plugins) guide for instructions building - your guessed it - a LAN Zoo Plugin. ## Related Topics - [Creating LAN Zoo Plugins](/guides/rhinocommon/creating-zoo-plugins) - [Digitally Signing Plugins for LAN Zoo](/guides/rhinocommon/digitally-signing-plugins-for-zoo) -------------------------------------------------------------------------------- # What is Hops Source: https://developer.rhino3d.com/en/guides/compute/what-is-hops/ Hops lets you simplify large complex definitions. Grasshopper definitions can be big, complicated, and repetitive. This state of disorganization is sometimes called [Spaghetti Code](https://www.pcmag.com/encyclopedia/term/spaghetti-code). Many factors contribute to spaghetti code including: time constraints, programmer skill level, project complexity, and limitations in the programming language. After trying to decipher a big ball of Grasshopper spaghetti, there is one universal truth - spaghetti code is hard to read and understand. **Now, you can use Hops to simplify hard to read spaghetti code.** ### Calling Functions in Grasshopper In programming, a **function** is a "self-contained" module of code that processes inputs and returns a result. Grasshopper definitions also process inputs and returns results. However, Grasshopper wasn’t written with functions in mind. Clusters sort of work, but don’t make it easy to reuse simple definitions in complex projects. To fix this, the Hops component was added. Hops lets you use separate grasshopper definitions as functions. The key is learning to break a problem into smaller sub-definitions which we will use as functions. Imagine that you needed to run a shadow analysis for a parametric tower you were designing for New York City. This problem could be broken down into four smaller tasks: 1. Create the building lot outline 1. Create the building envelope 1. Create the floor plates 1. Run the shadow analysis from the building enevlope and surrounding buildings Once the tasks have been clearly defined, you can determine what information will be needed to perform each task (i.e., the inputs) and what data will be returned at the end (i.e., the outputs). For step 1, the inputs needed to generate the building lot outline would include site setbacks and other regulatory parameters from the building and zoning codes. The output for this function would likely include a polyline outlining the maximum building area at the ground floor. To define the parameters which will be used in a Grasshopper function, use the **Context Get** components for the inputs and the **Context Bake** component for the outputs. Each of these can be found under the *Params Tab > Util Group*. Now that we have the inputs and outputs defined, all that is left is to fill in the steps to turn those inputs into outputs. After a function has been fully defined as a Grasshopper definition it can be called by Hops. Hops exposes the inputs and outputs you defined inside your function in the containing definition. Unlike clusters, Hops lets you reference external files which can be saved locally or on a network drive - making it easy to share definitions among other team members and collaborators. Hops lets you increase the legibility of your code by simplifying complex definitions, reducing duplication, and sharing and reusing functions. Hops lets you solve functions in parallel, potentially speeding up large projects. It also lets you solve functions asynchronously, without blocking Rhino and Grasshopper interactions. ## How it works? Behind the scenes, the Hops client passes the Grasshopper definition you specify to a headless instance of Rhino and Grasshopper server. ### The Hops Client A client is a hardware device (i.e., computer) or software application which makes a request for a digital resource from a server over a network connection. The Hops component is the client. It can be found under the *Params Tab > Util Group* after you install Hops through the package manager. Hops needs the path or URL for the definition that it is going to solve. Once you specify the definition, the component updates to show the inputs and outputs defined in the definition. ### The Rhino Server A server serves data to its clients over a network connection. In the context of Hops, the server is a headless (meaning it has no user interface to interact with) version of Rhino and Grasshopper. The headless Rhino server solves the Grasshopper definition sent from Hops and then returns the result. Hops optionally lets you point to a Rhino.Compute server running on another computer to solve the Grasshopper definitions. This lets you offload some or all of the solving to an external compute resource. ## Getting Started To create a function that can be referenced by Hops, we need to break our definition into three distinct sections; one to define our input parameters, another to specify the outputs, and finally a section which performs the *actions* of the function. To get started with Hops, you can either follow along in the video or walk through the steps listed below. In this example we want to create a simple function that will take in a user's name as an input (i.e., David) and return a message such as *"Hello David!"*. ### Install Hops Before we begin, let's start by making sure Hops is installed properly. There are a few ways to install hops on your machine. 1. [Install Hops](rhino://package/search?name=hops) (This will launch Rhino) 1. Or, type `PackageManager` on the Rhino command line. 1. Then, search for “Hops” 1. Select Hops and then Install ### Create an Input Now that we have Hops installed, let's start defining our function by creating an input text parameter. Under the *Params Tab > Util Group* you will see a collection of Context Getter components. Place a **Get String** component onto your canvas. Right-click on the middle of this component and change the name from `Get String` to `Name`. This value is what Hops will use as for the input parameter name. You can assign a default value to this parameter by connecting a string to the input of the Get String component. Add a **Text Panel** to the canvas. This can be found under the *Params Tab > Input Group*. Double-click on the Text Panel and write your name in the input. ### Define the Function Now that we have created an input parameter, we need to perform some sort of action using that value. Let's create a message using the name you just passed into the Get String component. Add a **Concatenate Component** onto the canvas. This can be found under the *Sets Tab > Text Group*. The Concatenate component will add a series of text snippets into a single message. In our case, we want to create a string that reads `Hello Your Name Here!`. The Concatenate component is a special type of component in Grasshopper which uses a Zoomable User Interface (ZUI for short). If you zoom in on the component (using the middle scroll wheel), a small (+) button will appear on the left side of the component. Click on the (+) under the second parameter (B) to add a third input parameter. Now, zoom out and add another Text Panel onto the canvas. Double-click on it to enter some text. Type `Hello ` into the panel (Note: there is an extra space added after the word). Connect that panel into the A-input of the Concatenate component. Next, connect the output of the Get String component to the B-input of the Concatenate component. Finally, add another text panel onto your canvas and type an exclamation mark `!` into the panel. Connect the output of that text panel into the C-input of the Concatenate component. Connect a Text Panel to the output of the Concatenate component to view this message. Your definition should look like this. ### Create an Output The last step in creating our Hops function would be to create an output parameter to return the string we just created. Go to the *Param Tab > Util Group* and add a **Context Print** component to your canvas. Right-click on the input parameter (labeled Tx) and change the name to `Message`. You can name this anything you like, but this value is what Hops will use for the output parameter. Now save your file to some directory on your computer. Call the file **Hello_World.gh**. ### Call the Function We've now created a Hops compatible function. Let's use Hops to call that function. Start by creating a new Grasshopper definition (Ctrl + N). Go to the *Param Tab > Util Group* and add a **Hops** component to your canvas. Right-click on this component and select the **Path** menu item. In the pop-up dialog, select the **Hello_World.gh** file we just created. Select **OK** when the path has been selected. At this point, the Hops component should change appearance - adding one input called `Name` and one output called `Message`. Hops essentially bundled up the Hello_World.gh example we created and sent it to a local rhino.compute server to perform the calculation and return the result. Try adding a new **Text Panel** onto the canvas and connect it to the Name-input of the Hops component. Double-click to change the name in the Text panel. Add another Text Panel to the output of the Hops component to see the result that is return from the rhino.compute server. We have just covered how to create and call your very first Hops function. If you would like to learn more about how Hops communicates with the rhino.compute server or setting up your own production environment, check out the links below. --- ## Quick Links - [How Hops Works](../how-hops-works) - [The Hops Component](../hops-component) - [Setting up a Production Environment](../deploy-to-iis) -------------------------------------------------------------------------------- # What is openNURBS? Source: https://developer.rhino3d.com/en/guides/opennurbs/what-is-opennurbs/ This guide gives an overview of the openNURBS toolkit. ## Overview The openNURBS Initiative provides CAD, CAM, CAE, and computer graphics software developers the tools to accurately transfer 3-D geometry between applications. The openNURBS Toolkit consists of C++ source code for a library that will read and write openNURBS 3D model files (*.3dm*). More than 400 software development teams and applications, including *Rhinoceros®*, exchange 3D models using the openNURBS (*.3dm*) file format. The openNURBS Toolkit reads and writes all Rhino 3DM files. Additionally, the openNURBS Toolkit provides NURBS evaluation tools and elementary geometric and 3D view manipulation tools. Unlike other open development initiatives, alliances, or consortia: - Commercial use is encouraged. - The tools, support, and membership are free. - There are no restrictions. Neither copyright nor copyleft restrictions apply. - No contribution of effort or technology is required from the members, although it is encouraged. The openNURBS Toolkit is intended for C++ programmers. The toolkit includes complete source code to create a library that will read and write 3DM files. The toolkit also includes source code for several example programs. ## Details The tools provided by openNURBS include: - [openNURBS C++ source SDK and samples](https://www.rhino3d.com/download/openNURBS/7/release) - the original cross platform SDK. - Quality assurance and revision control. - Technical support. ## Limitations Although the openNURBS toolkit appears to be a full-featured geometry library, it is not. The toolkit does not include a number of important features, including: - Closest point calculations - Intersection calculations - Surface tessellation (meshing) - SubD to Brep conversion - Interpolation - Booleans - Area and mass property calculations - Other miscellaneous geometry calculations openNURBS is an open source toolkit for only reading and writing 3DM models. Our full-featured development platform is *Rhinoceros®*. All of the above features are found in the Rhino SDKs, the toolkit used to build plugins for Rhino. ## Who is funding the openNURBS Initiative and why? Robert McNeel & Associates. We feel that the 3-D market is stifled because of the inability to reliably transfer 3-D geometry between applications. The problem is too big for us to solve alone. By funding the operating cost of openNURBS, others will get involved in the toolkit design and development. It will be a much cheaper and effective way to solve the problem. ## References The following are references to fundamental work on NURBS: - Bohm, Wolfgang, Gerald Farin, Jurgen Kahman (1984). *A survey of curve and surface methods in CAGD*, Computer Aided Geometric Design Vol 1. 1-60 - DeBoor, Carl. (1978). *A Practical Guide To Splines*, Springer Verlag; ISBN: 0387953663 - Farin, Gerald. (1997). *Curves and Surfaces for Computer-Aided Geometric Design: A Practical Guide*, 4th edition, Academic Press; ISBN: 0122490541 ## Related Topics - [openNURBS Migration Guide](/guides/opennurbs/migration-guide) -------------------------------------------------------------------------------- # What is Rhino.Python? Source: https://developer.rhino3d.com/en/guides/rhinopython/what-is-rhinopython/ This guide is an overview of Python in Rhino. Rhino.Python refers to the Python executables embedded in Rhino, and the associated Python code editors (aka. the ScriptEditor, in Rhino and Grasshopper). They let you write and run standard Python scripts in Rhino, to create interactive scripts, new custom commands, and many more. Currently Rhino supports IronPython 2.7, and CPython 3.9. The main Rhino APIs that are accessible through Rhino.Python are [RhinoCommon](https://developer.rhino3d.com/api/rhinocommon/) and [rhinoscriptsyntax](https://developer.rhino3d.com/api/RhinoScriptSyntax/). ## What is Python? [Python](https://www.python.org/) is a *modern programming language*. Python is sometimes called a scripting language or a glue language. This means python is used often to run a series of commands as a script or used to create links between two other technologies as a glue. It is easier to learn and use than other non-scripting style, compiled languages like C#, VB, or C/C++. Yet it is quite powerful. Python is [interpreted](https://en.wikipedia.org/wiki/Programming_language_implementation), meaning it is executed one line at a time. This makes the program flow easy to understand. Also it is [semantically dynamic](https://en.wikipedia.org/wiki/Programming_language#Static_versus_dynamic_typing) which allows the syntax to be less restrictive and less formal when using declarations and variables types. These characteristics add to Python's ease-of-use for basic programming tasks. You may need Python if you want to: - Automate a repetitive task in Rhino much faster than you could do manually. - Perform tasks in Rhino or Grasshopper that you don't have access to in the standard set of Rhino commands or Grasshopper components. - Generate geometry using algorithms. - Many, many other things. It is a programming language after all. ## Why Python? Why should you use Python? Well, Python is meant to be a simple language to read and write. Python also runs both the Windows and Mac versions of Rhino. Since Rhino Python scripting is available on both platforms, the same Python scripts can run on both breeds of Rhino! Python also will run within a Grasshopper component. But more importantly: Python is very popular outside of Rhino! Much of what you learn about Python can be applied in many other domains. Rhino already has a scripting language called RhinoScript why do we need another? RhinoScript is a very easy to use scripting language on Windows. We will continue to support Rhinoscript. But, RhinoScript is based on an little older technology making it less flexible then the more modern Python language. RhinoScript cannot be supported on the Mac platform. It also unfortunately is not supported by the commnuity to the same level as Python. ### What version of Python does Rhino use? Rhino 8 uses Python version 3.9.11. This is based on [CPython](https://www.python.org/), the reference implementation of Python. Rhino 7 uses Python version 2.7.12. To be more specific Rhino 7 uses [IronPython](http://ironpython.net/). This is an older open-source version of Python. See the [Rhino 7 Python Guides](/guides/rhinopython/7/). ## Where can you use Python in Rhino? Python can be used all over Rhino in many different ways. Python can be used to create - Interactive scripts. - New custom commands. - Create new plug-ins. - Read and Write customized file formats. - Interact with cloud applications. - Create realtime links to other applications - Create customer Grasshopper components - Store and display project specific information beyond what basic Rhino can store. #### RhinoScript Style Functions One of the key features of RhinoScript that make it easy to write powerful scripts is a large library of Rhino specific functions that can be called from scripts. Our python implementation includes a set of similar functions that can be imported and used in any python script for Rhino. This set of functions is known as the `rhinoscriptsyntax` package. Let's compare scripts for letting a user pick two points and adding a line to Rhino... Here's RhinoScript: ```vbnet Dim arrStart, arrEnd arrStart = Rhino.GetPoint("Start of line") If IsArray(arrStart) Then arrEnd = Rhino.GetPoint("End of line") If IsArray(arrEnd) Then Rhino.AddLine arrStart, arrEnd End If End If ``` compared with Python: ```py import rhinoscriptsyntax as rs start = rs.GetPoint("Start of line") if start: end = rs.GetPoint("End of line") if end: rs.AddLine(start,end) ``` Yes, different...but similar enough that you should be able to figure out what is happening in python if you've written RhinoScript. Rhinoscriptsyntax also contains many helper functions to make it easier to program with in Python. For more information on how to use Rhinoscriptsyntax and Python, see the [Rhino.Python Guide](/guides/rhinopython/) #### Grasshopper Python can also be used in Grasshopper to create custom components within a larger grasshopper definition. The GhPython component contains a Python script editor and direct access to Python, Rhino and Grasshopper functions. Use GhPython to create and manipulate customer data within Grasshopper. For an introduction to GhPython in Grasshopper, see the [Your First Python Script in Grasshopper Guide](/guides/rhinopython/your-first-python-script-in-grasshopper) #### RhinoCommon [Rhinocommon](/guides/rhinopython/what-is-rhinocommon/) is a low-level powerful SDK for all Rhino platforms used mainly by more experienced developers. Python scripts can use Rhinocommon, to have full access to the .NET framework including access to Rhino's RhinoCommon SDK. A guide of accessing RhinoCommon from Python scripts is at [Using RhinoCommon from Python](/guides/rhinopython/using-rhinocommon-from-python). ## Related Topics - [Python Basic Syntax](/guides/rhinopython/python-statements/) - [Rhinoscript Syntax in Python](/guides/rhinopython/python-rhinoscriptsyntax-introduction/) - [Rhino.Python Home Page](/guides/rhinopython/) - [Python (homepage)](https://www.python.org/) -------------------------------------------------------------------------------- # What is RhinoCommon? Source: https://developer.rhino3d.com/en/guides/rhinocommon/what-is-rhinocommon/ This guide gives an overview of RhinoCommon. RhinoCommon is the cross-platform .NET plugin SDK available for: - Rhino for Windows - Rhino for Mac - Rhino.Python Scripting - Grasshopper The term _Common_ is meant to be just that: an SDK that can be used across Rhino platforms. A plugin built with RhinoCommon could potentially run on both Windows and Mac platforms with no changes. RhinoCommon is available as [NuGet package](https://www.nuget.org/packages/rhinocommon). ## Inside RhinoCommon RhinoCommon is composed of the following components: | Assembly | Description | | :-------------- | :----------------------------------------------------------- | | **RhinoCommon.dll** | [RhinoCommon](https://developer.rhino3d.com/api/rhinocommon/html/R_Project_RhinoCommon.htm?version=8.x) is the core .NET assembly that plugins reference in order to interact with Rhino. | | **Eto.dll** | [Eto](https://github.com/picoe/Eto) is a framework can be used to build user interfaces that run across multiple platforms using their native toolkit, with an easy to use API. This will make your plug-in look and work as a native application on all platforms, using a single UI codebase. | | **Rhino.UI.dll** | [Rhino.UI](https://developer.rhino3d.com/api/rhinocommon/rhino.ui) is a utility .NET assembly that contains Rhino-specific user interface and other miscellaneous classes. | ## Types of Plugins RhinoCommon supports five different types of plugins: | Type | Description | |:-------------------- |:------------------------------------------------------------ | | **General Utility** | A general purpose utility that can contain one or more commands. | | **File Import** | Imports data from other file formats into Rhino; can support multiple file formats. | | **File Export** | Exports data from Rhino to other file formats; can support multiple file formats. | | **Custom Rendering** | Applies materials, textures, and lights to a scene to produce rendered images. | | **3D Digitizing** | Interfaces with 3D digitizing and other alternative input devices. | Note: File Import, File Export, Custom Rendering and 3D Digitizing plugins are all specialized enhancements to the General Utility plugin. Thus, all plugin types can contain one or more commands. As with all of our development tools, RhinoCommon is free, royalty free, and includes free developer support. -------------------------------------------------------------------------------- # What is the C/C++ SDK? Source: https://developer.rhino3d.com/en/guides/cpp/what-is-the-cpp-sdk/ This guide gives an overview of C/C++ SDK. The Rhino C/C++ Software Development Kit (SDK) is an set of developer resources for customizing and extending Rhino for Windows. The SDK provides tools that provide direct access to its database structures, geometry, graphics system, file I/O, command definitions, and much more. The Rhino C/C++ SDK consists primarily of C++ headers and libraries that can be used to build Rhino extensions called *Plugins*. Plugins are Windows DLLs that can be loaded into the Rhino process and interact directly with the Rhino application. Rhino plugin modules use the file extension *.rhp* instead of the more common *.dll*. The Rhino C/C++ SDK is for Rhino for Windows only. There currently is no Rhino C/C++ SDK for Rhino for Mac. ## Types of Plugins Rhino supports five different types of plugins: | Type | Description | |:-------------------- |:------------------------------------------------------------ | | **General Utility** | A general purpose utility that can contain one or more commands. | | **File Import** | Imports data from other file formats into Rhino; can support multiple file formats. | | **File Export** | Exports data from Rhino to other file formats; can support multiple file formats. | | **Custom Rendering** | Applies materials, textures, and lights to a scene to produce rendered images. | | **3D Digitizing** | Interfaces with 3D digitizing and other alternative input devices. | Note: File Import, File Export, Custom Rendering and 3D Digitizing plugins are all specialized enhancements to the General Utility plugin. Thus, all plugin types can contain one or more commands. As with all of our development tools, the Rhino C/C++ SDK is free, royalty free, and includes free developer support. -------------------------------------------------------------------------------- # What is the Package Manager? Source: https://developer.rhino3d.com/en/guides/yak/what-is-yak/ This guide introduces the Rhino Package Manager (a.k.a. Yak). ## Overview The Rhino Package Manager assists in the discovery, installation, and management resources in the Rhino ecosystem (Grasshopper included!). Currently it supports Rhino and Grasshopper plug-ins, but the goal is to include things like scripts, materials, viewports, etc. in the future! The Rhino Package Manager was initially referred to by the codename "Yak". The name Yak is still used for the command line tool that creates and publishes packages. The package manager has several goals. - Make it easier for users to discover and manage plug-ins and more - Help developers and reusable content authors to share their work - Provide simple system administration tools Not wanting to reinvent the wheel, we've taken inspiration from Linux and the software development world. The package management system can be broken down into three main areas. 1. [Server](#server) 2. [Integrations](#integrations) 3. [Command line tool](#command-line-tool) ## Server The package server is the heart of the system. Once created, packages are pushed to the server to share them with others. It keeps the packages organised for its clients – the command line tool and Rhino (via integrations). In addition to the public package server (https://yak.rhino3d.com), the package manager also supports file-/folder-based [custom package repositories](../package-sources). ## Integrations Integrations provide direct access to the package ecosystem from inside of Rhino. Currently this has been done in two ways; "package restore" for Grasshopper and the package manager UI. ### Package restore for Grasshopper The Rhino Package Manager has been integrated into Grasshopper's "Unrecognized Objects" dialog, providing [package restore](../package-restore-in-grasshopper) functionality. When opening a new file which contains components from a plug-in not installed on the machine, the user is given the option to check the package server for the missing plug-ins and install them directly. ![Package restore for Grasshopper](https://developer.rhino3d.com/images/yak-gh-restore-guid.gif) ### Package Manager UI The package manager UI is avilable via the `_PackageManager` command. It provides a NuGet-style interface that allows users to search for packages, install them and see if any updates are avilable to currently installed packages. ![The package manager UI](https://developer.rhino3d.com/images/testpackagemanager-wip.jpg) ## Command Line Tool The command line tool provides a basic interface but with full functionality. It is modelled on well known domain-specific package managers such as Ruby's `gem` and Python's `pip`. It communicates with the server as well as hooking into Rhino Accounts for authentication. On Windows, the tool can be found at `"C:\Program Files\Rhino 8\System\yak.exe"`. On Mac there is a script, `"https://developer.rhino3d.com/Applications/Rhino 8.app/Contents/Resources/bin/yak"`. Type ` help` to get started. ## Related Topics - [Anatomy of a Package](/guides/yak/the-anatomy-of-a-package/) - [The Package Manifest](/guides/yak/the-package-manifest/) - [Yak CLI Reference](/guides/yak/yak-cli-reference) -------------------------------------------------------------------------------- # What is the RDK? Source: https://developer.rhino3d.com/en/guides/cpp/what-is-the-rdk/ This guide describes the Rhino Renderer Development Kit (AKA RDK) and its features. ![RDK Logo](https://developer.rhino3d.com/images/rdk-what-is-the-rdk-01.png) ### Overview The RDK is a collection of tools that extend the Rhino application platform with visualization-specific capabilities. Third-party developers can use the RDK SDK to integrate their renderers into Rhino. ### Features ![RDK Features Banner](https://developer.rhino3d.com/images/rdk-what-is-the-rdk-02.png) - Extensible Material, Environment and Texture editors which display and edit Materials, Environments and Textures (AKA _Render Content_) and allow [operations](/guides/cpp/rdk-task-classes/) to be performed on them. - Render content can have tags assigned. - [Frame buffer](/guides/cpp/rdk-rendering-classes/) implementation with multiple channels and post-processing. - Pre-process custom mesh provision interface for third party developers. - Built-in material types, including gem, glass, plastic, plaster, metal, paint, picture and custom. - Built-in procedural textures, including wood, marble, granite, noise generators, perturbs, and so on. - Built-in HDR and OpenEXR support. - Improved [render pipeline](/guides/cpp/rdk-rendering-classes/) that makes it much easier for developers to implement a renderer engine in Rhino. - [Sun light](/guides/cpp/rdk-sun-classes/) and sun angle calculation tools. - [Skylight](/guides/cpp/rdk-skylight-classes/) support. - [Safe Frame](/guides/cpp/rdk-safe-frame-classes/) support. - Gamma, [Linear Workflow](/guides/cpp/rdk-linear-workflow-classes/) and [Dithering](/guides/cpp/rdk-dithering-classes/) support. - Automatic shader UI support for third party Material/Environment/Texture providers. - Several utility classes to aid in the development of renderers and visualization related tools. - Decal support with planar, UV, cylindrical or spherical mapping. - [Ground Plane](/guides/cpp/rdk-ground-plane-classes/) support with automatic height, material and texture mapping options. - 360 degree [environment](/guides/cpp/rdk-current-environment-classes/) preview in the viewport. - Extensive library of ready-to-use materials, environments and textures with Library browser. ### Material, Environment and Texture Editors The Material, Environment, and Texture Editors display objects called _Render Contents_ and allow the user to edit them. These editors are all based on a similar interface with only small functional differences between them. Render Contents are the foundation of the RDK and one of the most important objects it provides. The RDK SDK provides an extensive system that allows render engine developers to create their own custom render contents. The editors then allow users to create, edit and manage these specialized contents as well as the ones bundled with the RDK and apply them to objects in the scene. Main articles: - [Render Content](/guides/cpp/rdk-render-content) - [Render Content Editors](/guides/cpp/rdk-render-content-editors) ### Render Window ![Render Window](https://developer.rhino3d.com/images/rdk-what-is-the-rdk-04.png) A user thinks of the [Render Window](/guides/cpp/rdk-rendering-classes/) as the window that appears on the screen when one renders a model (see the picture above). However, the render window object used by developers corresponds more to the actual _frame buffer_. It contains information about the channels and pixels that make up the rendered image. The standard render window provides a number of features to renderers, including built-in support for scripting, cloning, saving to high dynamic-range formats, post effects, zooming and channel display. ### HDR and EXR Support An HDR image which provides automatic conversion to a bitmap for non-HDR capable renderers. This allows the Rhino renderer and viewport display to show HDR environments while providing HDR tools to third-party renderer engines. The HDR texture also provides projection conversion features. Most HDRi files come as Light Probe projection. The Basic Environment requires Spherical (Equirectangular) projection for spherical environments, so the HDR texture defaults to this conversion. However, several other types are supported. The LDR exposure determines the brightness of the image when converted to a bitmap image. This will not affect the rendering when used by a HDR capable renderer. HDR multiplier is a simple linear multiplier on all values in the image. This can be used to brighten or dim the image in an HDR capable renderer. The Save As button can be used to convert the image to a bitmap file. The LDR exposure value is used convert the image during this process. Azimuth and Altitude values modify the way the image is rotated in space during the projection conversion. ### Decals Decals are non-repeating textures that are applied to the surface of an object with a given projection. They are an easy-to-use way of attaching single images or similar textures to objects without going through the complexity of the texture mapping process. Decals are textures that are placed directly on a specified area of one or more objects. They consist of a single instance of a texture, rather than being tiled as they are when used in a material. Users use decals to modify a limited part of an object's color. ![Decals2](https://developer.rhino3d.com/images/rdk-what-is-the-rdk-08.jpg) The following classes can be used to access decal features: - [IRhRdkDecal](/guides/cpp/rdk-decal-classes/#IRhRdkDecal) - [CRhRdkObjectDataAccess](/guides/cpp/rdk-decal-classes/#CRhRdkObjectDataAccess) ### Sun The RDK provides easy-to-use sun tools, including a docking panel to control the [document sun](/guides/cpp/rdk-sun-classes/#DocumentSun), a sunlight preview within the Rendered viewport, a Sunlight command and a number of other scripting and developer tools to make sun-angle calculations easy. The following classes can be used to access sun features: - [IRhRdkSun](/guides/cpp/rdk-sun-classes/#IRhRdkSun) - [CRhRdkSun](/guides/cpp/rdk-sun-classes/#CRhRdkSun) - [CRhRdkSunDialog](/guides/cpp/rdk-sun-classes/#CRhRdkSunDialog) ### Summary This article introduced the RDK and described some of its main features. Each feature is explained in more detail in a different article. -------------------------------------------------------------------------------- # Where to find help... Source: https://developer.rhino3d.com/en/guides/rhinopython/primer-101/where-to-find-help/ ## Forums: The RhinoPython community in Discourse is very active and offers a wonderful resource for posting questions/answers and finding help on just about anything!: [https://discourse.mcneel.com/c/scripting](https://discourse.mcneel.com/c/scripting) ## General References for Python: Python's main website offers a plethora of information about the syntax, building-in functionality, libraries etc! This is the main resource for anything Python! [http://docs.python.org/](http://docs.python.org/) The Python Documentation also has a great introduction into the basics of Python: [http://docs.python.org/tutorial/introduction.html](http://docs.python.org/) [http://docs.python.org/tutorial/](http://docs.python.org/tutorial/) A very useful Python style guide: [http://www.python.org/dev/peps/pep-0008/](http://www.python.org/dev/peps/pep-0008/) Another very thorough resource for Python is from MIT, called "How to Think Like a Computer Scientist": [http://www.greenteapress.com/thinkpython/thinkCSpy/thinkCSpy.pdf](http://www.greenteapress.com/thinkpython/thinkCSpy/thinkCSpy.pdf) ## Common Exceptions/Errors: For a list of common errors, exceptions and pitfalls that you are likely to run into when coding see: [http://docs.python.org/release/3.1.3/library/exceptions.html#bltin-exceptions](http://docs.python.org/release/3.1.3/library/exceptions.html#bltin-exceptions) [http://secant.cs.purdue.edu/_media/proghints.pdf](http://secant.cs.purdue.edu/_media/proghints.pdf) ## Syntax & Programming Reminders: - Python is Case Sensitive ("A" and "a" are NOT the same thing!) - Python is Indent Sensitive (Use indentation to delineate the scope of loops, conditionals, functions and classes) Remember an extra space or the absence of a space can make a world of a difference! - You do NOT need to declare variables or variables types! Just simply use them (x=3)! - The " # " sign is used for comments, the computer will skip over them. - Print and Return are NOT the same thing - print writes something to the screen, return actually passes a value! - Remember Variable Scope - where you define a variable is important! Variables defined within functions & classes can only be used within those functions/classes unless passed as input or through the return statement! - Develop code incrementally, testing, debugging and printing as you finish smaller sections. Writing hundreds of lines and hitting run will most likely not work and will make it far more difficult to spot errors! ***If this makes no sense to you yet - no fear! Keep reading (and come back to it later)!... ## Related Topics - [Where to find help - Next Topic >>](/guides/rhinopython/primer-101/1-whats-it-all-about/) - [Rhino.Python Primer 101](/guides/rhinopython/primer-101/rhinopython101) - [Running Scripts](/guides/rhinopython/python-running-scripts) - [Canceling Scripts](/guides/rhinopython/python-canceling-scripts) - [Editing Scripts](/guides/rhinopython/python-editing-scripts) -------------------------------------------------------------------------------- # Window Selecting Source: https://developer.rhino3d.com/en/guides/cpp/window-selecting/ This brief guide demonstrates how to drag a window to select objects. ## Overview The `CRhinoGetObject` class is used for selecting Rhino objects. When active, `CRhinoGetObject` object will allow the user to select objects either by picking them or by dragging a crossing window. But, using C/C++, it is possible to write your own object picking class or function. The heart of such a tool is the `CRhinoPickContext` which defines the rules for the picking. Once the rules have been defined, you can use `CRhinoDoc::PickObjects` to do the work. ## Sample The following sample code demonstrates how to drag a window to select objects. ```cpp CRhinoCommand::result CCommandTest::RunCommand( const CRhinoCommandContext& context ) { CRhinoGetPoint gp; gp.SetCommandPrompt( L"Drag a window to select objects" ); gp.SetGetPointCursor( RhinoApp().m_default_cursor ); gp.ConstrainToTargetPlane(); CRhinoGet::result res = gp.Get2dRectangle( 0, 0, FALSE, PS_DOT ); if( res != CRhinoGet::rect2d ) return failure; CRect pick_rect = gp.Rectangle2d(); CRhinoView* view = gp.View(); CRhinoPickContext pick_context; pick_context.m_go = 0; pick_context.m_view = view; pick_context.m_pick_style = CRhinoPickContext::window_pick; pick_context.m_bPickGroups = true; switch( view->Viewport().DisplayMode() ) { case ON::shaded_display: case ON::renderpreview_display: pick_context.m_pick_mode = CRhinoPickContext::shaded_pick; break; } CRhinoObjRefArray pick_list; int pick_count = 0; if( view->Viewport().GetPickXform(pick_rect, pick_context.m_pick_region.m_xform) ) { pick_context.UpdateClippingPlanes(); POINT screen_point = pick_rect.BottomRight(); view->ActiveViewport().VP().GetFrustumLine( screen_point.x, screen_point.y, pick_context.m_pick_line ); int i, pick_count = context.m_doc.PickObjects( pick_context, pick_list ); for( i = 0; i < pick_count; i++ ) { const CRhinoObject* obj = pick_list[i].Object(); if( obj && obj->IsSelectable() ) obj->Select( true ); } if( pick_count ) context.m_doc.Redraw(); } return success; } ``` -------------------------------------------------------------------------------- # Writing Code for 32- and 64-bit Compilers Source: https://developer.rhino3d.com/en/guides/cpp/writing-code-for-32-and-64-bit-compilers/ This guide outlines some considerations when writing C/C++ code for both 32- and 64-bit compilers. ## strlen The return type of string length functions like `strlen` and `wcslen` is a `size_t`. Since we will never have null terminated strings with more than 2,147,483,647 characters, simply use a cast like so: ```cpp int length = (int)wcslen(str); //(int) cast for 64 bit compilers ``` ## fread and fwrite The return type of `fread` and `fwrite` is a `size_t`. Design your calls to `fread` and `fwrite` so that the count argument is never > 2,147,483,648 and use an int cast on the return type like so: ```cpp int count = ...; some number <= 2,147,483,648 if ( count != (int)fread( buffer, size, count, fp ) ) { //fread failed //handle file reading error } ``` or ```cpp int count = ...; some number <= 2,147,483,648 if ( count != (int)fwrite( buffer, size, count, fp ) ) { //fwrite failed //handle file writing error } ``` If you are compelled to write 9,223,372,036,854,775,808 bytes in a single call to `fwrite`, you can do so with: ```cpp size_t size = 9223372036854775808; void* buffer = ...; FILE* fp = ...; if ( 1 != fwrite( buffer, size, 1, fp ) ) { //failed to write 9223 terrabytes in a single call to fwrite - duh //printf("You're a looser\n"); } ``` ## Sort functions that compare pointers Use `INT_PTR` to store the difference between two pointers. ```cpp //Sort functions have to return 32 bit ints static int compar( const void** a, const void** b ) { //pointer differences have to be INT_PTR (64 bit int on x64) INT_PTR i = ((const CTheRealType**)b)->pRhinoObject - ((const CTheRealType**)a)->pRhinoObject //Expect i to be > MAX_INT, so do something like this return ( (i<0) ? -1 : ( (i>0) ? 1 : 0 ); } ``` ## size_t The type `size_t` is 64 bits on a 64-bit compiler. See the `strlen` and `fread` sections above for examples on dealing with this. ## Formatted printing You really need to pay attention to the size of your integer arguments to formatted printing strings. ```cpp int i = ...; size_t sz = ...; void* ptr = ...; INT_PTR ip = ...; hyper h = ...; __int64 i64 = ...; RhinoApp().Print("i = %d sz = %Id ptr = %I08X ip = %Id h = %I64d i64 = %I64d\n", i,sz,ptr,ip,h,i64); ``` ## Windows SendMessage If you cast the `WPARAM` and `LPARAM` arguments as (`WPARAM`) and (`LPARAM`), and put the return value in an `LRESULT`, everything works perfectly for both the 32- and 64-bit compilers. Since the value of `smresult` can be an `int`, `pointer`, `handle`, whatever, cast `smresult` as shown below. ```cpp LRESULT smresult = SendMessage((UNIT)id, (WPARAM)>, (LPARAM)sText); int rc = (int)smresult ; //In this case, I the rest of the code want HWND hwnd = (HWND)smresult; char** ptr = (char**)smresult; ``` ## Windows SetWindowLong and GetWindowLong Replace every single instance of Windows calls that pass pointers as mystery meat with the `Ptr` versions. ```cpp //BAD //GOOD SetWindowLong -> SetWindowLongPtr GetWindowLong -> GetWindowLongPtr ``` If you do this, then your code will work perfectly and compile cleanly on both 32- and 64-bit platforms. The `Ptr` part of the function names is misleading. The `Ptr` versions work when the return value or last argument has any type. Bad: ```cpp SetWindowLong( *pDockFrame, GWL_USERDATA, (LONG)this); WNDPROC wp = (WNDPROC)::GetWindowLong( *pDockFrame, GWL_WNDPROC); DWORD dwStyle = ::GetWindowLong( pMsg->hwnd, GWL_STYLE) ``` Good: ```cpp SetWindowLongPtr( hwnd, id, (LONG_PTR)this); WNDPROC wp = (WNDPROC)::GetWindowLongPtr( *pDockFrame, GWL_WNDPROC); DWORD dwStyle = (DWORD)::GetWindowLongPtr( pMsg->hwnd, GWL_STYLE) ``` ### Timers The value returned by `Windows ::SetTimer()` needs to be saved in a `UINT_PTR`. -------------------------------------------------------------------------------- # Writing to Text Files Source: https://developer.rhino3d.com/en/guides/cpp/writing-to-text-files/ This brief guide discuss writing text files using C/C++. ## Problem You need to write to a text file from my general utility plugin. ## Solution Rhino C/C++ SDK does not have any special functions or classes to help you read or write text files. With that said, there are a number of ways to read and write text files in C/C++. 1. Use the C-runtime library's `fopen`, `fputs`, `fwrite` and `fclose` functions. 1. You can use the `iostream` library's `ofstream` class. 1. You can use Win32's `CreateFile`, `WriteFile`, and `CloseHandle` functions. 1. You can use MFC's `CFile` and `CStdioFile` classes. ## Sample Here is a simple example that uses the C-runtime library. ```cpp /* Description: Writes a string to a text file Parameters: text - [in] The string to write filename - [in] The name of the file to write. If the file does not exist, it will be created. If the file does exist, it will be overwritten. Returns: true if successful, false otherwise. */ bool WriteStringToFile( const wchar_t* text, const wchar_t* filename ) { bool rc = false; if( (text && text[0]) && (filename && filename[0]) ) { FILE* fp = _wfopen( filename, L"w" ); if( fp ) { size_t num_write = wcslen( text ); size_t num_written = fwrite( text, sizeof(wchar_t), num_write, fp ); fclose( fp ); rc = ( num_written == num_write ); } } return rc; } ``` -------------------------------------------------------------------------------- # Yak Command Line Tool Reference Source: https://developer.rhino3d.com/en/guides/yak/yak-cli-reference/ A reference for the Yak command line tool. The Yak command line tool is included with Rhino 7 WIP. On Windows the tool is located at `"C:\Program Files\Rhino 8\System\yak.exe"`. On macOS there is a convenience script at `"https://developer.rhino3d.com/Applications/Rhino 8.app/Contents/Resources/bin/yak"`. ## Commands ### Build * _Since 0.2: Command added_ * _Since 0.4: Supports multiple .gha files, .rhp files or anything else for that matter_ * _Since 0.9: Appends distribution tag to filename and expands $version placeholder_ * _Since 0.10.1: Adds `--platform` argument_ When run in a directory containing a valid `manifest.yaml` file, creates a package containing all files in the directory. ```commandline Usage: yak build [options] Options: --platform PLATFORM The platform where the package will run ('win', 'mac' or 'any') -h, --help Get help (equivalent to `yak help build`) ``` A distribution tag (e.g. rh7-win) is appended to the filename of the created package. The tag is determined by inspecting the contents of the package during creation. The --platform=any argument can be used if the author wants to publish a cross-platform distribution, e.g. rh7-any. Only .rhp and .gha files can currently be inspected. If a package contains none of these, it will have a distribution tag of any-any. ### Install * _Since 0.1: Command added_ * _Since 0.13.0: Supports installing local .yak files_ Installs a package (optionally with a specific version). ```commandline Usage: yak install [--source=URL] [] yak install ``` Where `` is either the name of a package or the path to a local .yak file. ### List _Since 0.2_ Lists the packages installed on the machine. ```commandline yak list ``` ### Login * _Since 0.2: Command added_ * _Since 0.10: User registered during login_ Authenticates with Rhino Accounts and stores a time-limited OAuth2 access token so that the user can use commands which require authentication. ```commandline Usage: yak login [options] Options: --ci Generate a non-expiring API key and display it -s, --source URL Package repository location [default: https://yak.rhino3d.com/]. -h, --help Get help (equivalent to `yak help login`) ``` On Windows, the token is stored in `%appdata%\McNeel\yak.yml`. On macOS, it is stored in `~/.mcneel/yak.yml`. During the first login, the user is registered on the server. In an automated build environment – e.g. a build machine, GitHub Actions, etc. – the `yak` CLI tool can read the access token from the `YAK_TOKEN` environment variable. Use the `--ci` flag to login and generate a token for this purpose! ### Push _Since 0.1_ Pushes a package to the server. ```commandline yak push [--source=URL] ``` Requires authentication. ### Search * _Since 0.1: Command added_ * _Since 0.5: Adds `--all` and `--prerelease` flags_ Searches the server for packages which match `query`. ```commandline Usage: yak search [options] Options: --prerelease Display prerelease package versions -a, --all Display all package versions -s, --source URL Package repository location -h, --help Get help (equivalent to `yak help search`) ``` ### Spec * _Since 0.2: Command added_ * _Since 0.4: Adds support for inspecting .rhp files (RhinoCommon only)_ Creates a skeleton `manifest.yml` file based on the contents of the current directory. When run in a directory containing a Grasshopper assembly (`.gha`) or a RhinoCommon plug-in (`.rhp`) the file will be inspected and used to pre-populate the `manifest.yml` file. ```commandline yak spec ``` ### Uninstall _Since 0.1_ Uninstalls a package. ```commandline yak uninstall ``` ### Yank _Since 0.6_ Removes a version from the package index. ```commandline yak yank ``` Requires authentication. Yanked versions do not appear in searches but can still be installed if the exact package version is known. To all intents and purposes they are hidden. It is not possible to re-push a package version that has been yanked. If you find yourself in this situation, then simply roll the version number of your package and push again. If all versions of a package are removed, it will no longer show up in the package index. Deleting a package from the McNeel server If you absolutely need to delete your package from the public server, please email support@mcneel.com. Once a package has been deleted, the name can no longer be used. ### Unyank Works in the same way as the yank command, but in reverse! ### Owner _Since 0.10_ Adds, removes of lists the owners of a package. Package owners can push new versions of the package and (un)yank existing versions. ```commandline Usage: yak owner add [--source=URL] yak owner remove [--source=URL] yak owner list [--source=URL] Options: -h, --help -s, --source URL Package repository location [default: https://yak.rhino3d.com/]. ``` New owners can do everything that the original owner can do. Please bear this in mind! New owners must be registered on the server before they can be added to a package. They can do this by running the [`login`](#login) command. ## Downloads The `yak` CLI is available as a standalone executable for use in environments where Rhino isn't installed, such as on automated build machines. * https://files.mcneel.com/yak/tools/0.13.0/yak.exe * https://files.mcneel.com/yak/tools/0.13.0/win-arm64/yak.exe * https://files.mcneel.com/yak/tools/0.13.0/mac/yak * https://files.mcneel.com/yak/tools/0.13.0/linux-x64/yak * https://files.mcneel.com/yak/tools/0.13.0/linux-arm64/yak ## Related Topics - [Yak Guides and Tutorials](/guides/yak/) - [Anatomy of a Package](/guides/yak/the-anatomy-of-a-package/) - [The Package Manifest](/guides/yak/the-package-manifest/) -------------------------------------------------------------------------------- # Your First Python Script in Rhino Source: https://developer.rhino3d.com/en/guides/rhinopython/your-first-python-script-in-rhino-windows/ This guide demonstrates how to create Python scripts in Rhino. You will learn how to display a message box in Rhino that says "Hello World." It covers the most basic concepts for editing, loading, and running scripts. ## The Complete Script ```python import rhinoscriptsyntax as rs rs.MessageBox ("Hello World") ``` To test the Script: - Start Rhino - At the command prompt, type Scripteditor and press Enter. - The Script editor dialog box appears. - In the script Code window, type the code sample above. - Click the "Run the script" button. - The editor dialog box disappears, and the message below appears: ## The HelloWorld Function If you were writing a more complex script, and wanted to display "Hello World" at strategic points throughout the script, you could write this code every time you wanted the message to appear. But if you changed your mind and wanted it to say "Howdy World" instead, you'd have to search for all the places "Hello World" was used, and replace them. An easier way to solve this problem is to write a Function (`def` is used to define the function). At several places throughout your script, you call the function. The function handles displaying the message, so you only have to change the message in one place. Here's what the function definition looks like: ```python import rhinoscriptsyntax as rs def HelloWorld(): rs.MessageBox ("Hello World") ``` If you click the run button at this time nothing will happen. Nothing happened? That's because the RhinoScript defined the Subroutine but did not actually call it. To call the subroutine, either add this line of code and click Run. To call this function, simply add this to the bottom of the script: ```python HelloWorld() ``` In Python the functions definitions need to come before being called in the code. #### Testing HelloWorld - At the command prompt, type ScriptEditor and press Enter. - The Scripteditor dialog box appears. - In the script Code window, type ```python import rhinoscriptsyntax as rs def HelloWorld(): rs.MessageBox ("Hello World") HelloWorld() ``` - Click the Run button. When you do more than write a couple lines of script, it becomes necessary to save the script to a file so that you can run the script without typing it every time. #### To save your script: In the Edit Script dialog box, Click the Save button. Save the file as "HelloWorld.py". #### Running HelloWorld.py - At the Command prompt, type RunPythonScript and press Enter. - In the Run python script dialog box, select "HelloWorld.py" and click Open. Your script will run, and display the familiar "Hello World" dialog box. ## Related Topics - [What is RhinoPython?](/guides/rhinopython/what-is-rhinopython) - [Running Scripts](/guides/rhinopython/python-running-scripts) - [Canceling Scripts](/guides/rhinopython/python-canceling-scripts) - [Editing Scripts](/guides/rhinopython/python-editing-scripts) -------------------------------------------------------------------------------- # Zoom Extents of Points Source: https://developer.rhino3d.com/en/samples/cpp/zoom-extents-of-points/ Demonstrates how to zoom to the extends of an array of 3D points. ```cpp static void ZoomExtents( CRhinoView* view, const ON_3dPointArray& point_array ) { if( 0 == view ) return; const ON_Viewport& current_vp = view->ActiveViewport().VP(); ON_Viewport zoomed_vp; ON_Xform w2c; current_vp.GetXform( ON::world_cs, ON::camera_cs, w2c ); ON_BoundingBox bbox = point_array.BoundingBox(); if( bbox.IsValid() ) { double border = 1.1; double dx = bbox.m_max.x - bbox.m_min.x; dx *= 0.5 * (border - 1.0); bbox.m_max.x += dx; bbox.m_min.x -= dx; double dy = bbox.m_max.y - bbox.m_min.y; dy *= 0.5 * (border - 1.0); bbox.m_max.y += dy; bbox.m_min.y -= dy; if( RhinoDollyExtents(current_vp, bbox, zoomed_vp) ) { view->ActiveViewport().SetVP( zoomed_vp, true ); view->Redraw(); } } } ``` -------------------------------------------------------------------------------- # Zoom to Object Source: https://developer.rhino3d.com/en/samples/rhinocommon/zoom-to-object/ Zoom to a Selected Object ```cs partial class Examples { public static Rhino.Commands.Result ZoomToObject(Rhino.RhinoDoc doc) { Rhino.DocObjects.ObjRef rhObject; var rc = Rhino.Input.RhinoGet.GetOneObject("Select object to zoom", false, Rhino.DocObjects.ObjectType.None, out rhObject); if (rc != Rhino.Commands.Result.Success) return rc; var obj = rhObject.Object(); var view = doc.Views.ActiveView; if (obj == null || view == null) return Rhino.Commands.Result.Failure; var bbox = obj.Geometry.GetBoundingBox(true); const double pad = 0.05; // A little padding... double dx = (bbox.Max.X - bbox.Min.X) * pad; double dy = (bbox.Max.Y - bbox.Min.Y) * pad; double dz = (bbox.Max.Z - bbox.Min.Z) * pad; bbox.Inflate(dx, dy, dz); view.ActiveViewport.ZoomBoundingBox(bbox); view.Redraw(); return Rhino.Commands.Result.Success; } } ``` ```python import Rhino import scriptcontext import System def ZoomToObject(): rc, rhobject = Rhino.Input.RhinoGet.GetOneObject("Select object to zoom", False, System.Enum.Parse(Rhino.DocObjects.ObjectType, "None")) if rc != Rhino.Commands.Result.Success: return obj = rhobject.Object() view = scriptcontext.doc.Views.ActiveView if not obj or not view: return bbox = obj.Geometry.GetBoundingBox(True) pad = 0.05 #A little padding... dx = (bbox.Max.X - bbox.Min.X) * pad dy = (bbox.Max.Y - bbox.Min.Y) * pad dz = (bbox.Max.Z - bbox.Min.Z) * pad bbox.Inflate(dx, dy, dz); view.ActiveViewport.ZoomBoundingBox(bbox) view.Redraw() if __name__=="__main__": ZoomToObject() ``` -------------------------------------------------------------------------------- # Array Points on Surface Source: https://developer.rhino3d.com/en/samples/rhinopython/array-points-on-surface/ Demonstrates how to array points on a surface using Python. ```python # Creates an array of points on a surface import rhinoscriptsyntax as rs def ArrayPointsOnSurface(): # Get the surface object surface_id = rs.GetObject("Select surface", rs.filter.surface) if surface_id is None: return # Get the number of rows rows = rs.GetInteger("Number of rows", 2, 2) if rows is None: return # Get the number of columns columns = rs.GetInteger("Number of columns", 2, 2) if columns is None: return # Get the domain of the surface U = rs.SurfaceDomain(surface_id, 0) V = rs.SurfaceDomain(surface_id, 1) if U is None or V is None: return # Add the points for i in range(0,rows): param0 = U[0] + (((U[1] - U[0]) / (rows-1)) * i) for j in range(0,columns): param1 = V[0] + (((V[1] - V[0]) / (columns-1)) * j) point = rs.EvaluateSurface(surface_id, param0, param1) rs.AddPoint(point) # Check to see if this file is being executed as the "main" python # script instead of being used as a module by some other python script # This allows us to use the module which ever way we want. if __name__ == "__main__": # call the function defined above ArrayPointsOnSurface() ``` -------------------------------------------------------------------------------- # Commas & Periods Source: https://developer.rhino3d.com/en/guides/rhinocommon/commas-and-periods/ This guide demonstrates how to consistently read/write numbers to strings. ## English Culture Depending on the user's culture settings, their computer may be set up to use commas instead of periods for decimal separators. This can cause problems when writing numbers into xml files and reading these xml files on a different culture setting. An easy way around this problem is to temporarily adjust the application's culture settings when reading/writing files to always use a single culture... ```cs // save current culture System.Globalization.CultureInfo current_culture = System.Threading.Thread.CurrentThread.CurrentCulture; // create and set english-us culture System.Globalization.CultureInfo us_culture = new System.Globalization.CultureInfo("en-us"); System.Threading.Thread.CurrentThread.CurrentCulture = us_culture; int rc = WriteMyFile( filename ); // restore the saved culture System.Threading.Thread.CurrentThread.CurrentCulture = current_culture; ``` ```vbnet ' save current culture Dim current_culture As System.Globalization.CultureInfo = System.Threading.Thread.CurrentThread.CurrentCulture ' create and set english-us culture Dim us_culture As new System.Globalization.CultureInfo("en-us") System.Threading.Thread.CurrentThread.CurrentCulture = us_culture Dim rc As Integer = WriteMyFile( filename ) ' restore the saved culture System.Threading.Thread.CurrentThread.CurrentCulture = current_culture ``` -------------------------------------------------------------------------------- # Creating a Skin (Windows) Source: https://developer.rhino3d.com/en/guides/rhinocommon/creating-a-skin/ This guide outlines the tools for RhinoCommon developers to wrap their application around Rhino by creating custom Skin. Custom skins are supported on Windows only. ## Overview Rhino allows developers to customize most of Rhino's interface so that the application appears to be their own. We call this a custom *Skin*. With a custom Skin, you can change the application icon, splash screen, the application name etc. Creating a custom Skin for Rhino involves creating a custom skin assembly: *skin name.rhs* This is a regular .NET Assembly (*.DLL*) that implements the skin's icon, splash screen, application name, etc. In this guide, we will refer this to the Skin DLL. See a full list of methods and properties on the [Skin class documentation](/api/RhinoCommon/html/T_Rhino_Runtime_Skin.htm). ## Create the Skin DLL To create the Skin DLL: 1. Launch *Visual Studio* and add a new Class Library project to your solution. 1. In the new Class Library project, add a reference to *RhinoCommon.dll*, which is found in Rhino's *System* folder. Note: make sure, after adding the reference, to set the properties of the reference to *Copy Local = False*. 1. Create a new class that inherits from `Rhino.Runtime.Skin`. 1. Add a post build event to the project to rename the assembly from *.dll* to *.rhs*: ``` Copy "$(TargetPath)" "$(TargetDir)$(ProjectName).rhs" Erase "$(TargetPath)" ``` ## Skin Class The skin class can override basic properties, like the `ApplicationName`: ```cs namespace MySkin { public class MyHippoSkin : Rhino.Runtime.Skin { protected override string ApplicationName { get { return "Hippopotamus"; } } } // You can override more methods and properties here } ``` ```vbnet Namespace MySkin Public Class MyHippoSkin Inherits Rhino.Runtime.Skin Protected Overrides ReadOnly Property ApplicationName() As String Get Return "Hippopotamus" End Get End Property End Class ' You can override more methods and properties here End Namespace ``` ## Installation

WARNING

Modifying the registry incorrectly can have negative consequences on your system's stability and even damage the system. To install your custom Skin, use *REGEDIT.EXE* to add a scheme key to your registry with a path to your Skin DLL. For example: | **Item** | | | **Value** | |:--------|:----:|:----:|:--------| | Subkey | | | HKEY_LOCAL_MACHINE\SOFTWARE\McNeel\Rhinoceros\MajorVersion.0\Scheme: MySkin | | Entry name | | | SkinDLLPath | | Type | | | REG_SZ | | Data value | | | C:\Src\MySkin\Bin\Release\MySkin.rhs | Where `MajorVersion` is the major version of Rhino (e.g. 6, 7, 8). ## Testing You can now test your custom Skin by creating shortcut to your Rhino executable with `/scheme=""` as command line argument. For example: *C:\Program Files\Rhino 8\System\Rhino.exe" /scheme=MySkin* -------------------------------------------------------------------------------- # Creating LAN Zoo Plugins Source: https://developer.rhino3d.com/en/guides/rhinocommon/creating-zoo-plugins/ This guide discusses how to create plugins for the LAN Zoo. ## Overview The LAN Zoo allows third party plugin developers add licensing support for their products. When a customer attempts to add a product license to the LAN Zoo, the product's plugin is called to validate the user's request. Upon validation, the plugin will return the product's licensing information back to the Zoo. In turn, the LAN Zoo will serialize, maintain, and distribute this license. ## Prerequisites LAN Zoo plugins are .NET Framework 4.8 assemblies. To create a plugins for the LAN Zoo, you need one of the following development tools: 1. Microsoft Visual C#. 2. Microsoft Visual Basic .NET. Also, all plugins that use the LAN Zoo license system must be signed with an *Authenticode* certificate issued by *McNeel Plugin Security*. These certificates are free, but must be requested by each developer. Developers must agree to the *Terms of Use* before a certificate is issued. For more information on plugin signing, see [Digitally Signing Plugins for LAN Zoo](/guides/rhinocommon/digitally-signing-plugins-for-zoo). ## Writing a LAN Zoo Plugin The general steps required to create a Zoo plugin are: 1. Make sure you have the LAN Zoo installed. 2. Launch Microsoft Visual Studio. 3. Create a new *Class Library* project using either Visual C# or Visual Basic .NET. 4. CRITICAL: The LAN Zoo is a 32-bit application. Make sure your project targets the `x86` platform and not `AnyCPU`. 5. Add a reference to *ZooPlugin.dll*, which is found in the LAN Zoo installation folder. Make sure to set the reference's *Copy Local* property to `False`. 6. Create a new public class that inherits from the `IZooPlugin3` interface. 7. Implement the interface members. (For detailed information about the interface members, see the sample LAN Zoo plugin listed below.) 8. Build your plugin. 9. [Digitally sign your plugin](/guides/rhinocommon/digitally-signing-plugins-for-zoo). ## Installing a LAN Zoo Plugin Once you have built your LAN Zoo plugin, you can install it and test it: 1. Run *ZooAdmin.Wpf.exe* and make sure the LAN Zoo licensing service has stopped. 2. Copy your plugin assembly (*.dll*) and any dependent support libraries to the lan Zoo's plugin folder (i.e. *C:\Program Files\Zoo 7\Plugins*). 3. Restart the Zoo license service. 4. When the service has restarted, click the *Add License* button. Your product should be one of the available products for which to add a license. ## Related Topics - [Digitally Signing Plugins for LAN Zoo](/guides/rhinocommon/digitally-signing-plugins-for-zoo) - [Creating Plugins that use the LAN Zoo (RhinoCommon)](/guides/rhinocommon/rhinocommon-zoo-plugins) - [Creating Plugins that use the LAN Zoo (C/C++)](/guides/cpp/creating-zoo-plugins) - [Sample LAN Zoo Plugin Projects (on GitHub)](https://github.com/mcneel/rhino-developer-samples/tree/6/zoo) -------------------------------------------------------------------------------- # Creating Plugins that use the LAN Zoo Source: https://developer.rhino3d.com/en/guides/cpp/creating-zoo-plugins/ This guide discusses how to create C/C++ plugins that can obtain licenses from the LAN Zoo. ## Overview The LAN Zoo supports 3rd party plugins. The Rhino C/C++ SDK allows developers to write plugins for Rhino that use the Rhino license manager and obtain licenses from LAN Zoo servers. When a customer attempts to add a product license to the LAN Zoo, the product's plugin is called to validate the user's request. Upon validation, the plugin will return the product's licensing information back to the LAN Zoo. In turn, the LAN Zoo will serialize, maintain, and distribute this license. ## Prerequisites It is presumed you already have the necessary tools installed and are ready to go. If you are not there yet, see [Installing Tools (Windows)](/guides/cpp/installing-tools-windows). Also, all plugins that use the LAN Zoo license system must be signed with an Authenticode certificate issued by McNeel Plugin Security. These certificates are free, but must be requested by each developer. Developers must agree to the Terms of Use before a certificate is issued. For more information on plugin signing, see [Digitally Signing Plugins for LAN Zoo](/guides/rhinocommon/digitally-signing-plugins-for-zoo). It is also presumed you have a C/C++ plugin you wish to add license support to. See the [Your First Plugin (Windows)](http://developer.rhino3d.com/guides/cpp/your-first-plugin-windows/) guide for instructions. ## Adding Licensing Support After you have built and tested your basic plugin, you can add licensing support as follows: #### Step-by-Step 1. Add a new *.cpp* file to your project. 2. In this *.cpp* file, declare a new class that is derived from `CRhinoLicenseValidator`. 3. Override the `CRhinoLicenseValidator::ProductBuildType` virtual function and return the build type of the license that your product requires. 4. Override and implement the `CRhinoLicenseValidator::VerifyLicenseKey` virtual function. Rhino will call into this function whenever it needs your plugin to validate a license that is entered by a user, returned by the Rhino license manager (standalone node), or returned from a LAN Zoo server (network node). 5. Override and implement the `CRhinoLicenseValidator::VerifyPreviousVersionLicense` virtual function. Rhino will call into this function if a license key from a previous version of your product is required to validate the license key being verified. 6. Override and implement the `CRhinoLicenseValidator::OnLeaseChanged` virtual function. Rhino will call into this function if your product supports Rhino Accounts. When Rhino Accounts gets a new lease, this function is called. 7. Create one (and only one) static instance of your `CRhinoLicenseValidator`-derived object. 8. In your plugin's `CRhinoPlugIn::OnLoadPlugIn` member, call `CRhinoPlugIn::SetLicenseCapabilities` and pass it the required user interface parameters. 9. In your plugin's `CRhinoPlugIn::OnLoadPlugIn` member, `CRhinoPlugIn::GetLicense` to get a license. 10. Build your plugin. 11. [Digitally sign your plugin](/guides/rhinocommon/digitally-signing-plugins-for-zoo). 12. Launch Rhino and test your plugin. When your plugin is loaded for the first time, you will be prompted to enter a license. ## Related Topics - [Creating LAN Zoo Plugins](/guides/rhinocommon/creating-zoo-plugins) - [Digitally Signing Plugins for LAN Zoo](/guides/rhinocommon/digitally-signing-plugins-for-zoo) - [Sample C/C++ plugin project (on GitHub)](https://github.com/mcneel/rhino-developer-samples/tree/6/cpp/SampleWithLicensing) -------------------------------------------------------------------------------- # Creating your first C/C++ plugin for Rhino Source: https://developer.rhino3d.com/en/guides/cpp/your-first-plugin-windows/ This guide walks you through your first plugin for Rhino for Windows using C/C++ and Visual Studio. It is presumed you already have the necessary tools installed and are ready to go. If you are not there yet, see [Installing Tools (Windows)](/guides/cpp/installing-tools-windows). ## Barebones plugin We will use the *Rhino 3D Plugin (C++)* project template to create a new general purpose plugin. The project template generates the code for a functioning plugin. Follow these steps to build the plugin. ### Plugin Template 1. Launch *Visual Studio* and navigate to *File* > *New* > *Project...*. 2. From the *Create a new project* dialog, select the *Rhino 3D Plugin (C++)* template from the list of installed templates and click *Next*. ![New Project Template](https://developer.rhino3d.com/images/your-first-plugin-windows-cpp-01.png) 3. Type the project name as shown below. You can enter a different name if you want. The template uses the project name when it creates files and classes. If you enter a different name, your files and classes will have a name different from that of the files and classes mentioned in this tutorial. Don’t forget to choose a location to store the project. When finished, click *Create*. ![New Project Configure](https://developer.rhino3d.com/images/your-first-plugin-windows-cpp-02.png) 4. Upon clicking *Create*, the *New Rhino C++ Plugin* dialog will appear. By default, the template will create a *General Utility* plugin. ![Plugin Settings](https://developer.rhino3d.com/images/your-first-plugin-windows-cpp-04.png) 5. The *New Rhino C++ Plugin* dialog allows you to modify a number of settings used by the template when generating the plugin source code: 1. **Command name**: Modify this field if you want to change the name of the plugin's initial command. 2. **Plugin type**: Select the [type of plugin](/guides/general/what-is-a-rhino-plugin) that you want the template to create. 3. **Target Version**: Select the target Rhino version. 4. **Automation**: Select this option to allow your program to manipulate objects implemented in another program. Selecting this option also exposes your program to other Automation client plugins. 5. **Windows sockets**: Select this option to indicate that your program supports Windows sockets. Windows sockets allow you to write programs that communicate over TCP/IP networks. 6. **Security Development Lifecycle (SDL) checks**: Select this option to add recommended Security Development Lifecycle (SDL) checks to the project. These checks include extra security-relevant warnings as errors, and additional secure code-generation features. For more information, see [Enable Additional Security Checks](https://msdn.microsoft.com/en-us/library/jj161081.aspx). 6. For this tutorial, just accept the default settings. Click the *Finish* button, and the template begins to generate your plugin project’s folders, files, and classes. When the template is finished, look through the plugin project using *Visual Studio’s Solution Explorer*... ### Plugin Anatomy The following files are of interest: | File | Description | | :---------------- | :----------------------------------------------------------- | | Test.vcxproj | Project file that allows Visual C++ to build your plugin. | | stdafx.h | Main project header. | | stdafx.cpp | Used to generate the precompiled header. | | TestApp.h | Application class header that contains the `CTestApp` class declaration. | | TestApp.cpp | Application class implementation that contains the `CTestApp` member functions. | | TestPlugIn.h | Plugin class header that contains the `CTestPlugIn` class declaration. | | TestPlugIn.cpp | Plugin class implementation that contains the `CTestPlugIn` member functions. | | cmdTest.cpp | Initial Rhino command. | | Resource.h | #define constant definitions for resources. | | Test.rc | Resource script. | | Test.rc2 | Resource script. | | Test.def | Module definition. | | Test.ico | Plugin icon. | | targetver.h | Defines the supported Windows platform. | ### Project Settings With *Visual Studio*, you can view a project's setting by clicking *Project* > *[ProjectName] Properties...*. ![Test Property Pages](https://developer.rhino3d.com/images/your-first-plugin-windows-cpp-05.png) Reviewing the above settings, you can see that there is no 32-bit platform. This is because Rhino is only available as a 64-bit application. ### Property Sheets Visual Studio projects have hundreds of compiler switches and options to choose from. Using custom [Project Property Sheets](https://msdn.microsoft.com/en-us/library/669zx6zc.aspx) is a convenient way to synchronize or share these common settings among other projects. The Plugin Template, used to generate the plugin project, adds Rhino plugin specific property sheets to the project. To view these property sheets, click *View* > *Property Manager*. ![Test Property Manager](https://developer.rhino3d.com/images/your-first-plugin-windows-cpp-06.png) ### Boilerplate Build The *Rhino Plugin Template*, in addition to generating code, creates a custom project file for your plugin. This file, *Test.vcxproj*, specifies all of the file dependencies together with the compile and link option flags. Before we can build our project, we need to fill in the Rhino plugin developer declarations. These declarations will let the user of our plugin know who produced the plugin and where they can support information if needed. 1. Open *TestPlugIn.cpp* and modify the following lines of code, providing your company name and other support information: RHINO_PLUG_IN_DEVELOPER_ORGANIZATION(L"My Company Name"); RHINO_PLUG_IN_DEVELOPER_ADDRESS(L"123 Developer Street\r\nCity State 12345-6789"); RHINO_PLUG_IN_DEVELOPER_COUNTRY(L"My Country"); RHINO_PLUG_IN_DEVELOPER_PHONE(L"123.456.7890"); RHINO_PLUG_IN_DEVELOPER_FAX(L"123.456.7891"); RHINO_PLUG_IN_DEVELOPER_EMAIL(L"support@mycompany.com"); RHINO_PLUG_IN_DEVELOPER_WEBSITE(L"http://www.mycompany.com"); RHINO_PLUG_IN_UPDATE_URL(L"http://www.mycompany.com/support"); 2. When finished, delete the following line of source code as the `#error` directive will prevent the project from building: #error Developer declarations block is incomplete! 3. *NOTE*: If you do not delete this line, the plugin will build. You are now ready to build the project by picking *Build Test* from the *Build* menu. If the build was successful, a plugin file named *Test.rhp* is created in the project’s *Debug* folder. ### Testing 1. From *Visual Studio*, navigate to *Debug* > *Start Debugging*. This will load Rhino. The version of Rhino that is launched depends on the configuration that you build. The template adds the following configurations to your project: - *Debug*: The *Debug* project is a *Release* project that disables optimizations and generates debugging information using the compiler’s *Program Database* (`/Zi`) option and the linker’s *Generate Debug Information* (`/DEBUG`) option. These option settings let you use the debugger while you are developing your custom plugin. The *Debug* configuration also links with release runtime libraries. Plugins built with the *Debug* configuration will only load in the release version of Rhino that was installed with Rhino. - *Release*: The *Release* configuration of your program contains no symbolic debug information and is fully optimized. *Debug* information can be generated in PDB Files (C++) depending on the compiler options used. Creating PDB files can be very useful if you later need to debug your release version. The *Release* configuration also links with release runtime libraries. Plugins built with the *Release* configuration will only load in the release version of Rhino that was installed with Rhino. 1. For this guide, build the *Debug* configuration. 1. From within Rhino, navigate to *Tools* > *Options*. Navigate to the *Plugins* page under *Rhino Options* and install your plugin. ![Rhino Options](https://developer.rhino3d.com/images/your-first-plugin-windows-cpp-07.png) 1. Once your plugin is loaded, close the options dialog and run your *Test* command. 1. You have finished creating your first plugin! ## Adding Additional Commands Rhino plugins can contain any number of commands. Commands are created by deriving a new class from `CRhinoCommand`. See *rhinoSdkCommand.h* for details on the `CRhinoCommand` class. ### Example The following example code demonstrates a simple command class that essentially does nothing: ```cpp // Do NOT put the definition of class CCommandTest in a header // file. There is only ONE instance of a CCommandTest class // and that instance is the static theTestCommand that appears // immediately below the class definition. class CCommandTest : public CRhinoCommand { public: // The one and only instance of CCommandTest is created below. // No copy constructor or operator= is required. // Values of member variables persist for the duration of the application. // CCommandTest::CCommandTest() // is called exactly once when static theTestCommand is created. CCommandTest() = default; // CCommandTest::~CCommandTest() // is called exactly once when static theTestCommand is destroyed. // The destructor should not make any calls to the Rhino SDK. // If your command has persistent settings, then override // CRhinoCommand::SaveProfile and CRhinoCommand::LoadProfile. ~CCommandTest() = default; // Returns a unique UUID for this command. // If you try to use an id that is already being used, then // your command will not work. Use GUIDGEN.EXE to make unique UUID. UUID CommandUUID() override { // {F502C783-C0CE-4118-8869-EFB0CB34CCCB} static const GUID TestCommand_UUID = { 0xF502C783, 0xC0CE, 0x4118, { 0x88, 0x69, 0xEF, 0xB0, 0xCB, 0x34, 0xCC, 0xCB } }; return TestCommand_UUID; } // Returns the English command name. // If you want to provide a localized command name, then override // CRhinoCommand::LocalCommandName. const wchar_t* EnglishCommandName() override { return L"Test"; } // Rhino calls RunCommand to run the command. CRhinoCommand::result RunCommand(const CRhinoCommandContext& context) override; }; // The one and only CCommandTest object // Do NOT create any other instance of a CCommandTest class. static class CCommandTest theTestCommand; CRhinoCommand::result CCommandTest::RunCommand(const CRhinoCommandContext& context) { // CCommandTest::RunCommand() is called when the user // runs the "Test". // TODO: Add command code here. // Rhino command that display a dialog box interface should also support // a command-line, or scriptable interface. ON_wString str; str.Format(L"The \"%s\" command is under construction.\n", EnglishCommandName()); if (context.IsInteractive()) RhinoMessageBox(str, TestPlugIn().PlugInName(), MB_OK); else RhinoApp().Print(str); // TODO: Return one of the following values: // CRhinoCommand::success: The command worked. // CRhinoCommand::failure: The command failed because of invalid input, inability // to compute the desired result, or some other reason // CRhinoCommand::cancel: The user interactively canceled the command // (by pressing ESCAPE, clicking a CANCEL button, etc.) // in a Get operation, dialog, time consuming computation, etc. return CRhinoCommand::success; } ``` ### Notes A couple things to consider: 1. Command classes must return a unique UUID. If you try to use a UUID that is already in use, then your command will not work. Use the *GUIDGEN.EXE* that comes with *Visual Studio* to create unique UUIDs. 2. Command classes must return a unique command name. If you try to use a command name that is already in use, then your command will not work. 3. Only ONE instance of a command class can be created. This is why you should put the definition of your command classes in *.cpp* files. ## Related Topics - [What is a Rhino Plugin?](/guides/general/what-is-a-rhino-plugin) - [Installing Tools (Windows)](/guides/cpp/installing-tools-windows) -------------------------------------------------------------------------------- # Display Conduits Source: https://developer.rhino3d.com/en/guides/rhinocommon/display-conduits/ This guide gives an overview of Display Conduits and how to use them to access Rhino's display pipeline. Rhino lets you define your own display conduits, which provide access to many levels of the display pipeline. They are a bit tricky. This guide cover the concepts and basics of using display conduits. ## Conduit Concept The DisplayPipeline in Rhino is a big and complicated class and we do not recommend you derive your own pipeline. Instead, we've exposed something called a conduit for easy access. The pipeline itself is structured like this (except in reality there are many more channels): ![Rhino Display Pipeline](https://developer.rhino3d.com/images/display-conduits-01.png) At one end is the Rhino model, a collection of 3D geometry and data. At the other end is the image we want to display on the screen, a collection of 2D pixels. To get from model to image, the pipeline has to process a lot of information. These steps have been put into channels. When you implement a new conduit, you have to implement at least one of these channels, like so: ![Rhino Display Conduit](https://developer.rhino3d.com/images/display-conduits-02.png) Note that the pipeline itself is not bound to the channels. It just executes its code and raises events during specific phases of drawing. During the drawing of a single frame the events are raised in the following order. You hook into the pipeline by extending DisplayConduit and overriding the event handlers that have the same name as the pipeline events: 1. *ObjectCulling*: Create a list of all the objects to draw. 1. *CalculateBoundingBox*: Determine the extent of the entire scene. Override this function to increase the bounding box of scene so it includes the geometry that you plan to draw. 1. *CalculateBoundingBoxZoomExtents*: If you want to participate in the Zoom Extents command with your display conduit, then you need to override ZoomExtentsBoundingBox. Typically you could just call your CalculateBoundingBox override, but you may also want to spend a little more time here and compute a tighter bounding box for your conduit geometry. 1. *PreDrawObjects*: Called before objects are drawn. Depth writing and testing are on. Here you could set up the object's display attributes. 1. *PreDrawObject*: Called before every object in the scene is drawn. 1. *PostDrawObjects*: Called after all non-highlighted objects are drawn. Depth writing and testing are still turned on. If you want to draw without depth writing and testing, see DrawForeground. Here you draw stuff on top of all the objects, like selection wireframes. 1. *DrawForeground*: Called after all non-highlighted objects are drawn and PostDrawObjects called. Depth writing and testing are turned *off*. If you want to draw with depth writing and testing, see PostDrawObjects. For example, here you could draw objects like the little axis-system in the lower left corner of viewports. 1. *DrawOverlay*: If Rhino is in a feedback mode, the draw overlay call lets temporary geometry be drawn on top of everything in the scene. This is similar to the dynamic draw routine that occurs with custom get point. You hook into the pipeline by extending DisplayConduit and overriding the key event handlers which have the same name as the pipeline events. ## Implementation In the above image, the conduit overrode two event handlers; `CalculateBoundingBox` and `PostDrawObjects`. In RhinoCommon this conduit would look like: ```cs class MyConduit : Rhino.Display.DisplayConduit { protected override void CalculateBoundingBox(CalculateBoundingBoxEventArgs e) { base.CalculateBoundingBox(e); // .. } protected override void PostDrawObjects(DrawEventArgs e) { base.PreDrawObjects(e); // .. } } ``` By default, conduits are not enabled but it is easily done by setting the Enabled property of the DisplayConduit to true: ```cs var conduit = new MyConduit(); conduit.Enabled = true; ``` Once our conduit is constructed and enabled, the overriden methods will be called whenever the Rhino pipeline raises the event for which our overridden method is registered. In our case, this method will always be called once with the `CalculateBoundingBox` and the `PostDrawObjects` events have been raised (the events have the same name as the overridden methods). Here's a simple example: ```cs class MyConduit : Rhino.Display.DisplayConduit { protected override void CalculateBoundingBox(CalculateBoundingBoxEventArgs e) { base.CalculateBoundingBox(e); var bbox = new BoundingBox(); bbox.Union(new Point3d(0, 0, 0)); e.IncludeBoundingBox(bbox); } protected override void PreDrawObjects(DrawEventArgs e) { base.PreDrawObjects(e); e.Display.DrawPoint(new Point3d(0, 0, 0)); } } ``` The code above simply draws a single point at the world origin. Since a point is 3D geometry, it is subject to z-depth clipping. This means that if the point resides outside the z-buffer region, it will not be visible (it will get *clipped*). By default, the clipping planes are set up to encompass the bounding box of the entire Rhino model. If you are drawing stuff which is potentially outside this box, you should override `CalculateBoundingBox` to make sure your objects are not clipped. Let's take a look at a more complex drawing routine: ```cs protected override void CalculateBoundingBox(CalculateBoundingBoxEventArgs e) { base.CalculateBoundingBox(e); var bbox = new BoundingBox(); bbox.Union(e.Display.Viewport.ConstructionPlane().Origin); e.IncludeBoundingBox(bbox); } protected override void PreDrawObjects(DrawEventArgs e) { base.PreDrawObjects(e); var cPlane = e.Display.Viewport.ConstructionPlane(); var xColor = Rhino.ApplicationSettings.AppearanceSettings.GridXAxisLineColor; var yColor = Rhino.ApplicationSettings.AppearanceSettings.GridYAxisLineColor; var zColor = Rhino.ApplicationSettings.AppearanceSettings.GridZAxisLineColor; e.Display.EnableDepthWriting(false); e.Display.EnableDepthTesting(false); e.Display.DrawPoint(cPlane.Origin, System.Drawing.Color.White); e.Display.DrawArrow(new Line(cPlane.Origin, new Vector3d(cPlane.XAxis) * 10.0), xColor); e.Display.DrawArrow(new Line(cPlane.Origin, new Vector3d(cPlane.YAxis) * 10.0), yColor); e.Display.DrawArrow(new Line(cPlane.Origin, new Vector3d(cPlane.ZAxis) * 10.0), zColor); e.Display.EnableDepthWriting(false); e.Display.EnableDepthTesting(false); } ``` This piece of code draws a colored, c-plane axis system on top of all objects. Because we disable DepthWriting and Testing before drawing my points and arrows, my objects are not obscured by the existing geometry (which was drawn in a channel previous to `PostDrawObjects`): ![Resulting Frame](https://developer.rhino3d.com/images/display-conduits-03.png) ## Warning Another thing to realize is that there can be many other active conduits present as well. There is no way of telling which one will be called first. It is important to consider how your display conduits will interact with other conduits potentially called within the pipeline by other developers. Do not write display conduit code that intentionally disrupts other conduits. ![Many Conduits](https://developer.rhino3d.com/images/display-conduits-04.png) -------------------------------------------------------------------------------- # Extending the GUI Source: https://developer.rhino3d.com/en/guides/grasshopper/extending-the-gui/ This guide describes how to extend the default behaviour, functionality, and graphical user interface (GUI) of Grasshopper document objects. We'll focus on components as you should be familiar with those from earlier topics, but the same logic also applies to Parameters and custom objects. ## Default Functionality and GUI When you derive a class from [GH_Component](/api/grasshopper/html/T_Grasshopper_Kernel_GH_Component.htm) or [GH_Param(T)](/api/grasshopper/html/T_Grasshopper_Kernel_GH_Param_1.htm) you get a lot of functionality and GUI for free. It is quite important that all the objects in Grasshopper behave in a consistent and predictable fashion so you typically don't need to override this default behaviour. However there can be cases where changing the default Grasshopper GUI is the best solution. Overriding the GUI is not exactly a trivial task, there are a lot of different facets to this and I'll discuss the most important ones in this topic. There are three common ways in which the default GUI can be altered: - [Menus](#menus) - [Interaction](#interaction) - [Display](#display) ## Menus It is possible to insert items into the default pop-up menus of objects, or even to completely alter the menu layout. If your component for example can run in two different modes, you can choose to expose an additional GUI option instead of an input parameter. These modes can then be toggled via the component pop-up menu. At the lowest level, pop-up menus are generated by the canvas when a right mouse button click is detected over a component or parameter. The menu by default contains no items and it is the responsibility of said component or parameter to populate the menu. The top-level method which is in charge of this is [AppendMenuItems](/api/grasshopper/html/M_Grasshopper_Kernel_IGH_DocumentObject_AppendMenuItems.htm) which is defined inside `IGH_DocumentObject` and thus propagates to all objects on the canvas. The `GH_DocumentObject` abstract class does not create any menu items, so unless this function is overridden, there will be no popup menu for a certain object. However, it is unlikely you derive directly from `GH_DocumentObject` so this should not be a problem. `GH_ActiveObject`, which is the base class for all objects on the canvas that actually do something (Sketches and Scribbles don't "do" anything, hence they derive directly from `GH_DocumentObject`), does provide a default implementation of `AppendMenuItems()`. The default layout of popup menus is: - NickName - Preview - Enabled - Bake - Warnings - Errors - Help Although if the object in question does not support previews or baking then those items will be missing from the final menu. When overridden, `AppendMenuItems()` allows you to completely replace the default menu: ```cs public override bool AppendMenuItems(ToolStripDropDown menu) { Menu_AppendGenericMenuItem(menu, "First item"); Menu_AppendGenericMenuItem(menu, "Second item"); Menu_AppendGenericMenuItem(menu, "Third item"); Menu_AppendSeparator(menu); Menu_AppendGenericMenuItem(menu, "Fourth item"); Menu_AppendGenericMenuItem(menu, "Fifth item"); Menu_AppendGenericMenuItem(menu, "Sixth item"); // Return true, otherwise the menu won't be shown. return true; } ``` ```vbnet Public Overrides Function AppendMenuItems(ByVal menu As ToolStripDropDown) As Boolean Menu_AppendGenericMenuItem(menu, "First item") Menu_AppendGenericMenuItem(menu, "Second item") Menu_AppendGenericMenuItem(menu, "Third item") Menu_AppendSeparator(menu) Menu_AppendGenericMenuItem(menu, "Fourth item") Menu_AppendGenericMenuItem(menu, "Fifth item") Menu_AppendGenericMenuItem(menu, "Sixth item") 'Return True, otherwise the menu won't be shown. Return True End Function ``` ![AppendMenuItems](https://developer.rhino3d.com/images/extending-the-gui-01.png) If you want to insert additional items into the menu it would be very annoying if you had to recreate this default menu layout from scratch every single time. So even though you can override `AppendMenuItems()` it is recommended you instead override `AppendAdditionalMenuItems()` which allows you to insert custom menu items between Errors and Help without losing the standard functionality: ```cs public override void AppendAdditionalMenuItems(ToolStripDropDown menu) { base.AppendAdditionalMenuItems(menu); Menu_AppendGenericMenuItem(menu, "First item"); Menu_AppendGenericMenuItem(menu, "Second item"); Menu_AppendGenericMenuItem(menu, "Third item"); Menu_AppendSeparator(menu); Menu_AppendGenericMenuItem(menu, "Fourth item"); Menu_AppendGenericMenuItem(menu, "Fifth item"); Menu_AppendGenericMenuItem(menu, "Sixth item"); } ``` ```vbnet Public Overrides Sub AppendAdditionalMenuItems(ByVal menu As ToolStripDropDown) MyBase.AppendAdditionalMenuItems(menu) Menu_AppendGenericMenuItem(menu, "First item") Menu_AppendGenericMenuItem(menu, "Second item") Menu_AppendGenericMenuItem(menu, "Third item") Menu_AppendSeparator(menu) Menu_AppendGenericMenuItem(menu, "Fourth item") Menu_AppendGenericMenuItem(menu, "Fifth item") Menu_AppendGenericMenuItem(menu, "Sixth item") End Sub ``` ![AppendAdditionalMenuItems](https://developer.rhino3d.com/images/extending-the-gui-02.png) `GH_Param` for example derives from `GH_ActiveObject` and re-implements `AppendAdditionalMenuItems()` in order to supply the Wire Display, Disconnect, Reverse, Flatten and Graft menu items that are present in all parameters. `GH_Component`, likewise, derives from `GH_ActiveObject` and it re-implements `AppendAdditionalMenuItems()` in order to insert the Shortest List, Longest List, Cross Reference and the parameter submenus. Long story short; if you wish to insert additional menu items then either re-implement `AppendAdditionalMenuItems()` if you derive from `GH_Param` or re-implement `AppendAdditionalComponentMenuItems()` if you derive from `GH_Component`: ```cs // If you implement IGH_Param, then override this method. If you implement IGH_Component, // then override AppendAdditionalComponentMenuItems instead. protected override void AppendAdditionalMenuItems(ToolstripDropDown menu) { // Place a call to the base class to ensure the default parameter menu // is still there and operational. base.AppendAdditinalMenuItems(menu); // Now insert your own custom menu items. Menu_AppendGenericMenuItem(menu, "My Custom Menu Item", Menu_MyCustomItemClicked); } private void Menu_MyCustomItemClicked(Object sender, EventArgs e) { Rhino.RhinoApp.WriteLine("Alcohol doesn't affect me..."); } ``` ```vbnet 'If you implement IGH_Param, then override this method. If you implement IGH_Component, 'then override AppendAdditionalComponentMenuItems instead. Protected Overrides Sub AppendAdditionalMenuItems(ByVal menu As ToolstripDropDown) 'Place a call to the base class to ensure the default parameter menu 'is still there and operational. MyBase.AppendAdditinalMenuItems(menu) 'Now insert your own custom menu items. Menu_AppendGenericMenuItem(menu, "My Custom Menu Item", Addressof Menu_MyCustomItemClicked) End Sub Private Sub Menu_MyCustomItemClicked(ByVal sender As Object, ByVal e As EventArgs) Rhino.RhinoApp.WriteLine("Alcohol doesn't affect me...") End Sub ``` There's a lot of menu utility functions provided by `GH_DocumentObject`. They all start with "Menu_" and they'll make it much easier to insert formatted menu items. But of course nothing is stopping you from using typical `System.Windows.Forms` plumbing to extend the pop-up menu. ## Interaction You can also choose to handle specific mouse and keyboard events. You could for example choose to respond to a mouse double-click event on your component. Complicated objects such as Gradients and Graph Mappers handle all sorts of mouse events to provide grip-dragging functionality directly within the Grasshopper canvas. ## Display Finally, it is possible to override the way an object displays itself on the canvas. These alterations can be minor, mere additions to the default display, or they can be radically different (as with the Gradient and Graph Mapper objects). For a detailed explanation regarding display overrides, see the [Custom Attributes](/guides/grasshopper/custom-attributes) guide. ## Related Topics - [Custom Attributes](/guides/grasshopper/custom-attributes) -------------------------------------------------------------------------------- # How Hops Works Source: https://developer.rhino3d.com/en/guides/compute/how-hops-works/ The communication process between Hops and a Hops compliant server is a little more nuanced than simply sending and receiving a single http request and response. The first step in the process occurs when Hops bundles up the referenced Grasshopper definition and sends a http request to an endpoint on the server. An endpoint is a URL address where the server can be reached to perform a certain function. In this request, the server opens the Grasshopper definition sent from Hops and determines what information will be needed to populate the inputs and outputs for the Hops component. So, the endpoint will be called `/io` (short for Input Output). The Hops component should now have enough information to create the necessary inputs and output nodes for itself. When all of the Hops inputs have been connected to source parameters, it will then send another http request to the server - only this time it will it will send the request to the `/solve` endpoint. The Grasshopper definition does not need to be resent since it was stored on the server during the `/io` process. Instead, the data sent in the `/solve` request only contains a pointer ID which tells the server where to find the correct file and all of the input data. The http response from the server contains all data which would be returned from running the Grasshopper file in the traditional manner. To have a better understanding of how each step above works, you can export the last http request and response for both the `/io` and `/solve` endpoints directly from the Hops component. --- ## Quick Links - [What is Hops](../what-is-hops) - [The Hops Component](../hops-component) - [Setting up a Production Environment](../deploy-to-iis) -------------------------------------------------------------------------------- # How to create a virtual machine (VM) on Azure Source: https://developer.rhino3d.com/en/guides/compute/creating-an-Azure-VM/ ## Creating the Virtual Machine In this guide, we will walk through the process of setting up a virtual machine using Azure services. To start, please confirm that you have a valid Azure subscription and that you have already setup a resource group to hold the various resources for this instance. To learn more about setting up a resource group on Azure, [visit this page](https://docs.microsoft.com/en-us/azure/azure-resource-manager/management/manage-resource-groups-portal). 1. Sign in to the Azure [portal](https://portal.azure.com/#home). 1. Type **virutal machines** in the search bar. 1. Under **Services**, select **Virtual machines**. 1. In the **Virtual machines** page, select **Create** then **Virtual Machine** 1. In the **Basics** tab, under **Project details**, make sure the correct Subscription and Resource group are selected. 1. Under **Instance details**, create a unique name for the virutal machine. We'll use *Rhino-Compute-VM* for our VM name. Select a region close to you, and then select *Windows Server 2022 Datacenter - Azure Edition Gen2* for the **Image**. Feel free to select any **Size** VM that fits your needs. We'll use *Standard DS2_v2* for this example. Leave the other defaults. 1. Under **Administrator account**, provide a username and a password. Take note of these credentials as we will use these when we log into the remote machine. 1. Under **Inbound port rules**, choose **Allow selected ports** then select **RDP (3389)**, **HTTPS (443)**, and **HTTP (80)**. 1. Select **Next : Disk >**. 1. Select **Next : Networking >**. 1. Under the **Network interface** section, click on the *Create new* button under the **Public IP** subsection. 1. When the pop-out blade opens up, select **Static** under the Assignment tab. Click **OK** to save this setting. 1. Leave all other defaults. Select **Review + create**. 1. Once your configuration passes the validation check, select **Create** to deploy your virtual machine. 1. After deployment is complete, select **Go to resource**. ### Add an inbound port rule Once your virtual machine has been deployed you should be able to go to the resource home page. Here, you can change various settings and configurations. We are going to add an inbound port rule so that we can send API requests on a dedicated port. By default, Azure denies and blocks all public inbound traffic - which also includes ICMP traffic. This is a good thing since it improves security by reducing the attack surface. The [Internet Control Message Protocol (ICMP)](https://en.wikipedia.org/wiki/Internet_Control_Message_Protocol) is typically used for diagnostics and to troubleshoot networking issues. We'll want to turn ICMP traffic on for our VM so that we can try to ping the IP address and make sure we get a response. The first thing we need to do is add an inbound port rule for ICMP traffic. 1. On the left-hand side menu, select the **Networking** menu item. This will open the Networking blade. 1. Click on the **Add inbound port rule** button 1. In the **Add inbound security rule** pane, set the **Destination port ranges** to *, change the **Protocol** to **ICMP**, set the **Priority** to **100**, and type **ICMP** in the **Name** input. 1. Click **Add** to create the new inbound port rule. Congratulations! In this guide, you deployed a simple virtual machine on Azure. -------------------------------------------------------------------------------- # Installing Tools (Mac) Source: https://developer.rhino3d.com/en/guides/grasshopper/installing-tools-mac/ This guide covers all the necessary tools required to author Grasshopper components on Mac. By the end of this guide, you should have all the tools installed necessary for authoring, building, and debugging Grasshopper components using RhinoCommon in Rhino for Mac. ## Prerequisites This guide presumes you have an: - [Apple Mac](http://store.apple.com/) running [macOS](https://www.apple.com/osx/) Monterey (12) or later. - [Rhino for Mac](https://www.rhino3d.com/download/) ## Install Visual Studio Code Visual Studio for Mac has been [retired by Microsoft](https://learn.microsoft.com/en-us/visualstudio/mac/what-happened-to-vs-for-mac?view=vsmac-2022). #### Step-by-Step 1. *[Download Visual Studio Code](https://code.visualstudio.com/)*. 1. Once you have downloaded the *VSCode-darwin-universal.zip*, double-click it to unzip. 1. Drag Visual Studio Code to the */Applications* folder. 1. Open Visual Studio Code, you will need to click "Open" the first time you open it. 1. You will need to install some packages before starting, luckily all the required pacakges are bundled together. These can be found by clicking "Extensions" on the left side bar: - [Intellicode for C# Dev Kit](https://marketplace.visualstudio.com/items?itemName=ms-dotnettools.vscodeintellicode-csharp) - [NuGet Gallery Plugin](https://marketplace.visualstudio.com/items?itemName=patcx.vscode-nuget-gallery) (*Recommended*) 1. Restart Visual Studio Code. 1. Visual Studio Code is installed in your */Applications* folder. You will want to *drag its icon to your Dock* for future use or - if it's running - right/option-click the icon in the Dock and select *Keep in Dock*. ## Next Steps *Congratulations!* You have all the tools necessary to build a Grasshopper component on macOS. *Now what?* Check out the [Your First Component (Mac)](/guides/grasshopper/your-first-component-mac) guide for instructions building - your guessed it - your first component. -------------------------------------------------------------------------------- # Installing Tools (Mac) Source: https://developer.rhino3d.com/en/guides/rhinocommon/installing-tools-mac/ This guide covers all the necessary tools required to author Rhino plugins on Mac. By the end of this guide, you should have all the tools installed necessary for authoring, building, and debugging C# .NET plugins using RhinoCommon in Rhino for Mac. ## Prerequisites This guide presumes you have an: - [Apple Mac](http://store.apple.com/) running [macOS](https://www.apple.com/osx/) Monterey (12) or later. - [Rhino for Mac](https://www.rhino3d.com/download/) ## Install Visual Studio Code Visual Studio for Mac has been [retired by Microsoft](https://learn.microsoft.com/en-us/visualstudio/mac/what-happened-to-vs-for-mac?view=vsmac-2022). #### Step-by-Step 1. *[Download Visual Studio Code](https://code.visualstudio.com/)*. 1. Once you have downloaded the *VSCode-darwin-universal.zip*, double-click it to unzip. 1. Drag Visual Studio Code to the */Applications* folder. 1. Open Visual Studio Code, you will need to click "Open" the first time you open it. 1. You will need to install some packages before starting, luckily all the required pacakges are bundled together. These can be found by clicking "Extensions" on the left side bar: - [Intellicode for C# Dev Kit](https://marketplace.visualstudio.com/items?itemName=ms-dotnettools.vscodeintellicode-csharp) - [NuGet Gallery Plugin](https://marketplace.visualstudio.com/items?itemName=patcx.vscode-nuget-gallery) (*Recommended*) 1. Restart Visual Studio Code. 1. Visual Studio Code is installed in your */Applications* folder. You will want to *drag its icon to your Dock* for future use or - if it's running - right/option-click the icon in the Dock and select *Keep in Dock*. ## Next Steps *Congratulations!* You have all the tools necessary to build a RhinoCommon plugin for Rhino for Mac. *Now what?* Check out the [Your First Plugin (Mac)](/guides/rhinocommon/your-first-plugin-mac) guide for instructions building - your guessed it - your first plugin. -------------------------------------------------------------------------------- # Linking with openNURBS Source: https://developer.rhino3d.com/en/guides/opennurbs/linking-with-opennurbs/ This guide discusses openNURBS linking. ## Problem You are trying to write a simple console application like the *example_write* sample included with the openNURBS toolkit. However, you are having problems linking. You are using the *opennurbs_staticlib_linking_pragmas.h* header included with openNURBS, but it does not seem to work. ## Solution The *opennurbs_staticlib_linking_pragmas.h* header, included with openNURBS, is only designed to help build the example projects included with the toolkit. That is, it only works if your project is inside the openNURBS folder. Unlike the `#include` statement, `#pragma` statements, used in this header, are not very path smart. The library paths used by `#pragma` statement must be relative to the project, not the `#include` file in which it is used. You certainly don't have to use *opennurbs_staticlib_linking_pragmas.h* in your project. You can always add the required openNURBS libraries to your project's *Additional Dependencies* link settings. You can also just include the required libraries in your project, like you would a *.cpp* or *.h* file. But, if you like the flexibility that providing linking instructions via `#pragma` statements provide, then you can always change *opennurbs_staticlib_linking_pragmas.h* to reflect the path to your project. That said, if you use openNURBS in more than one project and the projects are not all stored in the same relative location, this will not work. ## Sample Below is sample that contains two header files that you can include in any project that needs to link with openNURBS. One of the headers provides instruction for statically linking openNURBS, and the other one for dynamically linking. Once these files are included in your project, you can edit them to reflect your project's relative path to the openNURBS toolkit. ### opennurbs_static_linking.h ```cpp ///////////////////////////////////////////////////////////////////////////// // linking_pragmas_static.h // This file is specific to Micrsoft's compiler. #pragma once #if defined(ON_DLL_IMPORTS) || defined(ON_DLL_EXPORTS) #error This file contains STATIC LIBRARY linking pragmas. #endif #if defined(WIN64) // x64 (64 bit) static libraries #if defined(NDEBUG) // Release x64 (64 bit) libs #pragma message( " --- openNURBS Release x64 (64 bit) build." ) #pragma comment(lib, "../opennurbs/zlib/x64/Release/zlibx64.lib") #pragma comment(lib, "../opennurbs/x64/ReleaseStaticLib/opennurbsx64_static.lib") #else // _DEBUG // Debug x64 (64 bit) libs #pragma message( " --- openNURBS Debug x64 (64 bit) build." ) #pragma comment(lib, "../opennurbs/zlib/x64/Debug/zlibx64_d.lib") #pragma comment(lib, "../opennurbs/x64/DebugStaticLib/opennurbsx64_staticd.lib") #endif // NDEBUG else _DEBUG #else // WIN32 // x86 (32 bit) static libraries #if defined(NDEBUG) // Release x86 (32 bit) libs #pragma message( " --- openNURBS Release x86 (32 bit) build." ) #pragma comment(lib, "../opennurbs/zlib/Release/zlib.lib") #pragma comment(lib, "../opennurbs/ReleaseStaticLib/opennurbs_static.lib") #else // _DEBUG // Debug x86 (32 bit) libs #pragma message( " --- openNURBS Debug x86 (32 bit) build." ) #pragma comment(lib, "../opennurbs/zlib/Debug/zlib_d.lib") #pragma comment(lib, "../opennurbs/DebugStaticLib/opennurbs_staticd.lib") #endif // NDEBUG else _DEBUG #endif // WIN64 else WIN32 ``` ### opennurbs_dynamic_linking.h ```cpp ///////////////////////////////////////////////////////////////////////////// // linking_pragmas_static.h // This file is specific to Micrsoft's compiler. #pragma once #if !defined(ON_DLL_IMPORTS) #error This file contains DYNAMIC LIBRARY linking pragmas. #endif #if defined(WIN64) // x64 (64 bit) dynamic libraries #if defined(NDEBUG) // Release x64 (64 bit) libs #pragma message( " --- openNURBS Release x64 (64 bit) build." ) #pragma comment(lib, "../opennurbs/zlib/x64/Release/zlibx64.lib") #pragma comment(lib, "../opennurbs/x64/Release/opennurbsx64.lib") #else // _DEBUG // Debug x64 (64 bit) libs #pragma message( " --- openNURBS Debug x64 (64 bit) build." ) #pragma comment(lib, "../opennurbs/zlib/x64/Debug/zlibx64_d.lib") #pragma comment(lib, "../opennurbs/x64/Debug/opennurbsx64_d.lib") #endif // NDEBUG else _DEBUG #else // WIN32 // x86 (32 bit) dynamic libraries #if defined(NDEBUG) // Release x86 (32 bit) libs #pragma message( " --- openNURBS Release x86 (32 bit) build." ) #pragma comment(lib, "../opennurbs/zlib/Release/zlib.lib") #pragma comment(lib, "../opennurbs/Release/opennurbs.lib") #else // _DEBUG // Debug x86 (32 bit) libs #pragma message( " --- openNURBS Debug x86 (32 bit) build." ) #pragma comment(lib, "../opennurbs/zlib/Debug/zlib_d.lib") #pragma comment(lib, "../opennurbs/Debug/opennurbs_d.lib") #endif // NDEBUG else _DEBUG #endif // WIN64 else WIN32 ``` To use these headers, just include them to your project's *stdafx.h* file like this: ```cpp // stdafx.h #pragma once #include "targetver.h" #include #include // TODO: reference additional headers your program requires here // Uncomment the following if you want to use the openNURBS dynamic link library //#define ON_DLL_IMPORTS #include "../opennurbs/opennurbs.h" #if defined(ON_DLL_IMPORTS) #include "opennurbs_dynamic_linking.h" #else #include "opennurbs_static_linking.h" #endif ``` -------------------------------------------------------------------------------- # Multi-threaded components Source: https://developer.rhino3d.com/en/guides/grasshopper/multi-treaded-components/ A guide to parallel computing in Grasshopper ## Overview Grasshopper for Rhino 6 provides multi-threaded solving in specific components. Benchmarks have shown that Grasshopper can be up to 20% faster when using multi-threaded components. Results may vary as there are only specific components that can compute in parallel. The following components have been modified to perform calculations using multiple threads. * Curve Plane Intersection * Project Curve * Pull Curve * Split with Brep * Shatter * Split with Breps * Trim with Brep * Trim with Breps * Area * Area Moments * Volume * Volume Moments * Brep Closest Point * Mesh Plane Intersection * Brep Line Intersection * Brep Brep Intersection * Brep Plane Intersection * Curve Curve Intersection * Curve Curves Intersection * Point in Brep * Point in Breps * Curve Self-Intersection * Contour * Dash Pattern * Divide Curve * Boundary Surface Multi-threaded components are decorated with small dots in the upper left corner to help you understand the component’s capabilities and current ‘mode’ of operation. * No dots : the component does not currently support multi-threaded calculations * One dot: the component does support multi-threaded calculations, but is currently calculating with a single thread (i.e. legacy mode) * Two dots: the component does support multi-threaded calculations and is solving using multiple threads. For components that support multi-threaded calculations, the feature can be enabled/disabled using the right click context menu on the component itself. We continue to look for components that would be useful to have multi-threaded. Join the [Multi-threaded Grasshopper component discussion](https://discourse.mcneel.com/t/v6-feature-multi-threaded-gh-components/47049) to participate. ## Creating you own multi-threaded components in Grasshopper Custom multi-threaded components are also possible by programmer your own components in C# or Python. for details on this read the [Making Task Capable Components in Grasshopper](/guides/grasshopper/programming-task-capable-component/) guide -------------------------------------------------------------------------------- # Points in Python Source: https://developer.rhino3d.com/en/guides/rhinopython/python-rhinoscriptsyntax-points/ This guide provides an overview of the RhinoScriptSyntax Point Geometry in Python. ## Points In Python, a Rhino 3D point is represented as a [Point3d](http://developer.rhino3d.com/api/RhinoCommon/html/T_Rhino_Geometry_Point3d.htm) structure. Conceptually, Point3d exist in memory as a zero-based list containing three numbers. These three numbers represent the X, Y and Z coordinate values of the point. ``` point3D contains (1.0, 2.0, 3.0) ``` ### Creating Points A Point3D structure can be constructed in a number of different ways. Two common ways are: ```python import rhinoscriptsyntax as rs pnt = rs.CreatePoint(1.0, 2.0, 3.0) pnt = rs.CreatePoint(1.0, 2.0) # This creates a point with the Z coordinate set to 0 ``` The 'CreatePoint()' function is very flexible. It can take a tuple of two or 3 numbers and return a Point3d. The function can also extract the coordinates of a Rhino GUID to return a Point3D. It is not always necessary to construct a point before passing it to a function that requires a point. It is possible to construct points directly as an argument to a function. A Point is a list like structure. Wrap coordinates in parenthesis`()` when passing them directly to a function. For instance the `rs.addline(point, point)` function requires two points. Use the following syntax to construct the points on the fly: ```python rs.AddLine((45,56,32),(56,47,89)) ``` Like 3-D points, Python represents a single 2-D point as a zero-based list of numbers. The difference being that 2-D points contain only X and Y coordinate values. Passing coordinates in `()` to a function is common with RhinoScriptSyntax. ### Accessing Point Coordinates A Point3D list can be accessed like a simple python tupple, one element at a time: ```python import rhinoscriptsyntax as rs pnt = rs.CreatePoint(1.0, 2.0, 3.0) print(pnt[0]) #Print the X coordinate of the Point3D print(pnt[1]) #Print the Y coordinate of the Point3D print(pnt[2]) #Print the Z coordinate of the Point3D ``` The coordinates of a Point3d may also be accessed through its .X, .Y and .Z properties. ```python import rhinoscriptsyntax as rs pnt = rs.CreatePoint(1.0, 2.0, 3.0) print(pnt.X) # Print the X coordinate of the Point3D print(pnt.Y) # Print the Y coordinate of the Point3D print(pnt.Z) # Print the Z coordinate of the Point3D ``` ### Editing Points To change an individual coordinate of a Point3d simply assign a new value to the correct coordinate through the index location or coordinate property: ```python import rhinoscriptsyntax as rs pnt = rs.CreatePoint(1.0, 2.0, 3.0) pnt[0] = 5.0 # Sets the X coordinate to 5.0 pnt.Y = 45.0 # Sets the Y coordinate to 45.0 print(pnt) #Print the new coordinates ``` Rhinoscriptsyntax contains a number of methods to manipulate points. See [RhinoScript Points and Vectors Methods](/guides/rhinopython/python-rhinoscriptsyntax-point-vector-methods) for details. ### Adding a point to display in Rhino It is important to understand the difference between a Point3d and a point object added to Rhino's document. Any geometry object that exists in Rhino's database is assigned an object identifier. This is represented as a [Guid](/guides/rhinopython/python-rhinoscriptsyntax-objects). The [Guid's object](/guides/rhinopython/python-rhinoscriptsyntax-objects) is geometry and the associated attributes that can be drawn, belongs to a layer, is saved to a Rhino file and is counted as a Rhino object. A Point3d is simply a data structure of 3 numbers that exists in memory. It can be manipulated in memory, but will not be seen in Rhino or saved. A Point3d is added to the Rhino document through the `rs.AddPoint()` command. To create a Point3d from a Guid, use the `rs.PointCoordinates(guid)` or the `rs.CreatePoint(Guid)` function. ## Related Topics - [What is RhinoScriptSyntax and RhinoScript?](/guides/rhinopython/what-is-rhinopython) - [Python List of Points](/guides/rhinopython/python-rhinoscriptsyntax-list-points) - [Python Vectors](/guides/rhinopython/python-rhinoscriptsyntax-vectors) - [Python Lines](/guides/rhinopython/python-rhinoscriptsyntax-lines) - [Python Planes](/guides/rhinopython/python-rhinoscriptsyntax-plane) - [Python Objects](/guides/rhinopython/python-rhinoscriptsyntax-objects) - [RhinoScript Points and Vectors Methods](/guides/rhinopython/python-rhinoscriptsyntax-point-vector-methods) - [Point3d RhinoCommon Documentation](http://developer.rhino3d.com/api/RhinoCommon/html/T_Rhino_Geometry_Point3d.htm) -------------------------------------------------------------------------------- # Python Procedures Source: https://developer.rhino3d.com/en/guides/rhinopython/python-procedures/ This guide discusses Python procedures. ## Overview In Python a function is a named block of code that can perform a reusable action. This allows Python code to be broken down into functional, reusable blocks of code. There are many modules available for Python. These modules contain a great number of pre-defined procedures that can be very useful. There are libraries that help with Date, Time, Math, etc. ## Import Modules You can use any Python file as a module by using the import statement. Once imported all the procedures in the import are available. The standard syntax for importing is: ```python import rhinoscriptsyntax ``` To import more the one module, use commas to separate module names: ```python import rhinoscriptsyntax, time, math ``` To access procedures in imported modules, prefix the function with the imported model name, separated by a period (.): ```python import time print (time.strftime("%H:%M:%S")) #strftime is a proccedure in the time module. ``` The `import` statement can also be used to change the reference name of the incoming module. Use this function to make module names shorter to use and easier to read in the code. A very common example of this is how we normally will shorted the `rhinoscriptsyntax` module to `rs` for convenience: ```python import rhinoscriptsyntax as rs rs.AddPoint (1, 2, 3) # The Rhinoscriptsyntax module is accessed throught 'rs' abreviation. ``` It is also possible to import only a portion of a module. In the following case, only certain namespaces are imported from the larger `Syste.IO` module: ```python from System.IO import Path, File, FileInfo, FileAttributes ``` The imported modules above are referenced by using `Path`, `File`, `FileInfo`, `FileAttributes` as namespaces. ## Common Modules There are many modules available for Python. Some of the most useful to Rhino Python are: * Rhinoceros modules * rhinoscriptsyntax - The basic rhino library of procedures * rhino - * String Services * string — Common string operations * StringIO — Read and write strings as files * fpformat — Floating point conversions * Date and Time * datetime — Basic date and time types * time — Time access and conversions * Numeric and Mathematical Modules * math — Mathematical functions * fractions — Rational numbers * random — Generate pseudo-random numbers * File and Directory Access * System.IO — Common pathname manipulations * tempfile — Generate temporary files and directories * csv — CSV File Reading and Writing A complete list of predefined modules in Python, see the [Python Standard Library modules](https://docs.python.org/2/library/) ## User-Defined Procedures A `Function` is a series of Python statements begins by a `def`, followed by the function name and enclosed in parenthesis. A Function may or may not return a value. A Function procedure can take arguments (constants, variables, or expressions that are passed by a calling procedure). If a Function procedure has no arguments, its `def` statement should include an empty set of parentheses `()`. Parameters can also be defined within the parenthesis. The parenthesis are followed by a colon (:) to end the first line. The end of the function is marked by the loss of whitespace in the next line of the code (ending the code block). It is common practice to use a `return` statement followed by the argument to return a value. You may also finish a function with a return statement and a simple colon (;). In the following example, the Celsius `def` calculates degrees Celsius from degrees Fahrenheit. When the def is called from the `ConvertTemp` def procedure, a variable containing the argument value is passed to the def. The result of the calculation is returned to the calling procedure and displayed in a message box. ```python def Celsius(fDegrees): _Celsius = (fDegrees - 32) * 5 / 9 return _Celsius; # Use this code to call the Celsius function temp = raw_input("Please enter the temperature in degrees F.", 1) MsgBox "The temperature is " & Celsius(temp) & " degrees C." ``` ## Getting Data In and Out Each piece of data is passed into your procedures using an argument. Arguments serve as placeholders for the data you want to pass into your procedure. You can name your arguments any valid variable name. When you create a procedure parentheses must be included after the name of the procedure. Any arguments are placed inside these parentheses, separated by spaces. For example, in the following example, `fDegrees` is a placeholder for the value being passed into the Celsius function for conversion. ```python Function Celsius(fDegrees) _Celsius = (fDegrees - 32) * 5 / 9 return _Celsius; ``` To call a procedure from another procedure, type the name of the procedure along with values for any required arguments, each separated by a space. The function will returns a single value based on its final `return` statement. A return statement with a variable name, followed by a semicolon (;) returns the reference to that variable. A return statement with simply a semicolon (;) returns nothing. The return statement is not required to end a procedure. ## Assigning a Function to a Variable Python allows a variable to contain a function. ## Related Topics - [What are Python and RhinoScriptSyntax?](/guides/rhinopython/what-is-rhinopython) - [Additional information on Functions in Python](https://www.tutorialspoint.com/python/python_functions.htm) -------------------------------------------------------------------------------- # Register as an Issuer in Cloud Zoo Source: https://developer.rhino3d.com/en/guides/rhinocommon/cloudzoo/cloudzoo-issuer/ This guide explains how to register yourself as an issuer in Cloud Zoo In Cloud Zoo, an _issuer_ represents a vendor of products with license keys. If you want your plugin to use Cloud Zoo, you must register to be an issuer by emailing support@mcneel.com with the subject _"Cloud Zoo issuer registration for NAME"_ (replace _NAME_ with your name\*) or clicking on the link below. Send us a registration email \* _Please provide the name (organization or individual) that you would like the issuer to be registered under, e.g. "Acme Inc" or "Jolyn Bloggs"._ You will be provided with an issuer id and a secret that will uniquely identify you as a product vendor and will allow you to implement the required HTTPS callbacks. -------------------------------------------------------------------------------- # Render Engine Integration - Modal Source: https://developer.rhino3d.com/en/guides/rhinocommon/render-engine-integration-modal/ This guide, the second of a series, demonstrates integrating a modal rendering engine using RhinoCommon. ## Overview This is part two in the series on render engine integration in Rhinoceros 3D using RhinoCommon. 1. [Setting up the plug-in](/guides/rhinocommon/render-engine-integration-introduction/) 1. Modal Rendering (this guide) 1. [ChangeQueue](/guides/rhinocommon/render-engine-integration-changequeue/) 1. [Interactive render - viewport integration](/guides/rhinocommon/render-engine-integration-interactive-viewport/) 1. Preview render *(forthcoming)* If you have not already read [part one]((/guides/rhinocommon/render-engine-integration-introduction/)), please do so before proceeding. ## Render To implement a modal rendering solution for Rhinoceros there are two particular pieces you'll need to create, which will allow you to render into a separate render window: a custom implementation of a `Rhino.Render.AsyncRenderContext`, and a `Rhino.Render.RenderPipeline`. The full source code of this plug-in can be found [here](https://github.com/mcneel/rhino-developer-samples/tree/6/rhinocommon/cs/SampleCsRendererIntegration/MockingBird/MockingBirdModal). The rendering will start when giving the Rhino command `Render`. This will result in a call into the `Render()` of your Render plug-in implementation. ```cs protected override Result Render(RhinoDoc doc, RunMode mode, bool fastPreview) { // initialise our render context MockingRenderContext rc = new MockingRenderContext(); // initialise our pipeline implementation RenderPipeline pipeline = new MockingRenderPipeline(doc, mode, this, rc); // query for render resolution var renderSize = RenderPipeline.RenderSize(doc); // set up view info ViewInfo viewInfo = new ViewInfo(doc.Views.ActiveView.ActiveViewport); // set up render window rc.RenderWindow = pipeline.GetRenderWindow(); // add a wireframe channel for curves/wireframes/annotation etc. rc.RenderWindow.AddWireframeChannel(doc, viewInfo.Viewport, renderSize, new Rectangle(0, 0, renderSize.Width, renderSize.Height)); // set correct size rc.RenderWindow.SetSize(renderSize); // now fire off render thread. var renderCode = pipeline.Render(); // note that the rendering isn't complete yet, rather the pipeline.Render() // call starts a rendering thread. Here we essentially check whether // starting that thread went ok. if (renderCode != RenderPipeline.RenderReturnCode.Ok) { RhinoApp.WriteLine("Rendering failed:" + rc.ToString()); return Result.Failure; } // all ok, so we are apparently rendering. return Result.Success; } ``` In our `Render()` function we start by initialising our render context (line 4) and render pipeline (line 7). The render context will be essentially where our actual rendering code will be hosted. The render pipeline starts and stops our engine (context) as necessary. In `Render()` we also ensure we have a `RenderWindow`, set to the requested render resolution (lines 10-20). As said, for the modal rendering to get hooked up two classes need to be implemented. An implementation of a RenderPipeline is used to communicate start and stop events between Rhino and the second class, an implementation of AsyncRenderContext. The latter is essentially where a custom render engine will be hooked up to the Rhino world. RenderPipeline requires several functions to be overridden. For our modal (production) render case we are interested mostly in `OnRenderBegin()`, `OnRenderEnd()` and `ContinueModal()`. ```cs public class MockingRenderPipeline : RenderPipeline { private readonly MockingRenderContext m_rc; public MockingRenderPipeline(RhinoDoc doc, RunMode mode, RenderPlugIn plugin, MockingRenderContext rc) : base(doc, mode, plugin, RenderSize(doc), "MockingBird (modal)", Rhino.Render.RenderWindow.StandardChannels.RGBA, false, false) { m_rc = rc; } protected override bool OnRenderBegin() { m_rc.Thread = new Thread(m_rc.Renderer) { Name = "MockingBird Modal Rendering thread" }; m_rc.Thread.Start(); return true; } protected override bool OnRenderBeginQuiet(Size imageSize) { return OnRenderBegin(); } protected override bool OnRenderWindowBegin(RhinoView view, Rectangle rectangle) { return false; } protected override void OnRenderEnd(RenderEndEventArgs e) { // stop render engine here. m_rc.StopRendering(); } protected override bool ContinueModal() { return !m_rc.Done; } } ``` On our implementation of AsyncRenderContext we need to override one function, `StopRendering()`. Obviously we have one extra function, which is the main entry to our rendering code, `Renderer()`. ```cs /// /// The render context essentially hosts our render engine. It'll contain the /// main render entry function that gets called by the RenderPipeline /// mechanism. /// public class MockingRenderContext : AsyncRenderContext { /// /// Hold on to the thread /// public Thread Thread { get; set; } /// /// Hold on to the render window (note, may be moved to base class /// AsyncRenderContext /// public RenderWindow RenderWindow { get; set; } public bool Done { get; private set; } private bool Cancel { get; set; } /// /// Called when through UI interaction the render process is to be /// stopped. /// public override void StopRendering() { Cancel = true; } // our main rendering function. public void Renderer() { RhinoApp.WriteLine("Starting modal MockingBird"); Done = false; using (var channel = RenderWindow.OpenChannel(RenderWindow.StandardChannels.RGBA)) { var size = RenderWindow.Size(); var max = (float)size.Width*size.Height; var rendered = 0; for (var x = 0; x < size.Width; x++) { for (var y = 0; y < size.Height; y++) { channel.SetValue(x, y, Color4f.FromArgb(1.0f, 1.0f, 0.75f, 0.5f)); rendered++; Thread.Sleep(1); RenderWindow.SetProgress("rendering...", rendered/max); } if (Cancel) break; } } // must set progress to 1.0f to signal RenderWindow (and pipeline/rc) // that rendering is now done. RenderWindow.SetProgress("MockingBird Modal done", 1.0f); // and send completion signal RenderWindow.EndAsyncRender(RenderWindow.RenderSuccessCode.Completed); Done = true; RhinoApp.WriteLine("... done"); } } ``` ## Next Steps This is part two in the series on render engine integration in Rhinoceros using RhinoCommon. The next guide is [Render Engine Integration - ChangeQueue](/guides/rhinocommon/render-engine-integration-changequeue/). ## Related Topics - [Render Engine Integration - Introduction](/guides/rhinocommon/render-engine-integration-introduction/) - [Render Engine Integration - ChangeQueue](/guides/rhinocommon/render-engine-integration-changequeue/) - [Render Engine Integration - Interactive Viewport](/guides/rhinocommon/render-engine-integration-interactive-viewport/) - Preview render *(forthcoming)* -------------------------------------------------------------------------------- # Running and Debugging Compute Locally Source: https://developer.rhino3d.com/en/guides/compute/development/ Deploy Compute for Production Rhino.Compute allows you to bring the Rhino's geometric computation capabilities to the cloud. Before deploying your application to a production environment, you should ensure that everything works perfectly in a controlled environment. This guide is intended for developers who are familiar with Windows and have basic knowledge of [Visual Studio](https://visualstudio.microsoft.com/downloads/) and [Git](https://git-scm.com/downloads). Whether you are a seasoned Rhino user or new to Rhino.Compute, this documentation will provide you with the necessary steps to set up your development environment and start debugging effectively. ## Prerequisites Before diving into the setup of Rhino.Compute on your local machine, you need to ensure that you have the right tools. Here’s what you'll need: - Windows Operating System. Rhino.Compute only runs in Windows. - Development Environment. You will need [Visual Studio 2022](https://visualstudio.microsoft.com/downloads/) to compile the code. - Version control. [Git](https://git-scm.com/downloads) is necessary for cloning the compute.rhino3d repository and managing branches according to your Rhino version. - Rhino. Visit our downloads page to get the latest [Rhino 8](https://www.rhino3d.com/download/rhino-for-windows/8/latest) or [Rhino 7](https://www.rhino3d.com/download/rhino-for-windows/7/latest) builds. After downloading and installing it, please start Rhino and follow the instructions at startup to validate your license on your machine or through the Cloud Zoo. You will need to run the specific version of Rhino.Compute according to the Rhino version you installed. In the next section, you will clone the source code from the compute.rhino3d repository. If you have installed Rhino 8 on your machine, you will need to make sure you clone the 8.x branch. Alternatively, if you installed Rhino 7, make sure to clone the 7.x branch. ## Clone the repository To get the latest version of Rhino.Compute you will need to clone its [repository](https://github.com/mcneel/compute.rhino3d) from our official account in Github. 1. Go to [https://github.com/mcneel/compute.rhino3d](https://github.com/mcneel/compute.rhino3d) 1. From the branch drop-down menu, make sure you select either the 8.x or 7.x branch to clone. 1. Once there, click on the green button "<> Code" and copy the URL for the repository. ![compute_geometry_clone](https://developer.rhino3d.com/images/compute_geometry_clone.png) 1. On your local computer, create a folder where you want to clone the repository into. 1. Navigate to that directory through your preferred command prompt and type `git clone` and paste the URL you copied earlier. ```python git clone https://github.com/mcneel/compute.rhino3d.git ``` 1. After the cloning operation is complete, you should see a number of files and directories which have be downloaded from the compute.rhino3d repository into the folder you created. ## Anatomy of the solution The Rhino.Compute repository consists of two main projects, each serving distinct purposes within the framework. Understanding these can help clarify how Rhino.Compute operates, especially if you're looking to work with or contribute to its development. Here's a breakdown of the two projects: - **compute.geometry**. This project primarily focuses on the geometric calculation aspects. Esentially, compute.geometry provides a REST API that exposes Rhino's geometry engine to be used over the web. This means that geometric operations can be performed on a server, and results can be retrieved remotely, making it highly useful for web-based applications or serices that require complex geometric processing. - **rhino.compute**. This project can thought of as a higher-level service layer that manages and orchestrates the calls to the compute.geometry API - handling tasks like authentication, spawning child processes, and setting up time limits for the shutting down process. ## Compile the solution Now that you have the code, it's time to open the project in Visual Studio 2022 and prepare it for debugging. 1. Navigate to the directory where you cloned the repository and find the **compute.sln** file. Double-click it to open the project in Visual Studio 2022. 1. Set the solution configuration to **Debug** mode. This allows you to run the code with full debugging features, making it easier to trace through the code and spot errors. ![compute_geometry_vs_debug](https://developer.rhino3d.com/images/compute_geometry_vs_debug.png) 1. In the File menu, click on Build -> Build Solution (Ctrl + Shift + B). This will compile all of the project files. ![compute_geometry_vs_build](https://developer.rhino3d.com/images/compute_geometry_vs_build.png) 1. In the Solution Explorer panel, right-click on the **rhino.compute** project and select **Set as Startup Project**. This ensures that when you run the debugger, Visual Studio will start this particular project. ![compute_geometry_vs_startup](https://developer.rhino3d.com/images/compute_geometry_vs_startup.png) 1. Click on **rhino.compute** to start the application in the debugger. A console application should show up in your task bar showing the status of the Rhino.Compute loading process. ![compute_geometry_vs_run](https://developer.rhino3d.com/images/compute_geometry_vs_run.png) 1. Now all you need to do is wait for a few seconds for Rhino.Compute to load. Please take into account that the logged information depends on the Rhino version you had chosen previously. ![compute.geometry.exe](https://developer.rhino3d.com/images/compute_geometry_screenshot.png) ## Test an endpoint With the Rhino.Compute console application running, browse to any of these endpoints to check that everything is working! - [http://localhost:6500/version](http://localhost:6500/version) - [http://localhost:6500/healthcheck](http://localhost:6500/healthcheck) - [http://localhost:6500/activechildren](http://localhost:6500/activechildren) - [http://localhost:6500/sdk](http://localhost:6500/sdk) -------------------------------------------------------------------------------- # Scripting Methods for RDK (Windows) Source: https://developer.rhino3d.com/en/guides/rhinoscript/scripting-methods-for-rdk/ This guide enumerates the RhinoScript methods for accessing the RDK. ## Overview To get a scripting object for the Rhino RDK, use the following code: ```vbnet Dim rdk Set rdk = Rhino.GetPlugInObject("Renderer Development Kit") ``` Then use the RDK object to access the functions below... ## Functions `FactoryList();` Return a list of RDK's content factory collection. **Returns:** Array of strings identifying the factory. `ContentList(strContentType)` Returns a list of contents for a certain type: material, texture, environment. **Parameters:** strContentType = Required. String. Type of content: material, texture or environment. **Returns:** Array of strings identifying the content. NULL in error conditions. `DeleteFactory(strFactoryId)` Deletes a content factory by its identifier. **Parameters:** strFactoryId = Required. String. The identifier of the factory to delete. **Returns:** Boolean True or false indicating success or failure. NULL in error conditions. `FactoryKind(strFactoryId)` Returns the kind of the content. **Parameters:** strFactoryId = Required. String. The identifier of the factory. **Returns:** String of the kind of the factory. NULL in error conditions. `FactoryNewContent(strFactoryId, strParentId)` Create a new content of the specified type. **Parameters:** strFactoryId = Required. String. The identifier of the factory. strParentId = Optional. String. The identifier of the parent content. **Returns:** String which identifies new content. NULL in error conditions. `FactoryContentTypeId(strFactoryId)` Returns the identifier of the content type. **Parameters:** strFactoryId = Required. String. The identifier of the factory. **Returns:** String which identifies the factory. NULL in error conditions. `FactoryContentInternalName(strFactoryId)` Returns the internal name of the content created by specified factory. **Parameters:** strFactoryId = Required. String. The identifier of the factory. **Returns:** String which is the internal name of the factory. NULL in error conditions. `FactoryRenderEngineId(strFactoryId)` Returns the render engine id of the content that this factory produces. **Parameters:** strFactoryId = Required. String. The identifier of the factory. **Returns:** String which is the identifier of the render engine. NULL in error conditions. `FactoryPlugInId(strFactoryId)` Returns the plugin id of the plugin that created this factory. **Parameters:** strFactoryId = Required. String. The identifier of the factory. **Returns:** String which is the identifier of the plugin. NULL in error conditions. `FactoryRebuildCache(strFactoryId)` Rebuild the factory cache. This forces a refresh of cached data such as the factory name. **Parameters:** strFactoryId = Required. String. The identifier of the factory. **Returns:** Boolean True or false indicating success or failure. NULL in error conditions. `ContentRenderEngineId(strContentInstanceID)` Return Render engine identifier. **Parameters:** strContentInstanceID = Required. String. The identifier of the content. **Returns:** String which is the identifier of the render engine. NULL in error conditions. `ContentPlugInId(strContentInstanceID)` Return The plugin id of the plugin that defines this content. **Parameters:** strContentInstanceID = Required. String. The identifier of the content. **Returns:** String which is the identifier of the plugin. NULL in error conditions. `ContentTypeId(strContentInstanceID)` Return The unique id of the content type. **Parameters:** strContentInstanceID = Required. String. The identifier of the content. **Returns:** String which is the identifier of the content type. NULL in error conditions. `ContentInternalName(strContentInstanceID)` Return the internal name of the content type. **Parameters:** strContentInstanceID = Required. String. The identifier of the content. **Returns:** String which is the internal name of the content. NULL in error conditions. `ContentTypeName(strContentInstanceID)` Return the name of the content type. **Parameters:** strContentInstanceID = Required. String. The identifier of the content. **Returns:** String which is the name of the content type. NULL in error conditions. `ContentTypeDescription(strContentInstanceID)` Return the description of the content type. **Parameters:** strContentInstanceID = Required. String. The identifier of the content. **Returns:** String which is the description of the content type. NULL in error conditions. `ContentCategory(strContentInstanceID)` Return the category of the content. **Parameters:** strContentInstanceID = Required. String. The identifier of the content. **Returns:** String which is the category of the content. NULL in error conditions. `ContentKind(strContentInstanceID)` Return return a string uniquely identifying the kind of the content. **Parameters:** strContentInstanceID = Required. String. The identifier of the content. **Returns:** String which is the uniquely identifying the kind of the content. NULL in error conditions. `ContentLibraryFileExtension(strContentInstanceID)` Return the library file extension of the content. **Parameters:** strContentInstanceID = Required. String. The identifier of the content. **Returns:** String which is the library file extension of the content. NULL in error conditions. `CurrentContent(strContentInstanceID)` Returns if content is currently selected in thumbnail display. **Parameters:** strContentInstanceID = Required. String. The identifier of the content. **Returns:** Boolean True or false indicating if content is selected. NULL in error conditions. `ContentInstanceName(strContentInstanceID, strName)` Return content's name. **Parameters:** strContentInstanceID = Required. String. The identifier of the content. strName = Optional. String. Use to set content name. **Returns:** String which is the current name of the content. NULL in error conditions. `ContentNotes(strContentInstanceID, strNotes, strSendEvent)` Return the content's notes. **Parameters:** strContentInstanceID = Required. String. The identifier of the content. strNotes = Optional. Use to set notes. bSendEvent = Optional. Use to update UI. **Returns:** String which are the current notes. NULL in error conditions. `ContentOpenInThumbnailEditor(strContentInstanceID)` Call this method to open the content in the relevant thumbnail editor and select it. **Parameters:** strContentInstanceID = Required. String. The identifier of the content. **Returns:** Boolean True or false indicating success or failure. NULL in error conditions. `ContentParameter(strContentInstanceID, strName, varValue)` Returns or modifies a content parameter. **Parameters:** strContentInstanceID = Required. String. The identifier of the content. strName = Optional. String. Name of the parameter. varValue = Optional. New value of the parameter. **Returns:** Array of strings which are the available paramter names when strName not specified. Variant with current value of parameter if strName is specified. NULL in error conditions. `UngroupContent(strContentInstanceID)` Remove this content tree from any instance group it may be a member of. **Parameters:** strContentInstanceID = Required. String. The identifier of the content. **Returns:** Boolean True or false indicating success or failure. NULL in error conditions. `ContentGroupId(strContentInstanceID)` Returns the group id which this content is a member of. **Parameters:** strContentInstanceID = Required. String. The identifier of the content. **Returns:** String which is the identifier of the group. NULL in error conditions. `DeleteContent(strContentInstanceID)` Delete the content. **Parameters:** strContentInstanceID = Required. String. The identifier of the content. **Returns:** Boolean True or false indicating success or failure. NULL in error conditions. `DuplicateContent(strContentInstanceID)` Duplicate the content. **Parameters:** strContentInstanceID = Required. String. The identifier of the content. **Returns:** String which is the identifier of the new content. NULL in error conditions. `ContentParent(strContentInstanceID)` Return parent content or empty string if this is the top level object. **Parameters:** strContentInstanceID = Required. String. The identifier of the content. **Returns:** String which is the identifier of parent or empty string if this is the top level object. NULL in error conditions. `ContentFirstChild(strContentInstanceID)` Return first child of this content or NULL if none. **Parameters:** strContentInstanceID = Required. String. The identifier of the content. **Returns:** String which is the identifier of the child. NULL in error conditions or if no child. `ContentNextSibling(strContentInstanceID)` Return first sibling of this content or NULL if none. **Parameters:** strContentInstanceID = Required. String. The identifier of the content. **Returns:** String which is the identifier of the first sibling. NULL in error conditions or if no sibling. `ContentTopLevelParent(strContentInstanceID)` Return identifier of top level parent of this content. **Parameters:** strContentInstanceID = Required. String. The identifier of the content. **Returns:** String which is the identifier of the top level parent. NULL in error conditions. `ContentReplaceChild(VARIANT vaContentInstance, strOldChild, strNewChild)` Replace a child content. **Parameters:** strContentInstanceID = Required. String. The identifier of the content. strOldChild = Required. The identifier of the old child. strNewChild = Required. The identifier of the new child. **Returns:** Boolean True or false indicating success or failure. NULL in error conditions. `ContentAddChild(strContentInstanceID, strContent)` Adds another content as a child of this content. **Parameters:** strContentInstanceID = Required. String. The identifier of the content. strContent = Required. String. The identifier of the child content. **Returns:** Boolean True or false indicating success or failure. NULL in error conditions. `ContentChildSlotName(strContentInstanceID, strChildSlotName)` Return the child-slot-name of this content. **Parameters:** strContentInstanceID = Required. String. The identifier of the content. varChildSlotName = Optional. String. The new child slot name **Returns:** String which is current child slot name. NULL in error conditions. `ContentChildSlotNameFromParamName(strContentInstanceID, strParamName)` Return the child-slot-name corresponding to a parameter name. **Parameters:** strContentInstanceID = Required. String. The identifier of the content. strParamName = Required. String. The parameter name of the slot name. **Returns:** String which is the current child slot name. NULL in error conditions. `ContentParamNameFromChildSlotName(strContentInstanceID, strChildSlotName)` Return the parameter name corresponding to a child-slot-name. **Parameters:** strContentInstanceID = Required. String. The identifier of the content. strChildSlotName = Required. String. The child slot name. **Returns:** String which is the current parameter name of the child slot. NULL in error conditions. `ContentFindChild(strContentInstanceID, strChildSlotName)` Return the immediate child that has the specified child-slot-name or NULL if none. **Parameters:** strContentInstanceID = Required. String. The identifier of the content. strChildSlotName = required. String. The child slot name. **Returns:** String which is the identifier of child content. NULL in error conditions. `IsContentTypeAcceptableAsChild(strContentInstanceID, strType, strChildSlotName)` Return true only if content type can be accepted as a child in the specified child slot. **Parameters:** strContentInstanceID = Required. String. The identifier of the content. strType = Required. String. The identifier of a content type. strChildSlotName = Required. String. Name of the child slot. **Returns:** Boolean True or false indicating success or failure. NULL in error conditions. `IsTypeAcceptableAsChild(strContentInstanceID, strFactory, strChildSlotName)` Return true only if content produced by pFactory can be accepted as a child in the specified child slot. **Parameters:** strContentInstanceID = Required. String. The identifier of the content. strFactory = Required. The identifier of the factory. strChildSlotName = Required. The name of the child slot. **Returns:** Boolean True or false indicating success or failure. NULL in error conditions. `ContentGetChildren(strContentInstanceID)` Return all the children of this content. **Parameters:** strContentInstanceID = Required. String. The identifier of the content. **Returns:** Array of strings which are the identifier of the child content. NULL in error conditions. `ContentGetChildSlots(strContentInstanceID)` Return all child slots. **Parameters:** strContentInstanceID = Required. String. The identifier of the content. **Returns:** Array of strings of child slot names. NULL in error conditions. `ContentFactory(strContentInstanceID)` Return the factory that creates this type of content. **Parameters:** strContentInstanceID = Required. String. The identifier of the content. **Returns:** String which is the identifier of the factory. NULL in error conditions. `FindContentInstance(strContentInstanceID, strInstance)` Searches for the content with the specified instance id. **Parameters:** strContentInstanceID = Required. String. The identifier of the content. strInstance = Required. String. The identifier of the instance to search for. **Returns:** Boolean True or false indicating success or failure. NULL in error conditions. `ContentDestroyChildContent(strContentInstanceID, strPlugIn)` Unlink and destroy all child contents belonging to the specified plugin. **Parameters:** strContentInstanceID = Required. String. The identifier of the content. strPlugIn = Required. String. The identifier of the plug-In. **Returns:** Boolean True or false indicating success or failure. NULL in error conditions. `IsContentCompatible(strContentInstanceID, strRenderEngine)` A content is compatible with a render engine when its RenderEngineId() matches the id of the render engine. **Parameters:** strContentInstanceID = Required. String. The identifier of the content. strRenderEngine = Required. String. The identifier of the render engine. **Returns:** Boolean True or false indicating success or failure. NULL in error conditions. `IsContentUniversal(strContentInstanceID)` A content is universal if it is meant to be used with any render engine. **Parameters:** strContentInstanceID = Required. String. The identifier of the content. **Returns:** Boolean True or false indicating success or failure. NULL in error conditions. `IsContentPrivate(strContentInstanceID)` A content is private if it is not intended to show in any editor. **Parameters:** strContentInstanceID = Required. String. The identifier of the content. **Returns:** Boolean True or false indicating success or failure. NULL in error conditions. `ContentSaveToFile(strContentInstanceID, strFullPath)` Save the content to a library file. **Parameters:** strContentInstanceID = Required. String. The identifier of the content. strFullPath = Required. String. The full path to the library file to be created. **Returns:** Boolean True or false indicating success or failure. NULL in error conditions. `ContentLoadFromFile(VARIANT vaFullPath)` Load content from a library file. **Parameters:** strFullPath = Required. String. The full path to the library file to be created. **Returns:** String which is the identifier of the content. NULL in error conditions. `IsContentInDocument(strContentInstanceID)` Query whether or not the content is in the document. **Parameters:** strContentInstanceID = Required. String. The identifier of the content. **Returns:** Boolean True or false indicating success or failure. NULL in error conditions. `ContentEmbeddedFiles(strContentInstanceID)` Return a semicolon-delimited string of full paths to files used by the content. **Parameters:** strContentInstanceID = Required. String. The identifier of the content. **Returns:** String which is a semicolon-delimited string of full paths. NULL in error conditions. `ContentSupportsCommand(strContentInstanceID, strCommand)` Indicates whether or not the content supports a particular command. **Parameters:** strContentInstanceID = Required. String. The identifier of the content. strCommand = Required. String. Command identifier. **Returns:** Boolean True or false indicating success or failure. NULL in error conditions. `ContentExecuteCommand(strContentInstanceID, strCommand)` Executes a command. **Parameters:** strContentInstanceID = Required. String. The identifier of the content. strCommand = Required. String. Command identifier. **Returns:** Boolean True or false indicating success or failure. NULL in error conditions. `SunEnableAllowed([optional] VARIANT vaAllowed)` Returns or modifies the sun enable allowed value. **Parameters:** bAllowed = Optional. Boolean. Use to enable/disable this option. **Returns:** Boolean which is the current value. NULL in error conditions. `SunEnableOn(bOn)` Returns or modifies the sun ON value. **Parameters:** bOn = Optional. Boolean. Use to enable/disable this option. **Returns:** Boolean which is the current value. NULL in error conditions. `SunManualControlAllowed(bAllowed)` Returns or modifies the ManualControlAllowed value. **Parameters:** bAllowed = Optional. Boolean. Use to enable/disable this option. **Returns:** Boolean which is the current value. NULL in error conditions. `SunManualControlOn(bManual)` Returns or modifies the ManualControlOn value. **Parameters:** bManual = Optional. Boolean. Use to enable/disable this option. **Returns:** Boolean which is the current value. NULL in error conditions. `SunNorth(dblNorth)` Returns or modifies the azimuth corresponding to North. **Parameters:** dblNorth = Optional. Use to set/get north. **Returns:** Array which is the current value. NULL in error conditions. `SunVector(vVector)` Return the sun's direction vector in world space. **Parameters:** vVector = Optional. Array. 3D-Vector **Returns:** Array which is the current value. NULL in error conditions. `SunAzimuth(dblAzimuth)` Return the azimuth of the sun in degrees. **Parameters:** dblAzimuth = Optional. Number. **Returns:** Number which is the current value. NULL in error conditions. `SunAltitude(dblAltitude)` Return the altitude of the sun in degrees. **Parameters:** dblAltitude = Optional. Number. **Returns:** Number which is the current value. NULL in error conditions. `SunLatitude(dblLatitude)` Return the latitude of the observer. **Parameters:** dblLatitude = Optional. Number. **Returns:** Number which is the current value. NULL in error conditions. `SunLongitude(dblLongitude)` Return the longitude of the observer. **Parameters:** dblLongitude = Optional. Number. **Returns:** Number which is the current value. NULL in error conditions. `SunTimeZone(dblHours)` Return the time zone of the observer in hours. **Parameters:** dblHours = Optional. Number. **Returns:** Number which is the current value. NULL in error conditions. `SunDaylightSavingOn(bOn)` Return true if daylight saving is on. **Parameters:** bOn = Optional. Boolean. **Returns:** Boolean which is the current value. NULL in error conditions. `SunDaylightSavingMinutes(intMinutes)` Return the daylight saving of the observer in minutes. **Parameters:** intMinutes = Optional. Number. **Returns:** Number which is the current value. NULL in error conditions. `SunLocalDate(intDate)` Retrieves the local date of the observer. **Parameters:** intDate = Optional. Number. **Returns:** Number which is the current value. NULL in error conditions. `SunLocalTime(dblHours)` Retrieves the local time of the observer. **Parameters:** dblHours = Optional. Number. **Returns:** Number which is the current value. NULL in error conditions. `SunUTCDate(intDate)` Retrieves the UTC date of the observer. **Parameters:** intDate = Optional. Number. **Returns:** Number which is the current value. NULL in error conditions. `SunUTCTime(dblHours)` Retrieves the UTC time of the observer. **Parameters:** dblHours = Optional. Number. **Returns:** Number which is the current value. NULL in error conditions. `SunLight()` Get an ON_Light which represents the sun. **Returns:** String which is the identifier of the light. NULL in error conditions. `MaterialInstanceId(intMaterialTableIndex)` Returns the content instance id for a given index into the Rhino material table **Parameters:** intMaterialTableIndex = Required. Number. Index in material table. **Returns:** String which is the identifier of the content. NULL in error conditions. `SetMaterialInstanceId(strInstanceId, intMaterialTableIndex)` Assigns a certain content to a material table index. **Parameters:** strInstanceId = Required. String. Identifier of content. intMaterialTableIndex = Required. Number. Index in material table. **Returns:** Boolean True or false indicating success or failure. NULL in error conditions. `HSB2RGB(arrColor)` Converts color **Parameters:** arrColor = Required. Array. 3D Array of doubles. **Returns:** Array of Numbers. NULL in error conditions. `RGB2HSB(arrColor)` Converts color. **Parameters:** arrColor = Required. Array. 3D Array of doubles. **Returns:** Array of Numbers. NULL in error conditions. `ShowContentChooser(strDefaultType, strDefaultInstance, strAllowedKinds)` Shows the content chooser to allow the user to select a new or existing content. **Parameters:** strDefaultType = Required. String. Is the content type that will be initially selected in the 'New' tab. strDefaultInstance = Required. String. Is the content instance that will be initially selected in the 'Existing' tab. strAllowedKinds = Required. Array. List of strings of content kinds that will be displayed. **Returns:** Boolean True or false indicating success or failure. NULL in error conditions. `ContentKindList()` Returns list of content kinds. **Returns:** Array of strings of content kinds. NULL in error conditions. `SelectedContent(strContentType)` Get the selected content of the specified kind. **Parameters:** strContentType = Required. String. Content type to look for selection. **Returns:** String which is the identifier of the selected content. NULL in error conditions. `OpenFloatingContentPreview(strContentInstanceID, arrPos, arrSize)` Open content preview window. **Parameters:** strContentInstanceID = Required. String. Content identifier. arrPos = Required. Array. 2D array of integers x,y. arrSize = Required. Array. 2D array of integers width, height. **Returns:** String which is the identifier of the preview window. NULL in error conditions. `CloseFloatingContentPreview(strWindowId)` Close content preview window. **Parameters:** strWindowId = Required. String. Identifier of preview window. **Returns:** Boolean True or false indicating success or failure. NULL in error conditions. `ContentResetToDefaults(strContentInstanceID)` Reset content to defaults. **Parameters:** strContentInstanceID = Required. String. Content identifier. **Returns:** Boolean True or false indicating success or failure. NULL in error conditions. `ChangeContent(strContentInstanceID, strNewContentId, bHarvest)` Change content. **Parameters:** strContentInstanceID = Required. String. Content identifier. strNewContentID = Required. String. Content identifier to change too. bHarvest = Required. Boolean. Determines whether or not parameter harvesting will be performed. **Returns:** Boolean True or false indicating success or failure. NULL in error conditions. -------------------------------------------------------------------------------- # Simple Mathematics Component Source: https://developer.rhino3d.com/en/guides/grasshopper/simple-mathematics-component/ This guide contains a brief example of a component that deals with some simple mathematics and multiple input and output parameters. ## Overview We'll discuss parameter order, legacy support for changing component layouts and default values for input parameters. For this component we'll bundle the Sine(), Cosine() and Tangent() trigonometry functions while allowing inputs to be specified in either Radians or Degrees. We'll need to define two input parameter (one of which will have a default value) and three output parameters. ## Prerequisites We will not be dealing with any of the basics of component development. Please start with the [Your First Component](/guides/grasshopper/your-first-component-windows) guide and the [Simple Component](/guides/grasshopper/simple-component) guide before starting this one. Before you start, create a new class that derives from `Grasshopper.Kernel.GH_Component`, as outlined in the [Simple Component](/guides/grasshopper/simple-component) guide. ## Input parameters This component will require two input parameters, one of which has a default value. We'll need to register these parameters inside the `RegisterInputParams()` method: ```cs ... protected override void RegisterInputParams(GH_Component.GH_InputParamManager pManager) { pManager.AddNumberParameter("Angle", "A", "The angle to measure", GH_ParamAccess.item); pManager.AddBooleanParameter("Radians", "R", "Work in Radians", GH_ParamAccess.item, true); // The default value is 'true' } ... ``` ```vbnet ... Protected Overrides Sub RegisterInputParams(ByVal pManager As GH_Component.GH_InputParamManager) pManager.AddNumberParameter("Angle", "A", "The angle to measure", GH_ParamAccess.item) pManager.AddBooleanParameter("Radians", "R", "Work in Radians", GH_ParamAccess.item, True) 'The default value is 'True' End Sub ... ``` The first parameter is of type Double meaning it accepts floating point values. It only has a name, abbreviation, description and access level defined. The second parameter is of type `bool` and it will accept `true` and `false` values. The `pManager` object allows us to specify a single default value for many parameter types. The order in which we register the parameters is also the order in which they'll appear on the component. We should not change the order (or the types, or the number) of the parameters once the component has been released to the world. Every Grasshopper file that was saved with one of our components will expect to be deserialized by the exact same component layout. If you add an additional parameter to an component and someone tries to open a file which was saved while an older version of that component, it will fail to deserialize as the data in the file no longer matches the new layout. An exception will be thrown and Grasshopper will short circuit that particular instance. The entire component and all connections to it will be missing when the file is eventually displayed to the user. *If you must* change the parameter layout of a component, you should create a completely new `GH_Component` class with a different `ComponentGuid`, while maintaining the old component type for legacy file purposes. You can hide components from the Grasshopper GUI by overriding the [Exposure](/api/grasshopper/html/P_Grasshopper_Kernel_IGH_DocumentObject_Exposure.htm) property of the `GH_Component` class and changing the return value to `GH_Exposure.hidden`: ```cs public override Grasshopper.Kernel.GH_Exposure Exposure { get { return GH_Exposure.hidden; } } ``` ```vbnet Public Overrides ReadOnly Property Exposure() As Grasshopper.Kernel.GH_Exposure Get Return GH_Exposure.hidden End Get End Property ``` ## Output Parameters Our component will also need three output parameters. Output parameters differ from input parameters in that they have much fewer options. Users cannot add expressions to them, there are no default values, they don't support persistent data. Outputs are always cleared when the component expires, and they are slowly filled out from within the `SolveInstance()` routine. ```cs ... protected override void RegisterOutputParams(GH_Component.GH_OutputParamManager pManager) { pManager.AddNumberParameter("Sin", "sin", "The sine of the Angle.", GH_ParamAccess.item); pManager.AddNumberParameter("Cos", "cos", "The cosine of the Angle.", GH_ParamAccess.item); pManager.AddNumberParameter("Tan", "tan", "The tangent of the Angle.", GH_ParamAccess.item); } ... ``` ```vbnet ... Protected Overrides Sub RegisterOutputParams(ByVal pManager As GH_Component.GH_OutputParamManager) pManager.AddNumberParameter("Sin", "sin", "The sine of the Angle.", GH_ParamAccess.item) pManager.AddNumberParameter("Cos", "cos", "The cosine of the Angle.", GH_ParamAccess.item) pManager.AddNumberParameter("Tan", "tan", "The tangent of the Angle.", GH_ParamAccess.item) End Sub ... ``` ## SolveInstance The `SolveInstance()` implementation for this component is hardly any more complicated than it was for the [Simple Component](/guides/grasshopper/simple-component/#the-solver-routine). The only difference is that we now have more than one parameter on each side. ```cs ... protected override void SolveInstance(IGH_DataAccess DA) { // Declare variables to contain all inputs. // We can assign some initial values that are either sensible or indicative. double angle = double.NaN; bool radians = false; // Use the DA object to retrieve the data inside the input parameters. // If the retrieval fails (for example if there is no data) we need to abort. if (!DA.GetData(0, ref angle)) { return; } if (!DA.GetData(1, ref radians)) { return; } // If the angle value is not a valid number, we should abort. if (!Rhino.RhinoMath.IsValidDouble(angle)) { return; } // If the user wants to work in degrees rather than radians, // we assume that angle is defined in degrees. // We need to convert it into Radians again. if (!radians) { angle = Rhino.RhinoMath.ToRadians(angle); } // Now we are ready to assign the outputs via the DA object. // Since the Sin(), Cos() and Tan() never fail, we might as well // combine them with the assignment. DA.SetData(0, Math.Sin(angle)); DA.SetData(1, Math.Cos(angle)); DA.SetData(2, Math.Tan(angle)); } ... ``` ```vbnet ... Protected Overrides Sub SolveInstance(ByVal DA As Grasshopper.Kernel.IGH_DataAccess) 'Declare variables to contain all inputs. 'We can assign some initial values that are either sensible or indicative. Dim angle As Double = Double.NaN Dim radians As Boolean = False 'Use the DA object to retrieve the data inside the input parameters. 'If the retrieval fails (for example if there is no data) we need to abort. If (Not DA.GetData(0, angle)) Then Return If (Not DA.GetData(1, radians)) Then Return 'If the angle value is not a valid number, we should abort. If (Not Rhino.RhinoMath.IsValidDouble(angle)) Then Return 'If the user wants to work in degrees rather than radians, 'we assume that angle is defined in degrees. 'We need to convert it into Radians again. If (Not radians) Then angle = Rhino.RhinoMath.ToRadians(angle) End If 'Now we are ready to assign the outputs via the DA object. 'Since the Sin(), Cos() and Tan() never fail, we might as well 'combine them with the assignment. DA.SetData(0, Math.Sin(angle)) DA.SetData(1, Math.Cos(angle)) DA.SetData(2, Math.Tan(angle)) End Sub ... ``` **DONE!** We've discussed parameter order, legacy support for changing component layouts, and default values for input parameters. **Now what?** ## Next Steps Next, check out the [Simple Geometry Component](/guides/grasshopper/simple-geometry-component) guide to see how to use some of the simpler geometry types and classes in RhinoCommon and Grasshopper. ## Related Topics - [What is a Grasshopper Component?](/guides/grasshopper/what-is-a-grasshopper-component) - [Installing Tools (Windows)](/guides/grasshopper/installing-tools-windows/) - [Your First Component](/guides/grasshopper/your-first-component-windows) - [Simple Component](/guides/grasshopper/simple-component) - [Simple Geometry Component](/guides/grasshopper/simple-geometry-component) -------------------------------------------------------------------------------- # Task Capable Components Source: https://developer.rhino3d.com/en/guides/grasshopper/programming-task-capable-component/ A guide to programming multi-threaded components in Grasshopper ## Overview Grasshopper for Rhino allows you to develop multi-threaded components by way of the new `IGH_TaskCapableComponent` interface. Benchmarks have shown that Grasshopper can run significantly faster when using multi-threaded components. Results may vary, as not all solutions can be computed in parallel. ## The Interface When a component implements the `IGH_TaskCapableComponent` interface, Grasshopper will notice and potentially call a full enumeration of `SolveInstance` for the component twice in consecutive passes: 1. The first pass is for collecting data and starting tasks to compute results 1. The second pass is for using the results from the tasks to set outputs. ## Example In this guide, we will convert a standard component into a task capable component. In our example, the initial component code looks like this: ```cs public class FibonacciComponent : GH_Component { ... protected override void SolveInstance(IGH_DataAccess data) { const int max_steps = 46; int steps = 0; data.GetData(0, ref steps); if (steps < 0) { AddRuntimeMessage(GH_RuntimeMessageLevel.Error, "Steps must be >= 0."); return; } if (steps > max_steps) // Prevent overflow... { AddRuntimeMessage(GH_RuntimeMessageLevel.Error, $"Steps must be <= {max_steps}."); return; } int result; if (steps == 0) result = 0; else if (steps == 1) result = 1; else { int x = 0, y = 1, rc = 0; for (int i = 2; i <= steps; i++) { rc = x + y; x = y; y = rc; } result = rc; } data.SetData(0, result); } ... } ``` ## Separate Methods Now you need to separate calculations into separate methods. Independent tasks should not be directly accessing `IGH_DataAccess`, as that interface is not thread safe. Thus, the we will want to break the current flow of a component’s `SolveInstance` method into three distinct steps: 1. Collect input data 1. Compute results on given data 1. Set output data To implement *Step 2*, we will break out the computation code into its own method. To start, create a public `SolveResults` class to hold the data for each `SolveInstance` iteration. For this computation, our definition of `SolveInstance` is very simple: ```cs public class SolveResults { public int Value { get; set; } } ``` Create a *Compute* function that takes the input retrieved from `IGH_DataAccess` and returns an instance of `SolveResults`. ```cs private static SolveResults ComputeFibonacci(int n) { SolveResults result = new SolveResults(); if (n == 0) result.Value = 0; else if (n == 1) result.Value = 1; else { int x = 0, y = 1, rc = 0; for (int i = 2; i <= n; i++) { rc = x + y; x = y; y = rc; } result.Value = rc; } return result; } ``` ## Implement the Interface Now, we are ready to launch multiple tasks in the component. Change the component's inheritance from `GH_Component` to `GH_TaskCapableComponent`. In this example, modify the component from this: `public class FibonacciComponent : GH_Component` to this: `public class FibonacciComponent : GH_TaskCapableComponent` Finally, modify `SolveInstance` to use tasks: ```cs protected override void SolveInstance(IGH_DataAccess data) { const int max_steps = 46; if (InPreSolve) { // First pass; collect input data int steps = 0; data.GetData(0, ref steps); if (steps < 0) { AddRuntimeMessage(GH_RuntimeMessageLevel.Error, "Steps must be >= 0."); return; } if (steps > max_steps) // Prevent overflow... { AddRuntimeMessage(GH_RuntimeMessageLevel.Error, $"Steps must be <= {max_steps}."); return; } // Queue up the task Task task = Task.Run(() => ComputeFibonacci(steps), CancelToken); TaskList.Add(task); return; } if (!GetSolveResults(data, out SolveResults result)) { // Compute right here; collect input data int steps = 0; data.GetData(0, ref steps); if (steps < 0) { AddRuntimeMessage(GH_RuntimeMessageLevel.Error, "Steps must be >= 0."); return; } if (steps > max_steps) // Prevent overflow... { AddRuntimeMessage(GH_RuntimeMessageLevel.Error, $"Steps must be <= {max_steps}."); return; } // Compute results on given data result = ComputeFibonacci(steps); } // Set output data if (result != null) { data.SetData(0, result.Value); } } ``` The full source code for this revision [can be seen here](https://github.com/mcneel/rhino-developer-samples/tree/7/grasshopper/cs/SampleGhTaskCapable). -------------------------------------------------------------------------------- # The Why and How of Data Trees Source: https://developer.rhino3d.com/en/guides/grasshopper/the-why-and-how-of-data-trees/ This guide explains why data trees are used in Grasshopper. ## Data storage in general programming One of the key aspects of programming is deciding how and where to store your data. If you're writing textual code using any one of a huge number of programming languages there are a lot of different options, each with its own benefits and drawbacks. Sometimes you just need to store a single data point. At other times you may need a list of exactly one hundred data points. At other times still circumstances may demand a list of a *variable* number of data points. In programming jargon, [lists](https://msdn.microsoft.com/en-us/library/6sh2ey19(v=vs.110).aspx) and [arrays](https://msdn.microsoft.com/en-us/library/system.array(v=vs.110).aspx) are typically used to store an ordered collection of data points, where each item is directly accessible. [Bags](https://msdn.microsoft.com/en-us/library/dd381779(v=vs.110).aspx) and [hash sets](https://msdn.microsoft.com/en-us/library/bb359438(v=vs.110).aspx) are examples of *unordered* data storage. These storage mechanisms do not have a concept of which data comes first and which next, but they are much better at searching the data set for specific values. [Stacks](https://msdn.microsoft.com/en-us/library/3278tedw(v=vs.110).aspx) and [queues](https://msdn.microsoft.com/en-us/library/7977ey2c(v=vs.110).aspx) are ordered data structures where only the youngest or oldest data points are accessible respectively. These are popular structures for code designed to create and execute schedules. [Linked lists](https://msdn.microsoft.com/en-us/library/he2s3bh7(v=vs.110).aspx) are chains of consecutive data points, where each point knows only about its direct neighbours. As a result, it's a lot of work to find the one-millionth point in a linked list, but it's incredibly efficient to insert or remove points from the middle of the chain. [Dictionaries](https://msdn.microsoft.com/en-us/library/xfhwa508(v=vs.110).aspx) store data in the form of key-value pairs, allowing one to index complicated data points using simple lookup codes. The above is a just a small sampling of popular data storage mechanisms, there are many, many others. From [multidimensional arrays](https://msdn.microsoft.com/en-us/library/2yd9wwz4.aspx) to [SQL databases](http://en.wikipedia.org/wiki/SQL). From [readonly collections](https://msdn.microsoft.com/en-us/library/ms132474(v=vs.110).aspx) to concurrent [k-dTrees](http://en.wikipedia.org/wiki/K-d_tree). It takes a fair amount of knowledge and practice to be able to navigate this bewildering sea of options and pick the best suited storage mechanism for any particular problem. We did not wish to confront our users with this plethora of programmatic principles, and instead decided to offer only a single data storage mechanism.[^1] ## Data storage in Grasshopper In order to see what mechanism would be optimal for Grasshopper, it is necessary to first list the different possible ways in which components may wish to access and store data, and also how families of data points flow through a Grasshopper network, often acquiring more complexity over time. - A lot of components operate on individual values and also output individual values as results. This is the simplest category, let's call it `1:1` (pronounced as "one to one", indicating a mapping from single inputs to single outputs). Two examples of `1:1` components are Subtraction and Construct Point. Subtraction takes two arguments on the left (A and B), and outputs the difference (A-B) to the right. Even when the component is called upon to calculate the difference between two collections of 12 million values each, at any one time it only cares about three values; A, B and the difference between the two. Similarly, Construct Point takes three separate numbers as input arguments and combines them to form a single xyz point. - Another common category of components create lists of data from single input values. We'll refer to these components as `1:N`. Range and Divide Curve are oft used examples in this category. Range takes a single numeric domain and a single integer, but it outputs a list of numbers that divide the domain into the specified number of steps. Similarly, Divide Curve requires a single curve and a division count, but it outputs several lists of data, where the length of each list is a function of the division count. - The opposite behaviour also occurs. Common `N:1` components are Polyline and Loft, both of which consume a list of points and curves respectively, yet output only a single curve or surface. - Lastly (in the list category), `N:N` components are also available. A fair number of components operate on lists of data and also output lists of data. Sort and Reverse List are examples of N:N components you will almost certainly encounter when using Grasshopper. It is true that `N:N` components mostly fall into the data management category, in the sense that they are mostly employed to change the way data is stored, rather than to create entirely new data, but they are common and important nonetheless. - A rare few components are even more complex than `1:N`, `N:1`, or `N:N`, in that they are not content to operate on or output single lists of data points. The Divide Surface and Square Grid components want to output not just lists of points, but several lists of points, each of which represents a single row or column in a grid. We can refer to these components as `1:N'` or `N':1` or `N:N'` or ... depending on how the inputs and outputs are defined. The above listing of data mapping categories encapsulate all components that ship with Grasshopper, though they do not necessarily minister to all imaginable mappings. However in the spirit of getting on with the software it was decided that a data structure that could handle individual values, lists of values, and lists of lists of values would solve at least 99% of the then existing problems and was thus considered to be a 'good thing'. ## Data storage as the outcome of a process If the problems of `1:N'` mappings only occurred in those few components to do with grids, it would probably not warrant support for lists-of-lists in the core data structure. However, `1:N'` or `N:N'` mappings can be the result of the concatenation of two or more `1:N` components. Consider the following case: A collection of three polysurfaces (a box, a capped cylinder, and a triangular prism) is imported from Rhino into Grasshopper. The shapes are all exploded into their separate faces, resulting in 6 faces for the box, 3 for the cylinder, and 5 for the prism. Across each face, a collection of isocurves is drawn, resembling a hatching. Ultimately, each isocurve is divided into equally spaced points. ![Data Storage](https://developer.rhino3d.com/images/the-why-and-how-of-data-trees-01.png) ![Data Storage](https://developer.rhino3d.com/images/the-why-and-how-of-data-trees-02.png) ![Data Storage](https://developer.rhino3d.com/images/the-why-and-how-of-data-trees-03.png) ![Data Storage](https://developer.rhino3d.com/images/the-why-and-how-of-data-trees-04.png) This is not an unreasonably elaborate case, but it already shows how shockingly quickly layers of complexity are introduced into the data as it flows from the left to the right side of the network. It's no good ending up with a single huge list containing all the points. The data structure we use must be detailed enough to allow us to select from it any logical subset. This means that the ultimate data structure must contain a record of all the mappings that were applied from start to finish. It must be possible to select all the points that are associated with the second polysurface, but not the first or third. It must also be possible to select all points that are associated with the first face of each polysurface, but not any subsequent faces. Or a selection which includes only the fourth point of each division and no others. ![Data Storage](https://developer.rhino3d.com/images/the-why-and-how-of-data-trees-05.png) ![Data Storage](https://developer.rhino3d.com/images/the-why-and-how-of-data-trees-06.png) ![Data Storage](https://developer.rhino3d.com/images/the-why-and-how-of-data-trees-07.png) The only way such selection sets can be defined, is if the data structure contains a record of the "history" of each data point. I.e. for every point we must be able to figure out which original shape it came from (the cube, the cylinder or the prism), which of the exploded faces it is associated with, which isocurve on that face was involved and the index of the point within the curve division family. ## A flexible mechanism for variable history records The storage constraints mentioned so far (to wit, the requirement of storing individual values, lists of values, and lists of lists of values), combined with the relational constraints (to wit, the ability to measure the relatedness of various lists within the entire collection) lead us to Data Trees. The data structure we chose is certainly not the only imaginable solution to this problem, and due to its terse notation can appear fairly obtuse to the untrained eye. However since data trees only employ non-negative integers to identify both lists and items within lists, the structure is very amenable to simple arithmetic operations, which makes the structure very pliable from an algorithmic point of view. A data tree is an *ordered collection of lists*. Each list is associated with a *path*, which serves as the identifier of that list. This means that two lists in the same tree cannot have the same path. A path is a collection of one or more non-negative integers. Path notation employs curly brackets and semi-colons as separators. The simplest path contains only the number zero and is written as: `{0}`. More complicated paths containing more elements are written as: `{2;4;6}`. Just as a path identifies a list within the tree, an *index* identifies a data point within a list. An index is always a single, non-negative integer. Indices are written inside square brackets and appended to path notation, in order to fully identify a single piece of data within an entire data tree: `{2,4,6}[10]`. Since both path elements and indices are zero-based (we start counting at zero, not one), there is a slight disconnect between the ordinality and the cardinality of numbers within data trees. The *first* element equals index 0, the *second* element can be found at index 1, the *third* element maps to index 2, and so on and so forth. This means that the "Eleventh point of the seventh isocurve of the fifth face of the third polysurface" will be written as `{2;4;6}[10]`. The first path element corresponds with the oldest mapping that occurred within the file, and each subsequent element represents a more recent operation. In this sense the path elements can be likened to taxonomic identifiers. The species `{Animalia;Mammalia;Hominidea;Homo}` and `{Animalia;Mammalia;Hominidea;Pan}` are more closely related to each other than to `{Animalia;Mammalia; Cervidea;Rangifer}`[^2] because they share more codes at the start of their classification. Similarly, the paths `{2;4;4}` and `{2;4;6}` are more closely related to each other than they are to `{2;3;5}`. ## The messy reality Although you may agree with me that in theory the data tree approach is solid, you may still get frustrated at the rate at which data trees grow more complex. Often Grasshopper will choose to add additional elements to the paths in a tree where none in fact is needed, resulting in paths that all share a lot of zeroes in certain places. For example a data tree might contain the paths: ```cs {0;0;0;0;0} {0;0;0;0;1} {0;0;0;0;2} {0;0;0;0;3} {0;0;1;0;0} {0;0;1;0;1} {0;0;1;0;2} {0;0;1;0;3} ``` instead of the far more economical: ```cs {0;0} {0;1} {0;2} {0;3} {1;0} {1;1} {1;2} {1;3} ``` The reason all these zeroes are added is because we value consistency over economics. It doesn't matter whether a component actually outputs more than one list, if the component belongs to the `1:N`, `1:N'`, or `N:N'` groups, it will *always* add an extra integer to all the paths, because some day in the future, when the inputs change, it may need that extra integer to keep its lists untangled. We feel it's bad behaviour for the topology of a data tree to be subject to the topical values in that tree. Any component which relies on a specific topology will no longer work when that topology changes, and that should happen as seldom as possible. ## Conclusion Although data trees can be difficult to work with and probably cause more confusion than any other part of Grasshopper, they seem to work well in the majority of cases and we haven't been able to come up with a better solution. That's not to say we never will, but data trees are here to stay for the foreseeable future. **Footnotes** [^1]: This is not something we hit on immediately. The very first versions of Grasshopper only allowed for the storage of a single data point per parameter, making operations like [Loft] or [Divide Curve] impossible. Later versions allowed for a single list per parameter, which was still insufficient for all but the most simple algorithms. [^2]: I'm skipping a lot of taxonometric classifications here to keep it simple. -------------------------------------------------------------------------------- # VBScript Procedures Source: https://developer.rhino3d.com/en/guides/rhinoscript/vbscript-procedures/ This guide discusses VBScript procedures. ## Overview In VBScript, there are two kinds of procedures; the `Sub` procedure and the `Function` procedure. ## Sub Procedures A `Sub` procedure is a series of VBScript statements (enclosed by `Sub` and `End Sub` statements) that perform actions but don't return a value. A `Sub` procedure can take arguments (constants, variables, or expressions that are passed by a calling procedure). If a `Sub` procedure has no arguments, its `Sub` statement should include an empty set of parentheses `()`. The following `Sub` procedure uses two intrinsic, or built-in, VBScript functions, MsgBox and InputBox, to prompt a user for information. It then displays the results of a calculation based on that information. The calculation is performed in a `Function` procedure created using VBScript. The `Function` procedure is shown after the following discussion. ```vbnet Sub ConvertTemp() temp = InputBox("Please enter the temperature in degrees F.", 1) MsgBox "The temperature is " & Celsius(temp) & " degrees C." End Sub ``` ## Function Procedures A `Function` procedure is a series of VBScript statements enclosed by the `Function` and End `Function` statements. A `Function` procedure is similar to a `Sub` procedure, but can also return a value. A `Function` procedure can take arguments (constants, variables, or expressions that are passed to it by a calling procedure). If a `Function` procedure has no arguments, its `Function` statement should include an empty set of parentheses. A `Function` returns a value by assigning a value to its name in one or more statements of the procedure. The return type of a `Function` is always a Variant. In the following example, the Celsius `Function` calculates degrees Celsius from degrees Fahrenheit. When the `Function` is called from the `ConvertTemp` `Sub` procedure, a variable containing the argument value is passed to the `Function`. The result of the calculation is returned to the calling procedure and displayed in a message box. ```vbnet Sub ConvertTemp() temp = InputBox("Please enter the temperature in degrees F.", 1) MsgBox "The temperature is " & Celsius(temp) & " degrees C." End Sub Function Celsius(fDegrees) Celsius = (fDegrees - 32) * 5 / 9 End Function ``` ## Getting Data In and Out Each piece of data is passed into your procedures using an argument. Arguments serve as placeholders for the data you want to pass into your procedure. You can name your arguments any valid variable name. When you create a procedure using either the `Sub` statement or the `Function` statement, parentheses must be included after the name of the procedure. Any arguments are placed inside these parentheses, separated by commas. For example, in the following example, `fDegrees` is a placeholder for the value being passed into the Celsius function for conversion. ```vbnet Function Celsius(fDegrees) Celsius = (fDegrees - 32) * 5 / 9 End Function ``` To get data out of a procedure, you must use a `Function`. Remember, a `Function` procedure can return a value; a `Sub` procedure cannot. ## Sub and Function A `Function` in your code must always be used on the right side of a variable assignment or in an expression. For example: ```vbnet Temp = Celsius(fDegrees) ``` or ```vbnet MsgBox "The Celsius temperature is " & Celsius(fDegrees) & " degrees." ``` To call a `Sub` procedure from another procedure, type the name of the procedure along with values for any required arguments, each separated by a comma. The `Call` statement is not required, but if you do use it, you must enclose any arguments in parentheses. The following example shows two calls to the `MyProc` procedure. One uses the `Call` statement in the code; the other does not. Both do exactly the same thing. ```vbnet Call MyProc(firstarg, secondarg) MyProc firstarg, secondarg ``` Notice that the parentheses are omitted in the call when the `Call` statement is not used. ## Related Topics - [What are VBScript and RhinoScript?](/guides/rhinoscript/what-are-vbscript-rhinoscript) - [Parentheses](/guides/rhinoscript/parentheses) -------------------------------------------------------------------------------- # What is Rhino3dm? Source: https://developer.rhino3d.com/en/guides/opennurbs/what-is-rhino3dmio/ This guide covers Rhino3dm builds of openNURBS. ## Overview Rhino3dm is a set of libraries based on the [OpenNURBS](/guides/opennurbs/what-is-opennurbs) geometry library. They provide the ability to access and manipulate geometry through .NET, Python or JavaScript applications independent of Rhino. Functionality includes: - Create, interrogate, and store all geometry types supported in Rhino. This includes points, point clouds, NURBS curves and surfaces, polysurfaces (BReps), meshes, annotations, extrusions, and SubDs. - Work with non-geometry classes supported in Rhino like layers, object attributes, transforms and viewports. - Read and write all of the above information to and from the .3dm file format. - Use as a client to make calls into the [Rhino.Compute](/guides/compute/) cloud server for advanced manipulation of geometry objects. - Available on most platforms (Windows, macOS, Linux). Rhino3dm is [open source](https://github.com/mcneel/rhino3dm). Explore all of the rhino3dm samples: [rhino3dm samples](https://github.com/mcneel/rhino-developer-samples/tree/8/rhino3dm)

WARNING

Rhino3dm is NOT meant for any Rhino plug-in development. You should only be using Rhino3dm if you are attempting to read/write 3dm files from an application other than Rhino! Rhino3dm comes in three flavors. ## Rhino3dm.py [Rhino3dm.py](https://pypi.org/project/rhino3dm/) is a Python package that can be used on all current versions of CPython (3.7 - 3.11) and is available on all platforms (Windows, macOS, Linux). Rhino3dm.pys packages are available on the [Python Package Index (PyPI)](https://pypi.org/project/rhino3dm/). See our [Python documentation](https://github.com/mcneel/rhino3dm/blob/main/docs/python/RHINO3DM.PY.md) for details. ## Rhino3dm.js [Rhino3dm.js](https://www.npmjs.com/package/rhino3dm) is a JavaScript library with an associated web assembly (rhino3dm.wasm). Rhino3dm.js should run on all major browsers as well as [node.js](https://nodejs.org). Rhino3dm.js packages are available on [npm](https://www.npmjs.com/package/rhino3dm). See our [JavaScript documentation](https://github.com/mcneel/rhino3dm/blob/main/docs/javascript/RHINO3DM.JS.md) for details. ## Rhino3dm.NET [Rhino3dm.NET](https://www.nuget.org/packages/Rhino3dm/), formerly known as Rhino3dmIO, allows you to write standalone .NET applications. Rhino3dm.NET packages are available on [NuGet](https://www.nuget.org/packages/Rhino3dm/). -------------------------------------------------------------------------------- # What's New and Update Guide Source: https://developer.rhino3d.com/en/guides/opennurbs/migration-guide/ This guide contains information to help you use the current version of openNURBS. ## Overview The openNURBS toolkit reads and writes Rhino 3DM files. To get the current openNURBS toolkit or technical support, visit [openNURBS](https://www.rhino3d.com/opennurbs). ## Updating For most developers, updating from the previous verson of openNURBS will involve recompiling and minor changes. As before, the easiest way to read a 3DM file is to use one of the `ONX_Model::Read()` functions. The easiest way to write a 3DM file is to use one of the `ONX_Model::Write()` functions. ## openNURBS 8 OpenNURBS and Rhino3dm now provide direct access to rendering information in the absence of Rhino. Prior to this, the only way to access this information outside of Rhino was by getting the data as XML and parsing it. The following new objects are provided: | **openNURBS** | **Rhino3dm** | **Description** | | :---------------------- | :-------------------------------------------- | :----------------------------------------------------------- | | `ON_RenderContent` | `File3dmRenderContent` | Provides access to generic render content settings. | | `ON_RenderMaterial` | `File3dmRenderMaterial` | Provides access to settings specific to render materials. | | `ON_RenderEnvironment` | `File3dmRenderEnvironment` | Provides access to settings specific to render environments. | | `ON_RenderTexture` | `File3dmRenderTexture` | Provides access to settings specific to render textures. | | `ON_Decals` | `Render.Decals` | Provides access to a collection of decals stored on object attributes. | | `ON_Decal` | `Render.Decal` | Provides access to settings for an individual decal in the collection. | | `ON_Dithering` | `Render.Dithering` | Provides access to dithering settings. | | `ON_EmbeddedFile` | `File3dmEmbeddedFile` | Provides access to embedded texture files. | | `ON_GroundPlane` | `Render.GroundPlane` | Provides access to ground plane settings. | | `ON_LinearWorkflow` | `Render.LinearWorkflow` | Provides access to gamma and linear workflow settings. | | `ON_RenderChannels` | `Render.RenderChannels` | Provides access to render channels settings. | | `ON_SafeFrame` | `Render.SafeFrame` | Provides access to safe frame settings. | | `ON_Skylight` | `Render.Skylight` | Provides access to skylighting settings. | | `ON_Sun` | `Render.Sun` | Provides access to sun settings and sun position calculations. | | `ON_PostEffects` | `Render.PostEffects.PostEffectCollection` | Provides access to the list of post effects. | | `ON_PostEffect` | `Render.PostEffects.PostEffectData` | Provides access to settings for an individual post effect in the list. | ## openNURBS 7 A new subdivision surface object has been added to Rhino 7. The core geometry component is `ON_SubD` class, which is also part of openNURBS. All subdivision code will be available in the Rhino plug-in SDK. The `ON_SubD` class has full support for Catmull-Clark quad subdivision surfaces and for Loop-Warren triangle subdivision surfaces. The Rhino subdivision surface control polygons have no limits on vertex valences (edge and face counts) or facet edge counts. Rhino subdivision objects are automatically converted to cubic NURBS polysurfaces or meshes when a subdivision object is selected as input to a command that is expecting a polysurface or mesh. This is how Rhino’s lightweight extrusion object behaves. ## openNURBS 6 To build openNURBS 6, use a C++ compiler that supports C++11. Both Microsoft’s Visual Studio 2017 and Apple’s XCode 9 support C++11. Use iterators to go through the contents of an `ONX_Model`. For an example, look at the source code for `ONX_Model::DumpComponentList()` in *opennurbs_extensions.cpp*. `ON_ComponentManifest` is a manifest of every component in a model or 3DM file. It provides simple ways fo find components by id or name. The function `ONX_Model.Manifest()` returns the manifest of the `ONX_Model`. When merging models, `ON_ManifestMap` can be used to efficiently manage name and id collisions. -------------------------------------------------------------------------------- # What's New? Source: https://developer.rhino3d.com/en/guides/cpp/whats-new/ This brief guide outlines the new and changed features in the Rhino C/C++ SDK. ## Overview The Rhino C/C++ SDK is *not* an abstract SDK. That is, the native classes and functions that are made available in the SDK are also used internally by Rhino. Thus, when the signatures of classes or functions change, all developers, both internal and external, are required to modify their source code to accommodate for the change. For this reason, the Rhino C/C++ SDK was *not* broken between Rhino 6, 7 and 8. Thus, plug-ins written for Rhino 7 using the Rhino 7 C/C++ SDK should load and run without modification in Rhino 8. However, we do encourage developers to migrate their plug-in projects to Rhino 8 so they can take advantage to new and enhanced features. ## Rhino 8 ### Visual Studio 2019 (v142) To write C++ plug-ins for Rhino 8 using the Rhino 8 C/C++ SDK, you will need a version of Microsoft Visual Studio that includes the Visual Studio 2019 (v142) platform toolset. Thus, you can use either [Visual Studio 2022](https://visualstudio.microsoft.com/downloads/) or [Visual Studio 2019](https://visualstudio.microsoft.com/vs/older-downloads/). There is a new [Rhino Visual Studio Extension](https://github.com/mcneel/RhinoVisualStudioExtensions/releases), which includes project and command templates, that installs independently of Rhino C/C++ SDK. ## Rhino 7 ### Visual Studio 2019 (v142) To write C++ plug-ins for Rhino 7 using the Rhino 7 C/C++ SDK, you will need a version of Microsoft Visual Studio that includes the Visual Studio 2019 (v142) platform toolset. Thus, you can use either [Visual Studio 2022](https://visualstudio.microsoft.com/downloads/) or [Visual Studio 2019](https://visualstudio.microsoft.com/vs/older-downloads/). Rhino 7 C/C++ SDK includes project and command wizards. Thus, you'll need to have Visual Studio 2019 installed on your system before installing the Rhino 7 C/C++ SDK. ### Subdivision Surfaces A new subdivision surface object has been added to Rhino 7. The core geometry component is ```ON_SubD``` class, which is also part of openNURBS. All subdivision code will be available in the Rhino plug-in SDK. The ```ON_SubD``` class has full support for Catmull-Clark quad subdivision surfaces and for Loop-Warren triangle subdivision surfaces. The Rhino subdivision surface control polygons have no limits on vertex valences (edge and face counts) or facet edge counts. Rhino subdivision objects are automatically converted to cubic NURBS polysurfaces or meshes when a subdivision object is selected as input to a command that is expecting a polysurface or mesh. This is how Rhino's lightweight extrusion object behaves. ## Deprecation Obsolete functions from Rhino are marked as deprecated with a message to help accomplish the same goal through alternate functions in the Rhino C/C++ SDK. These deprecations will generate compiler warnings when plug-in code attempts to call these functions. Functions marked as deprecated may or may not continue to work in future Rhino versions. Thus, you should replace all calls to deprecated functions with calls to their replacements before distributing any plug-in. ## Related Topics - [Installing Tools (Windows)](/guides/cpp/installing-tools-windows) - [C++ SDK samples on GitHub](https://github.com/mcneel/rhino-developer-samples) - [Migrate Rhino 6 plug-in projects to Rhino 7](/guides/cpp/migrate-your-plugin-windows) -------------------------------------------------------------------------------- # What's New? Source: https://developer.rhino3d.com/en/guides/rhinocommon/whats-new/ This brief guide outlines the changes in the RhinoCommon SDK. ## Overview The following document describes what has been added, what has changed, and how to deal with these changes in the RhinoCommon SDK. A lot of effort has been put into keeping RhinoCommon compatible with versions found in earlier Rhino releaes. One goal of this document is to describe these breaking changes and what to do about them. ## Rhino 8 Rhino 8 now uses the open source [.NET Core Runtime](https://github.com/dotnet/runtime) for running .NET code on both Windows and Mac. This brings some performance improvements and aligns the .NET runtimes used across platforms. Previously, Rhino 7 and earlier used the [Mono runtime](https://www.mono-project.com/) on Mac, and .NET Framework exclusively on Windows. On Windows, you can still optionally run using the .NET Framework runtime in the case of compatibility issues or running inside other software that requires it (e.g. Rhino.Inside Revit). Most plugins are already compatible when running in .NET Core without any recompilation, but in the case of any incompatibilities you may need to update your plugin. For more details, see [Moving to .NET Core](/guides/rhinocommon/moving-to-dotnet-core). Also, we've updated the [Rhino Visual Studio Extension](https://github.com/mcneel/RhinoVisualStudioExtensions/releases) for both Windows and Mac. And, there is an all new [RhinoCommon API Reference](https://developer.rhino3d.com/api/rhinocommon/html/R_Project_RhinoCommon.htm) online. Here is what is new in [RhinoCommon 8](https://developer.rhino3d.com/api/rhinocommon/whatsnew/8.0). ## Rhino 7 RhinoCommon plug-ins for Rhino 7 are based on the Microsoft .NET Framework 4.8. To developer plug-is Windows, use either [Visual Studio 2022](https://visualstudio.microsoft.com/downloads/) or [Visual Studio 2019](https://visualstudio.microsoft.com/vs/older-downloads/). To develop plug-in on Mac, use [Visual Studio 2022 for Mac](https://visualstudio.microsoft.com/vs/mac/). Visual Studio 2019 for Mac should work as well. Here is what is new in [RhinoCommon 7](https://developer.rhino3d.com/api/rhinocommon/whatsnew/7.0). -------------------------------------------------------------------------------- # Where to find help... Source: https://developer.rhino3d.com/en/guides/rhinoscript/primer-101/where-to-find-help/ ## Forums The RhinoScript community is very active and offers a wonderful resource for posting questions/answers and finding help on just about anything!: [https://discourse.mcneel.com/c/scripting](https://discourse.mcneel.com/c/scripting) ## Related Topics - [Where to find help - Next Topic >>](/guides/rhinoscript/primer-101/1-whats-it-all-about/) -------------------------------------------------------------------------------- # Your First Component (Windows) Source: https://developer.rhino3d.com/en/guides/grasshopper/your-first-component-windows/ This guide walks you through your first custom Grasshopper component library using Visual Studio. It is presumed you already have the necessary tools installed and are ready to go. If you are not there yet, see [Installing Tools (Windows)](/guides/grasshopper/installing-tools-windows). ## HelloGrasshopper We will use the Grasshopper Assembly templates to create a new, basic, component library called HelloGrasshopper. If you are familiar with Visual Studio, these step-by-step instructions may be overly detailed for you. The executive summary: create a new project using the Grasshopper Assembly template, build and run, and then make a change. We are presuming you have never used Visual Studio before, so we'll go through this one step at a time. ### File New 1. If you have not done so already, **launch Visual Studio** (for the purposes of this guide, we are using Visual Studio Community Edition and C#). 1. Navigate to **File** > **New** > **Project**... ![File New Project](https://developer.rhino3d.com/images/your-first-plugin-windows-01.png) 1. A **Create a new project** wizard should appear. In the **Search for templates** area, search for `Grasshopper` to filter the results. Find and select the **Grasshopper Assembly for Rhino (C\#)** entry and click **Next**. 1. For the purposes of this Guide, we will name our demo plugin _HelloGrasshopper_. In the **Configure your new project** dialog, fill in the **Project name** field. **Browse** and select a location for this project on your disk, then click **Next** 1. The _New Grasshopper Add-On dialog_ appears. Check the _Provide sample code_ checkbox. ![New Grasshopper Assembly](https://developer.rhino3d.com/images/your-first-component-windows-03.png) 1. This is where you fill out information about your first component: 1. Add-on display name: the name of component library itself. 1. Name: the name of the component as displayed in the ribbon bar and search menus. 1. Nickname: the default name of the component when inserted into the canvas. 1. Category: name of tab where component icon will be shown. 1. Subcategory: name of group inside tab where icon will be shown. 1. Description: description shown in tooltip when mouse is over the component icon in the menu. 1. For the purposes of this guide, let's chek the "Provide Sample Code", and then click **Finish**... 1. A **new solution** called **HelloGrasshopper** should open... ![HelloGrasshopper Solution](https://developer.rhino3d.com/images/your-first-component-windows-04.png) ### Boilerplate Build 1. Before we do anything, let's **build** and **run** HelloGrasshopper to make sure everything is working as expected. We'll just build the boilerplate Plugin template. Click **Start** (play) button in toolbar corner of Visual Studio (or press **F5**) to **Start Debugging**... ![Start Button](https://developer.rhino3d.com/images/your-first-compo-windows-06.png) 1. **Rhinoceros** launches and a moment later, so will **Grasshopper**. _The first debug may take a while depending on your settings, as Visual Studio downloads debugging files, this is normal._ 1. Navigate to **Curve** > **Primitive** in the components menus. You should see HelloGrasshopper in the list with a blank icon. Drag this onto the canvas. The component will run and some interesting Geometry will appear in the Rhino Viewport. 1. **Exit** Rhinoceros. This stops the session. Go back to **Visual Studio**. Let's take a look at the... ### Component Anatomy 1. Use the **Solution Explorer** to expand the **Solution** (_.sln_) so that it looks like this... ![Grasshopper Component Anatomy](https://developer.rhino3d.com/images/your-first-component-windows-06.png) _NOTE_: Depending on your edition of Visual Studio, it may look slightly different. 1. The **HelloGrasshopper** project (_.csproj_) has the same name as its parent solution...this is the project that was created for us by the **Grasshopper Assembly** template wizard earlier. 1. **Dependencies**: Just as with most projects, you will be referencing other libraries. The **Grasshopper Assembly** template added the necessary dependencies to create a custom Grasshopper component. 1. **Framework Targets** - The **Grasshopper Assembly** template is multi-targeted so that the correct assemblies are loaded for the correct platforms. 1. **Grasshopper** - The referenced Grasshopper Nuget. 1. **Properties** contains the **launchSettings.json** file. This file contains all of the debug. 1. **HelloGrasshopperInfo.cs** contains the component library information, such as the name, icon, etc. 1. **HelloGrasshopperComponent.cs** is where the action is. Let's take a look at this file... ### Make Changes 1. Open **HelloGrasshopperComponent.cs** in Visual Studio's Source Editor (if it isn't already). 1. Notice that `HelloGrasshopperComponent` inherits from `GH_Component` ... public class HelloGrasshopperComponent : GH_Component 1. If you hover over `GH_Component` you will notice this is actually `Grasshopper.Kernel.GH_Component`. 1. `HelloGrasshopperComponent` also overrides two methods for determining the input and output parameters ... ```cs protected override void RegisterInputParams(GH_Component.GH_InputParamManager pManager) ... protected override void RegisterOutputParams(GH_Component.GH_OutputParamManager pManager) ``` 5. The actual work done by the component is to be found in the `SolveInstance` method... ```cs protected override void SolveInstance(IGH_DataAccess DA) ``` 6. As you can see, this is where the action happens. This boilerplate component creates a spiral on a plane. Just to make sure everything is working, let's change the default plane on which the spiral is constructed. On line[^1] 67, in `SolveInstance`, notice that an XY plane is constructed... ```cs Plane plane = Plane.WorldXY; ``` 7. Further down in the `SolveInstance` method, you will notice that the input data is being fed into this plane... ```cs if (!DA.GetData(0, ref plane)) return; ``` 8. Go back to the `RegisterInputParams`, and find the line where the _Plane_ input is registered. The last argument being fed to the method - `Plane.WorldXY` - is the default value of the input... ```cs pManager.AddPlaneParameter("Plane", "P", "Base plane for spiral", GH_ParamAccess.item, Plane.WorldXY); ``` 9. Change the default value of the _Plane_ input to be `Plane.WorldYZ` ... ```cs pManager.AddPlaneParameter("Plane", "P", "Base plane for spiral", GH_ParamAccess.item, Plane.WorldYZ); ``` 10. Now let's examine what happens when inputs are given to this component... ### Debugging 1. Set a breakpoint on line[^1] 99 of _HelloGrasshopperComponent.cs_. You set breakpoints in Visual Studio by clicking in the gutter... ![Set a breakpoint](https://developer.rhino3d.com/images/your-first-component-windows-07.png) 1. **Build** and **Run**. 1. In Grasshopper, place a _HelloGrasshopper_ component on the canvas...as soon as you do, you should hit your breakpoint and pause... ![Hit a breakpoint](https://developer.rhino3d.com/images/your-first-component-windows-08.png) 1. The reason you hit the breakpoint is because the `SolveInstance` method was called once initially when the component was placed on the canvas. With Rhino and Grasshopper paused, in **Visual Studio** switch to the **Autos** tab (if it not already there). In the list, find the `plane` object. Our `plane` is a `Rhino.Geometry.Plane` with a value of `{Origin=0,0,0 XAxis=0,1,0, YAxis=0,0,1, ZAxis=1,0,0}` ...an YZ plane, the default, as expected. 1. **Continue** in Grasshopper by pressing the **Continue** button in the upper menu of **Visual Studio** (or press **F5**)... ![Continue Executing](https://developer.rhino3d.com/images/your-first-plugin-windows-11.png) 1. Control is passed back to **Grasshopper** and the spiral draws in the Rhino viewport. Now, place an _XY Plane_ component on the canvas and feed it as an input into _HelloGrasshopper_'s _Plane_ input. Notice you hit your breakpoint again, because the `SolveInstance` is being called now that the input values have changed. 1. **Exit** Grasshopper and Rhino or **Stop** the debugging session. 1. **Remove** the breakpoint you created above by clicking on it in the gutter. **DONE!** **Congratulations!** You have just built your first Grasshopper component for Rhino for Windows. **Now what?** ## Next Steps You've built a component library from boilerplate code, but what about putting together a new simple component "from scratch" and adding it to your project? (Component libraries are made up of multiple components after all). Next, check out the [Simple Component](/guides/grasshopper/simple-component) guide. Try debugging your new grasshopper plugin on [Mac](/guides/grasshopper/your-first-component-mac/), all plugins using the new templates are now cross-platform by default. ## Related topics - [Installing Tools (Windows)](/guides/grasshopper/installing-tools-windows) - [Simple Component](/guides/grasshopper/simple-component) **Footnotes** [^1]: **Line numbers** in Visual Studio can be enabled and disabled in **Tools** > **Options...** > **Text Editor** section > **All Languages** entry > **General** sub-entry > **Settings** subsection > check **Line numbers**. Click **OK** to close the **Options** dialog. -------------------------------------------------------------------------------- # Your First Renderer Plugin (Windows) Source: https://developer.rhino3d.com/en/guides/cpp/your-first-renderer-plugin-windows/ This guide has yet to be authored or ported. This guide has yet to be ported to this site. Please check back soon for updates. -------------------------------------------------------------------------------- # Add Products to Cloud Zoo Source: https://developer.rhino3d.com/en/guides/rhinocommon/cloudzoo/cloudzoo-add-products/ Once you are registered as an issuer, you can add, view, and modify products in Cloud Zoo using the endpoints described below. ## Endpoint Conventions Unless noted, the following conventions apply to *all* product endpoints available to registered issuers in Cloud Zoo. ### Endpoint Location All requests should be made to the following location: `https://cloudzoo.rhino3d.com/v1`. ### JSON All payload to and from endpoints happens in [JSON format](https://www.json.org). To make this explicit, every response to an endpoint will have the header `Content-Type: application/json` present in the HTTPS response. ### Authentication All endpoints in Cloud Zoo or on the issuer use [Basic Authentication](https://en.wikipedia.org/wiki/Basic_access_authentication). To receive a successful response from an endpoint, you must include an `Authorization` header like so: ``` Authorization: Basic BASE64ENCODEDSTRING ``` where `BASE64ENCODEDSTRING` is a [base64](https://en.wikipedia.org/wiki/Base64) encoded string containing your issuer id and your issuer secret: ```python BASE64ENCODEDSTRING = b64.encode(issuer_id + ":" + issuer_secret) ``` ### Non-successful responses All unsuccessful responses from endpoints will have an HTTP status code greater or equal to `400`. If the status code is also less than `500`, the payload will include the following JSON: { "Error": "SomeErrorCode" "Description": "A description about the error message", "Details": "More details about the error" } - The `Error` field contains a specific error code that can be used by the issuer to recognize a specific error, such as incorrect credentials. - The `Description` field contains a description of the error. - The `Details` field contains details of the error, possibly suggesting how to fix it. If the status code is greater or equal to `500`, the response may not be JSON and may be empty. ## Endpoints ### POST /product Adds a product to Cloud Zoo registered under the authenticating issuer. #### Example Request POST /product { "id": "06bb1e79-5456-47a1-ad6d-104518cd894b", "version": "12", "platforms": [ "Windows" ], "picture": "https://elisapi.mcneel.com/media/2", "downloadUrl": "https://www.rhino3d.com/download/new_product", "titles": { "en": "New Product Name", "es": "Nuevo Producto", "ja": "新製品" }, "format": { "example": "MA7B-XXXX-XXXX-XXXX-XXXX-XXXX", "prefix": "MA7B", "length": {"min": 24, "max": 24} } } The payload should be a [Product object](/guides/rhinocommon/cloudzoo/cloudzoo-product). The product ID must be lowercase! Products are heavily cached in Cloud Zoo. In some cases, it might take a couple of minutes for changes to propagate. #### Response A successful response (The product was created): - HTTP Status Code: `200 (OK)` - Response Payload: The newly created [Product object](/guides/rhinocommon/cloudzoo/cloudzoo-product). A non-successful (error) response (The product could not be created): - HTTP Status Code: A code greater or equal to `400 (Bad Request)` - Response Payload: [A non-successful response](#non-successful-responses) ### PUT /product/{product_id} Modifies an existing product with the given `product_id`. Note that some properties of a product cannot be modified after creation, including but not limited to its unique id. #### Example Request PUT /product/06bb1e79-5456-47a1-ad6d-104518cd894b { "platforms": [ "Windows", "Mac" ], "picture": "https://elisapi.mcneel.com/media/new_icon_url" } Products are heavily cached in Cloud Zoo. In some cases, it might take a couple of minutes for changes to propagate. #### Response A successful response (The product was modified): - HTTP Status Code: `200 (OK)` - Response Payload: The newly updated [Product object](/guides/rhinocommon/cloudzoo/cloudzoo-product). A non-successful (error) response (The product could not be modified): - HTTP Status Code: A code greater or equal to `400 (Bad Request)` - Response Payload: [A non-successful response](#non-successful-responses) ### GET /product/{product_id} Gets an existing product with the given `product_id`. #### Example Request GET /product/06bb1e79-5456-47a1-ad6d-104518cd894b #### Response A successful response: - HTTP Status Code: `200 (OK)` - Response Payload: A [Product object](/guides/rhinocommon/cloudzoo/cloudzoo-product). A non-successful (error) response: - HTTP Status Code: A code greater or equal to `400 (Bad Request)` - Response Payload: [A non-successful response](#non-successful-responses) -------------------------------------------------------------------------------- # Calling Compute with Python Source: https://developer.rhino3d.com/en/guides/compute/compute-python-getting-started/ This guide covers all the necessary tools required to get started with the Rhino Compute Service using Python. By the end of this guide, you should have all the tools installed necessary for using the [Rhino Compute](https://www.rhino3d.com/compute) through Python. For an [getting started video tutorial from Junichiro Horikawa with Python](https://youtu.be/XCkRXAEJMhg) This guide presumes you have Python installed on the platform: - Python 2.7 - Windows (32 and 64 bit) - Python 3.7 - Windows (32 and 64 bit) - Python 2.7 - OSX (installed through homebrew) - Python 3.7 - OSX (installed through homebrew) - Linux and other python versions are supported through source distributions on PyPi ## Setting up a Compute Project in Python There are a few client side tools which need to be installed in Python that are essential to communicate with the Compute server. These include: - **rhino3dm.py** - This is the part of the [Rhino3dm libraries](https://github.com/mcneel/rhino3dm). It is a Python wrapper for [OpenNurbs](https://developer.rhino3d.com/guides/opennurbs/) which contains the functions to read and write Rhino Geometry Objects. This is available as a Pip package. `pip install rhino3dm` - **compute-rhino3d.py** - This is a work in progress package which is meant to add classes available in [RhinoCommon](https://developer.rhino3d.com/guides/rhinocommon/what-is-rhinocommon/), but not available through rhino3dm.py. Compute-rhino3d makes calls into the McNeel Cloud Compute server for these functions. It handles all the transaction authorizations and JSON data conversion. `pip install compute-rhino3d` ## The first use of Compute An example of using Python to access compute can be found in the [makemesh.py example](https://github.com/mcneel/rhino-developer-samples/tree/8/compute/py/SampleTkinter). Please note that the compute Python calls are made through the `compute_rhino3d.py` package using a `_` (underbar) and not a `-` (dash). # Next Steps *Congratulations!* You have the tools to use [Rhino Compute server](https://www.rhino3d.com/compute). *Now what?* 1. To see the transactional nature of Compute, read through **compute.rhino3d.py** 1. See a list of the [2400+ API calls](https://compute.rhino3d.com/sdk) available for compute.rhino3d.com. 1. Download the [Compute Samples repo from GitHub](https://github.com/mcneel/compute.rhino3d.samples). 1. The libraries are still very new and changing rapidly. Give them a try or get involved. Ask any questions or share what you are working on the [Compute Discussion Forum](https://discourse.mcneel.com/c/serengeti/compute-rhino3d) --- -------------------------------------------------------------------------------- # Circle From Length Source: https://developer.rhino3d.com/en/samples/rhinopython/circle-from-length/ Demonstrates how to add a circle based on its circumference using Python. ```python # Create a circle from a center point and a circumference. import rhinoscriptsyntax as rs import math def CreateCircle(circumference=None): center = rs.GetPoint("Center point of circle") if center: plane = rs.MovePlane(rs.ViewCPlane(), center) length = circumference if length is None: length = rs.GetReal("Circle circumference") if length and length>0: radius = length/(2*math.pi) objectId = rs.AddCircle(plane, radius) rs.SelectObject(objectId) return length return None # Check to see if this file is being executed as the "Main" python # script instead of being used as a module by some other python script # This allows us to use the module which ever way we want. if __name__ == '__main__': CreateCircle() # NOTE: see UseModule.py sample for using this script as a module ``` -------------------------------------------------------------------------------- # Creating Plugins that use the LAN Zoo Source: https://developer.rhino3d.com/en/guides/rhinocommon/rhinocommon-zoo-plugins/ This guide discusses how to create RhinoCommon plugins that can obtain licenses the LAN Zoo. ## Overview The LAN Zoo supports 3rd party plugins. RhinoCommon allows developers to write plugins for Rhino that use the Rhino license manager and obtain licenses from LAN Zoo servers. When a customer attempts to add a product license to the LAN Zoo, the product's plugin is called to validate the user's request. Upon validation, the plugin will return the product's licensing information back to the LAN Zoo. In turn, the LAN Zoo will serialize, maintain, and distribute this license. ## Prerequisites It is presumed you already have the necessary tools installed and are ready to go. If you are not there yet, see [Installing Tools (Windows)](/guides/rhinocommon/installing-tools-windows). Also, all plugins that use the LAN Zoo license system must be signed with an Authenticode certificate issued by McNeel Plugin Security. These certificates are free, but must be requested by each developer. Developers must agree to the Terms of Use before a certificate is issued. For more information on plugin signing, see [Digitally Signing Plugins for LAN Zoo](/guides/rhinocommon/digitally-signing-plugins-for-zoo). It is also presumed you have a RhinoCommon plugin you wish to add license support to. See the [Your First Plugin (Windows)](/guides/rhinocommon/your-first-plugin-windows/) guide for instructions. ## Add License Support After you have built and tested your basic plugin, you can add licensing support as follows: #### Step-by-Step 1. In your plugin's `Rhino.PlugIns.PlugIn` inherited class, create a new method with the same signature as the `Rhino.PlugIns.ValidateProductKeyDelegate` delegate. Rhino will call into this function whenever it needs your plugin to validate a license that is entered by a user, returned by the Rhino license manager (standalone node), or returned from a LAN Zoo server (network node). 2. In your plugin's `Rhino.PlugIns.PlugIn` inherited class, create a new method with the same signature as the `Rhino.PlugIns.OnLeaseChangedDelegate` delegate. Rhino will call into this function if your product supports Rhino Accounts. When Rhino Accounts gets a new lease, this function is called. 3. In your plugin's `Rhino.PlugIns.PlugIn.OnLoad` override, call `Rhino.PlugIns.GetLicense` and pass it your licenses's capabilities (enum), a text mask to assist the user in entering a license, and your two delegate functions. 4. Build your plugin. 5. [Digitally sign your plugin](/guides/rhinocommon/digitally-signing-plugins-for-zoo). 6. Launch Rhino and test your plugin. When your plugin is loaded for the first time, you will be prompted to enter a license... ## Related Topics - [Creating LAN Zoo Plugins](/guides/rhinocommon/creating-zoo-plugins) - [Digitally Signing Plugins for LAN Zoo](/guides/rhinocommon/digitally-signing-plugins-for-zoo) - [Sample RhinoCommon plugin project (GitHub)](https://github.com/mcneel/rhino-developer-samples/tree/6/rhinocommon/cs/SampleCsWithLicense) -------------------------------------------------------------------------------- # Custom Attributes Source: https://developer.rhino3d.com/en/guides/grasshopper/custom-attributes/ This guide contains a step-by-step walkthrough regarding custom object display. ## Overview Objects on the Grasshopper canvas consist of two parts. The most important piece is the class that implements the [IGH_DocumentObject](/api/grasshopper/html/T_Grasshopper_Kernel_IGH_DocumentObject.htm) interface. This interface provides the basic plumbing needed to make objects work within a Grasshopper node network. The interface part of objects however is handled separately. Every `IGH_DocumentObject` carries around an instance of a class that implements the [IGH_Attributes](/api/grasshopper/html/T_Grasshopper_Kernel_IGH_Attributes.htm) interface (indeed, every `IGH_DocumentObject` knows how to create its own stand-alone attributes) and it is this class that takes care of display, mouse interactions, popup menus, tooltips and so forth. In this guide we'll see how you can create your own attributes object. Since it's not possible to have an `IGH_Attributes` instance work on its own, we need an `IGH_DocumentObject` to tie it to. For this guide we'll assume we have a custom simple parameter (i.e. without persistent data) that holds integers. ## Attributes Object ```cs public class MySimpleIntegerParameter : GH_Param { public MySimpleIntegerParameter() : base(new GH_InstanceDescription("Integer with stats", "Int(stats)", "Integer with basic statistics", "Params", "Primitive")) { } public override System.Guid ComponentGuid { get { return new Guid("{33D07726-8298-4104-9EBC-5398D8AD5421}"); } } } ``` ```vbnet Public Class MySimpleIntegerParameter Inherits GH_Param(Of Types.GH_Integer) Public Sub New() MyBase.New(New GH_InstanceDescription("Integer with stats", "Int(stats)", "Integer with basic statistics", "Params", "Primitive")) End Sub Public Overrides ReadOnly Property ComponentGuid() As System.Guid Get Return New Guid("{33D07726-8298-4104-9EBC-5398D8AD5420}") End Get End Property End Class ``` What we'll do is create a special attributes object for this parameter which also displays the median and mean values of the collection of all integers. We want to put this information below the parameter name, but inside the parameter box. The first step here is to override the `CreateAttributes()` on `MySimpleIntegerParameter` and assign a new instance of our (yet to be written) attributes class: ```cs public override void CreateAttributes() { m_attributes = new MySimpleIntegerParameterAttributes(this); } ``` ```vbnet Public Overrides Sub CreateAttributes() m_attributes = New MySimpleIntegerParameterAttributes(Me) End Sub ``` That's it, no more code is required inside the `MySimpleIntegerParameter` class. This part at least is simple. If you don't override the `CreateAttributes()` method, then an instance of [GH_FloatingParamAttributes](/api/grasshopper/html/T_Grasshopper_Kernel_Attributes_GH_FloatingParamAttributes.htm) will be created instead. If your parameter is to be attached to a component as an input or output, then the component will assign an instance of [GH_LinkedParamAttributes](/api/grasshopper/html/T_Grasshopper_Kernel_Attributes_GH_LinkedParamAttributes.htm) to the parameter and `CreateAttributes()` will never be called. ## Grasshopper.Kernel.GH_Attributes Although the [IGH_Attributes](/api/grasshopper/html/T_Grasshopper_Kernel_IGH_Attributes.htm) interface is required for custom attributes, it is usually a good idea to derive from one of the abstract attribute classes already available. [GH_Attributes(T)](/api/grasshopper/html/T_Grasshopper_Kernel_GH_Attributes_1.htm) is the most basic and obvious choice and it implements a large amount of methods with default behaviour, saving you a lot of time and effort: ```cs public class MySimpleIntegerParameterAttributes : GH_Attributes { public MySimpleIntegerParameterAttributes(MySimpleIntegerParameter owner) : base(owner) { } } ``` ```vbnet Public Class MySimpleIntegerParameterAttributes Inherits GH_Attributes(Of MySimpleIntegerParameter) Public Sub New(ByVal owner As MySimpleIntegerParameter) MyBase.New(owner) End Sub End Class ``` This is enough so far to make it work, even though all the logic is still standard. We need to start overriding methods in `MySimpleIntegerParameterAttributes` to suit our needs. But first some basic information regarding the default behaviour. `GH_Attributes` assumes that the object that owns it is rectangular. This is true for most objects in Grasshopper, but there are some notable exceptions such as Pie-Graphs, Sketches and Scribbles. But this assumption (which holds true in our case) allows `GH_Attributes` to supply basic functionality for a wide variety of methods. All attributes have a property that defines the size of the object called [Bounds](/api/grasshopper/html/P_Grasshopper_Kernel_IGH_Attributes_Bounds.htm). Basically everything that happens outside of the `Bounds` goes by unnoticed. Also, if the Bounds rectangle is not visible within the canvas area, Grasshopper might decide to not even bother calling any painting methods. Because our parameter will be rectangular, we don't have to override any of the picking logic, as the default implementation of [IsPickRegion](/api/grasshopper/html/Overload_Grasshopper_Kernel_GH_Attributes_1_IsPickRegion.htm), [IsMenuRegion](/api/grasshopper/html/M_Grasshopper_Kernel_GH_Attributes_1_IsMenuRegion.htm) and [IsTooltipRegion](/api/grasshopper/html/M_Grasshopper_Kernel_GH_Attributes_1_IsTooltipRegion.htm) will already work. ## Layout We do however need to supply custom Layout logic. The width of our attributes depends on both the length of the `NickName` of the `MySimpleIntegerParameter` that owns these attributes *and* on the length of the statistics information we want to include. The height of the parameter however is fixed, though larger than the standard height for parameters in Grasshopper. In order to supply custom layout logic, we need to override the [Layout](/api/grasshopper/html/M_Grasshopper_Kernel_GH_Attributes_1_Layout.htm) method. In this case I measure the width of the `NickName` of the Owner object, and make sure the parameter is never narrower than 80 pixels: ```cs protected override void Layout() { // Compute the width of the NickName of the owner (plus some extra padding), // then make sure we have at least 80 pixels. int width = GH_FontServer.StringWidth(Owner.NickName, GH_FontServer.Standard); width = Math.Max(width + 10, 80); // The height of our object is always 60 pixels int height = 60; // Assign the width and height to the Bounds property. // Also, make sure the Bounds are anchored to the Pivot Bounds = new RectangleF(Pivot, new SizeF(width, height)); } ``` ```vbnet Protected Overrides Sub Layout() 'Compute the width of the NickName of the owner (plus some extra padding), 'then make sure we have at least 80 pixels. Dim width As Int32 = GH_FontServer.StringWidth(Owner.NickName, GH_FontServer.Standard) width = Math.Max(width + 10, 80) 'The height of our object is always 60 pixels Dim height As Int32 = 60 'Assign the width and height to the Bounds property. 'Also, make sure the Bounds are anchored to the Pivot Bounds = New RectangleF(Pivot, New SizeF(width, height)) End Sub ``` The `Pivot` is a `PointF` structure that is changed when the object is dragged. It is therefore important that you always "anchor" the layout of some attributes to the `Pivot`. If you fail to do so, your attributes will become undraggable. There is a method you can override that will be called prior to the call to `Layout` which can be used to destroy any cached data you might have that's to do with display. But note that if you override [ExpireLayout](/api/grasshopper/html/M_Grasshopper_Kernel_GH_Attributes_1_ExpireLayout.htm) you *must* place a call to the base class method as well: ```cs publicoverride void ExpireLayout() { base.ExpireLayout(); // Destroy any data you have that becomes // invalid when the layout expires. } ``` ```vbnet Public Overrides Sub ExpireLayout() MyBase.ExpireLayout() 'Destroy any data you have that becomes 'invalid when the layout expires. End Sub ``` ## Render Now that we have handled the Layout, we need to override the display of the parameter. There's two parts to doing so. You always have to override the [Render](/api/grasshopper/html/M_Grasshopper_Kernel_GH_Attributes_1_Render.htm) method, as this is where the drawing takes place. Render is called a number of times as there are several "layers" or "channels" to a single Grasshopper canvas. At first, the background of the canvas is drawn. During this process attributes are not yet involved. Then there will be four channels where `IGH_Attributes` will be allowed to draw various shapes. First the groups are drawn (as they are behind all other objects), but every `GH_Attributes.Render()` method will be called once for the [Groups](/api/grasshopper/html/T_Grasshopper_GUI_Canvas_GH_CanvasChannel.htm) channel. Typically you should not draw anything in the Groups channel. Next up is the [Wires](/api/grasshopper/html/T_Grasshopper_GUI_Canvas_GH_CanvasChannel.htm) channel where all parameter connector wires are drawn. If your object has input parameters or is a parameter, it is your responsibility to draw all wires coming into your object. Wires going out the right side will be drawn by the recipient objects. Next the actual Components and Parameters themselves are drawn inside the [Objects](/api/grasshopper/html/T_Grasshopper_GUI_Canvas_GH_CanvasChannel.htm) channel. This is typically the most work, though there are lots of classes that take care of common tasks. The default visual style of Components and parameter objects is the shiny, rounded rectangle. You can use the [GH_Capsule](/api/grasshopper/html/T_Grasshopper_GUI_Canvas_GH_Capsule.htm) type to draw these shapes with a minimum of fuss. Ultimately there's an [Overlay](/api/grasshopper/html/T_Grasshopper_GUI_Canvas_GH_CanvasChannel.htm) channel which is rarely used but it allows you to draw shapes that need to be on top of all other components and parameters. After this, there are still more channels to do with canvas widgets, but `IGH_Attributes` are not involved here. Inside our implementation of the `Render()` method, we need to draw the wires coming into the `MySimpleIntegerParameter`, then the parameter capsule, while taking care to assign the correct colours (grey for normal, green for selected, dark for disabled, orange for warnings and red for errors). Finally we have to draw three lines of text on top of the capsule; the name of the owner, the median integer and the mean integer. The important types involved here are: - [GH_Canvas](/api/grasshopper/html/T_Grasshopper_GUI_Canvas_GH_Canvas.htm) - [GH_Painter](/api/grasshopper/html/T_Grasshopper_GUI_Canvas_GH_Painter.htm) - [GH_Palette](/api/grasshopper/html/T_Grasshopper_GUI_Canvas_GH_Palette.htm) - [GH_RuntimeMessageLevel](/api/grasshopper/html/T_Grasshopper_Kernel_GH_RuntimeMessageLevel.htm) - [GH_Capsule](/api/grasshopper/html/T_Grasshopper_GUI_Canvas_GH_Capsule.htm) - [GH_FontServer](/api/grasshopper/html/T_Grasshopper_Kernel_GH_FontServer.htm) ```cs protected override void Render(GH_Canvas canvas, Graphics graphics, GH_CanvasChannel channel) { // Render all the wires that connect the Owner to all its Sources. if (channel == GH_CanvasChannel.Wires) { RenderIncomingWires(canvas.Painter, Owner.Sources, Owner.WireDisplay); return; } // Render the parameter capsule and any additional text on top of it. if (channel == GH_CanvasChannel.Objects) { // Define the default palette. GH_Palette palette = GH_Palette.Normal; // Adjust palette based on the Owner's worst case messaging level. switch (Owner.RuntimeMessageLevel) { case GH_RuntimeMessageLevel.Warning: palette = GH_Palette.Warning; break; case GH_RuntimeMessageLevel.Error: palette = GH_Palette.Error; break; } // Create a new Capsule without text or icon. GH_Capsule capsule = GH_Capsule.CreateCapsule(Bounds, palette); // Render the capsule using the current Selection, Locked and Hidden states. // Integer parameters are always hidden since they cannot be drawn in the viewport. capsule.Render(graphics, Selected, Owner.Locked, true); // Always dispose of a GH_Capsule when you're done with it. capsule.Dispose(); capsule = null; // Now it's time to draw the text on top of the capsule. // First we'll draw the Owner NickName using a standard font and a black brush. // We'll also align the NickName in the center of the Bounds. StringFormat format = new StringFormat(); format.Alignment = StringAlignment.Center; format.LineAlignment = StringAlignment.Center; format.Trimming = StringTrimming.EllipsisCharacter; // Our entire capsule is 60 pixels high, and we'll draw // three lines of text, each 20 pixels high. RectangleF textRectangle = Bounds; textRectangle.Height = 20; // Draw the NickName in a Standard Grasshopper font. graphics.DrawString(Owner.NickName, GH_FontServer.Standard, Brushes.Black, textRectangle, format); // Now we need to draw the median and mean information. // Adjust the formatting and the layout rectangle. format.Alignment = StringAlignment.Near; textRectangle.Inflate(-5, 0); textRectangle.Y += 20; graphics.DrawString(String.Format("Median: {0}", Owner.MedianValue), _ GH_FontServer.StandardItalic, Brushes.Black, _ textRectangle, format); textRectangle.Y += 20; graphics.DrawString(String.Format("Mean: {0:0.00}", Owner.MeanValue), _ GH_FontServer.StandardItalic, Brushes.Black, _ textRectangle, format); // Always dispose of any GDI+ object that implement IDisposable. format.Dispose(); } } ``` ```vbnet Protected Overrides Sub Render(ByVal canvas As GH_Canvas, ByVal graphics As Graphics, ByVal channel As GH_CanvasChannel) 'Render all the wires that connect the Owner to all its Sources. If (channel = GH_CanvasChannel.Wires) Then RenderIncomingWires(canvas.Painter, Owner.Sources, Owner.WireDisplay) Return End If 'Render the parameter capsule and any additional text on top of it. If (channel = GH_CanvasChannel.Objects) Then 'Define the default palette. Dim palette As GH_Palette = GH_Palette.Normal 'Adjust palette based on the Owner's worst case messaging level. Select Case Owner.RuntimeMessageLevel Case GH_RuntimeMessageLevel.Warning palette = GH_Palette.Warning Case GH_RuntimeMessageLevel.Error palette = GH_Palette.Error End Select 'Create a new Capsule without text or icon. Dim capsule As GH_Capsule = GH_Capsule.CreateCapsule(Bounds, palette) 'Render the capsule using the current Selection, Locked and Hidden states. 'Integer parameters are always hidden since they cannot be drawn in the viewport. capsule.Render(graphics, Selected, Owner.Locked, True) 'Always dispose of a GH_Capsule when you're done with it. capsule.Dispose() capsule = Nothing 'Now it's time to draw the text on top of the capsule. 'First we'll draw the Owner NickName using a standard font and a black brush. 'We'll also align the NickName in the center of the Bounds. Dim format As New StringFormat() format.Alignment = StringAlignment.Center format.LineAlignment = StringAlignment.Center format.Trimming = StringTrimming.EllipsisCharacter 'Our entire capsule is 60 pixels high, and we'll draw 'three lines of text, each 20 pixels high. Dim textRectangle As RectangleF = Bounds textRectangle.Height = 20 'Draw the NickName in a Standard Grasshopper font. graphics.DrawString(Owner.NickName, GH_FontServer.Standard, Brushes.Black, textRectangle, format) 'Now we need to draw the median and mean information. 'Adjust the formatting and the layout rectangle. format.Alignment = StringAlignment.Near textRectangle.Inflate(-5, 0) textRectangle.Y += 20 graphics.DrawString(String.Format("Median: {0}", Owner.MedianValue), _ GH_FontServer.StandardItalic, Brushes.Black, _ textRectangle, format) textRectangle.Y += 20 graphics.DrawString(String.Format("Mean: {0:0.00}", Owner.MeanValue), _ GH_FontServer.StandardItalic, Brushes.Black, _ textRectangle, format) 'Always dispose of any GDI+ object that implement IDisposable. format.Dispose() End If End Sub ``` Note that in this case I assume that `MySimpleIntegerParameter` has two ReadOnly properties called `MedianValue` and `MeanValue`. I haven't written those, as they are not within the scope of this guide. If you have cached display objects (for whatever reason I don't want to hear), a good place to ensure they are [PrepareForRender](/api/grasshopper/html/M_Grasshopper_Kernel_GH_Attributes_1_PrepareForRender.htm) method. It is called once (and only once) just before any calls to `Render()`. You do not need to call the overridden method as it is empty by default. -------------------------------------------------------------------------------- # Defining New Plugin Commands Source: https://developer.rhino3d.com/en/guides/cpp/defining-new-plugin-commands/ This guide discusses Rhino commands and how define new commands using C/C++. ## Overview Rhino plugins can contain any number of commands. Commands are created by deriving a new class from `CRhinoCommand`. See *rhinoSdkCommand.h* for details on the `CRhinoCommand` class. Command classes must return a unique *GUID*. If you try to use a *GUID* that is already in use, then your command will not work. Use the *GUIDGEN.EXE* utility, that comes with Visual Studio, to create unique *GUIDs*. Command classes must return a unique command name. If you try to use a command name that is already in use, then your command will not work. Only ONE instance of a command class can be created. This is why you should put the definition of your command classes in *.cpp* files. ## Rhino 8 The **[Rhino Visual Studio Extension](https://github.com/mcneel/RhinoVisualStudioExtensions/releases)**, for the Rhino 8 C/C++ SDK, includes a template that lets you quickly add new commands to your plugin project. To add a new Rhino command to your plugin project, right-click on the *Source Files* folder, in *Visual Studio’s Solution Explorer*, and click *Add > New Item...*. From the *Add New Item* dialog, select *Empty Command for Rhino 3D (C++)*, specify the name of the command, and click *Add*. ![Add New Item](https://developer.rhino3d.com/images/defining-new-plugin-commands-cpp.png) ## Rhino 7 The *Rhino Command Generator* wizard, included with the Rhino 7 C/C++ SDK, is a standalone application that will generate new skeleton `CRhinoCommand`-derived class. The generated source code is copied to the Windows clipboard so you can easily paste it into your source files. To use this tool in Visual Studio: 1. Launch Visual Studio. 2. Navigate to *Tools* > *External Tools...*. 3. Use the *Add* button to add the *RhinoCommandGenerator.exe* file to the list. The file can be found in the following location: *C:\\Program Files\\Rhino 7.0 SDK\\Wizards\\Command* ![Rhino Command Generator](https://developer.rhino3d.com/images/your-first-plugin-windows-cpp-08.png) Once the tool is installed, you can create a new command by selecting *Tools* > *Rhino Command*. If you add the command declaration to a new *.cpp* file, be sure to `#include "stdafx.h"` at the top. ## Sample The following sample code demonstrates a simple command class that essentially does nothing: ```cpp // Do NOT put the definition of class CCommandTest in a header // file. There is only ONE instance of a CCommandTest class // and that instance is the static theTestCommand that appears // immediately below the class definition. class CCommandTest : public CRhinoCommand { public: // The one and only instance of CCommandTest is created below. // No copy constructor or operator= is required. // Values of member variables persist for the duration of the application. // CCommandTest::CCommandTest() // is called exactly once when static theTestCommand is created. CCommandTest() = default; // CCommandTest::~CCommandTest() // is called exactly once when static theTestCommand is destroyed. // The destructor should not make any calls to the Rhino SDK. // If your command has persistent settings, then override // CRhinoCommand::SaveProfile and CRhinoCommand::LoadProfile. ~CCommandTest() = default; // Returns a unique UUID for this command. // If you try to use an id that is already being used, then // your command will not work. Use GUIDGEN.EXE to make unique UUID. UUID CommandUUID() override { // {F502C783-C0CE-4118-8869-EFB0CB34CCCB} static const GUID TestCommand_UUID = { 0xF502C783, 0xC0CE, 0x4118, { 0x88, 0x69, 0xEF, 0xB0, 0xCB, 0x34, 0xCC, 0xCB } }; return TestCommand_UUID; } // Returns the English command name. // If you want to provide a localized command name, then override // CRhinoCommand::LocalCommandName. const wchar_t* EnglishCommandName() override { return L"Test"; } // Rhino calls RunCommand to run the command. CRhinoCommand::result RunCommand(const CRhinoCommandContext& context) override; }; // The one and only CCommandTest object // Do NOT create any other instance of a CCommandTest class. static class CCommandTest theTestCommand; CRhinoCommand::result CCommandTest::RunCommand(const CRhinoCommandContext& context) { // CCommandTest::RunCommand() is called when the user // runs the "Test". // TODO: Add command code here. // Rhino command that display a dialog box interface should also support // a command-line, or scriptable interface. ON_wString str; str.Format(L"The \"%s\" command is under construction.\n", EnglishCommandName()); if (context.IsInteractive()) RhinoMessageBox(str, TestPlugIn().PlugInName(), MB_OK); else RhinoApp().Print(str); // TODO: Return one of the following values: // CRhinoCommand::success: The command worked. // CRhinoCommand::failure: The command failed because of invalid input, inability // to compute the desired result, or some other reason. // CRhinoCommand::cancel: The user interactively canceled the command // (by pressing ESCAPE, clicking a CANCEL button, etc.) // in a Get operation, dialog, time consuming computation, etc. return CRhinoCommand::success; } ``` -------------------------------------------------------------------------------- # Grasshopper Icons Source: https://developer.rhino3d.com/en/guides/grasshopper/grasshopper-icons/ This guide contains the original vector graphics used for Grasshopper icons. ## Vector Source [ ](/files/Grasshopper_Icon_Set.zip) [Grasshopper_Icon_Set.zip](/files/Grasshopper_Icon_Set.zip) The files were written with Xara Designer Pro 6, but Xara is pretty good at file compatibility so you can probably open them on many versions. You are free to use and modify these vector graphics for any project, document or webpage that is associated with Grasshopper. You do not need to credit me, though it is not allowed to pass the work off as your own without modifications. ## Graphics Style There are no fast and hard rules when it comes to the graphics style of Grasshopper icons, but I suggest you try and mimic line-weights, contrast, saturation and arrow and point symbols when designing your own icons. Note that all icons must be 24x24 pixels and most icons in the original set leave a 2-pixel empty border around all edges. All icons with such an empty border have a drop-shadow with the following properties: Blur = 2 pixels Colour = black Transparency = 65 out of 255 (roughly 25%) Horizontal offset = 1 pixel to the right Vertical offset = 1 pixel downwards I apply drop-shadows as a post-process effect in a pixel-editor, as Xara is not good at pixel effects on very small images. ## Related Topics - [On Icons (ieatbugsforbreakfast)](https://ieatbugsforbreakfast.wordpress.com/2012/07/12/on-icons/) -------------------------------------------------------------------------------- # How to create a virtual machine (VM) on Amazon Web Service Source: https://developer.rhino3d.com/en/guides/compute/creating-an-aws-vm/ ## Overview In this guide, we will walk through the process of setting up a virtual machine using Amazon Elastic Compute Cloud (Amazon EC2). To start, you will need to confirm your AWS subscription. If you are new to AWS, you can get started with Amazon EC2 using the [AWS Free Tier](https://aws.amazon.com/free/?all-free-tier.sort-by=item.additionalFields.SortRank&all-free-tier.sort-order=asc&awsf.Free%20Tier%20Types=*all&awsf.Free%20Tier%20Categories=*all). If you created your AWS account on or after July 15, 2025, it's less than 6 months old, and you haven't used up all your credits, it will not cost you anything to complete this tutorial. Otherwise, you'll incur the standard Amazon EC2 usage fees from the time that you launch the instance until you terminate the instance, even if it remains idle. ## Launch the instance To create a new virtual machine instance on AWS, follow these steps: 1. Open the [Amazon EC2 console](https://console.aws.amazon.com/ec2/). 1. From the EC2 console dashboard, int the **Launch instance** pane, select **Launch Instance**. 1. Under the **Names and tags** section, enter a name for the VM instance. For this tutorial, we'll use the name **"RhinoComputeVM"**. 1. Under the **Application and OS Images** section, click on the **Windows** button under the Quick Start tab. Under the Amazon Machine Image (AMI) section there should be a drop-down menu listing all of the available machine images. Select the AMI for **Microsoft Windows Server 2025 Base**. 1. In the **Instance Type** section, select the **t3.micro** instance type (default) or a larger instance type if needed. Note: the *t3.micro* instance type is elegible for the free tier. 1. In the **Key Pair (login)** section, for **Key pair name**, choose an existing key pair or choose **Create new key pair** to create your first key pair. 1. In the **Network Settings** section, under the **Firewall (security groups)** choose the **Select existing security group** radio button. Then, under the **Common Security Groups** drop-down list, select the security group. Note: if you are creating a new security group, you must allow RDP traffic.If the **Auto-assign public IP** setting is set to **Disabled**, click on the **Edit** button on the top-right of this section panel and change this setting to **Enabled**. 1. In the **Configure storage** section, select the default amount of storage for this instance. 1. Now, on the far right select the **Launch Instance**. 1. A confirmation page lets you know that your instance has successfully launched. In the top-most menu which reads **EC2 > Instances > Launch an instance**, select the **Instances** menu item to view the instances console window. 1. On the **Instances** screen, you can view the status of the launched instance. The instance should automatically be running after launch, but if not select the instance row checkbox and then select the **Instance State** menu item at the top. Select **Start Instance** to start the virtual machine. 1. With the instance row selected, click the **Connect** button in the top menu. 1. On the **Connect to instance** page, select the **RDP client** tab. 1. Next, select the **Get password** button. 1. Choose **Upload private key file** and navigate to the private key (.pem) file that you created when you launched the instance. 1. Choose **Decrypt Password**. The console displays the default administrator password for the instance under **Password**, replacing the **Get password** link shown previously. **Save this password in a safe place**. This passord is required to connect to the instance. 1. Select **Download remote desktop file** to save the .rdp file to your local computer. You will need this file when you connect to your instance using the Remote Desktop Connect app. Congratulations! In this tutorial, you successfully launched a virtual machine on AWS and downloaded the RDP file which can be used to connect to that instance. -------------------------------------------------------------------------------- # How to read and write a simple file Source: https://developer.rhino3d.com/en/guides/rhinopython/python-reading-writing/ Use Python to read and write files. We'll use a couple of simple scripts from the Python Samples folder as examples - we'll dissect these to see how they work. ## Reading a file Here is the import-points.py script ```python # Import points from a text file import rhinoscriptsyntax as rs def ImportPoints(): #prompt the user for a file to import filter = "Text file (*.txt)|*.txt|All Files (*.*)|*.*||" filename = rs.OpenFileName("Open Point File", filter) if not filename: return #read each line from the file file = open(filename, "r") contents = file.readlines() file.close() # local helper function def __point_from_string(text): items = text.strip("()\n").split(",") x = float(items[0]) y = float(items[1]) z = float(items[2]) return x, y, z contents = [__point_from_string(line) for line in contents] rs.AddPoints(contents) ########################################################################## # Check to see if this file is being executed as the "main" python # script instead of being used as a module by some other python script # This allows us to use the module which ever way we want. if( __name__ == "__main__" ): ImportPoints() ``` We've put the action inside a function in this script, the ```python def ImportPoints() ``` section. This allows the code to be re-used and simplifies error checking. The first part uses the rs function ```python rs.OpenFileName() ``` to allow the user to specify a file to open, and sets a variable 'filename' to hold this information: ```python filename = rs.OpenFileName("Open Point File", filter) ``` with a filter for text files. If for some reason the user does not specify a file, say with a Cancel, then the line ```python if not filename: return ``` ensures that the rest of the code inside this particular function will not be run, since there is nothing that can be done without a file name, and executing the next lines in the script will result in error messages without it. Since there is only the one function in this script, a cancel means the script will stop. Next a variable is set to the open file for reading: ```python file = open(filename, "r") ``` and a variable is set to the contents of the file which is then held in memory as a list with one item per line: ```python contents = file.readlines() ``` The file is then closed: ``` python file.close() ``` See python documentation for other ways to read files (). At this point in the script, a new function is added, called *__point_from_string(text)* , inside the main function, that parses text in the expected format and converts the text into three numbers and returns those as the xy,z coordinates of a point. The list of lines from the orignal text file is fed into this function, line by line to create a list of points, by calling that function: ```python contents = [__point_from_string(line) for line in contents] ``` Lastly, the points in the list are added to the Rhino document as point objects: ```python rs.AddPoints(contents) ``` Note all if this code will not actually run unless the function is called - this is done from the very last line of the script: ```python if( __name__ == "__main__" ): ImportPoints() ``` ## Writing a file Writing a file is similar to reading, but the order in which things are done is different. This time we'll look at the ExportPoints.py sample file. ```python import rhinoscriptsyntax as rs #Get the points to export objectIds = rs.GetObjects("Select Points",1+2, True, True) if( objectIds==None ): return #Get the filename to create filter = "Text File (*.txt)|*.txt|All Files (*.*)|*.*||" filename = rs.SaveFileName("Save point coordinates as", filter) if( filename==None ): return with open(filename, "w") as file: # treat the objects differently depending on whether they are points or point clouds for id in objectIds: #process point clouds if( rs.IsPointCloud(id) ): points = rs.PointCloudPoints(id) for pt in points: file.write(str(pt)+"\n") elif( rs.IsPoint(id) ): point = rs.PointCoordinates(id) file.write(str(point) + "\n") ``` There are a couple of things to notice, compared to the previous example. We need some points to export, so we'll let the user select some point objects and point clouds. There is a filter applied to the selection, the _1+2_. 1 filters for point objects and 2 filters for point clouds - we can add the filters together to make combinations such as we have here - 1+2 or, we could have written just 3, that would work as well, it is just harder to parse when reviewing or modifying the code: ```python objectIds = rs.GetObjects("Select Points", 1+2, True, True) ``` As before we check to make sure that the user has in fact selected the things we're looking for. ```python if( objectIds==None ): return ``` Next we'll get a file name as in the first example: ```python #Get the filename to create filter = "Text File (*.txt)|*.txt|All Files (*.*)|*.*||" filename = rs.SaveFileName("Save point coordinates as", filter) if( filename==None ): return ``` When writing to a file it must first be *opened*. While it is open, the information can be written to the file. After writing all the information, the file must be closed. In the first example we explicitly opened, read and then closed the file. This time we've used the *with* statement to open the file - *with* is convenient because it takes care of closing the file and cleaning up when the script leaves the indented *with* section. ```python with open(filename, "w") as file: # treat the objects differently depending on whether they are points or point clouds for id in objectIds: #process point clouds if( rs.IsPointCloud(id) ): points = rs.PointCloudPoints(id) for pt in points: file.write(str(pt)+"\n") #process point objects elif( rs.IsPoint(id) ): point = rs.PointCoordinates(id) file.write(str(point) + "\n") ​``` Keep in mind that 'points' in the script are not the same as 'point objects' in the Rhino file. Points are a list of x,y,z coordinates and point objects are objects in the Rhino file that have, among other properties like display color or layer, a location - the point coordinates. As you can see in the above, the script iterates a list of IDs -those of the selected point objects or point clouds, and extracts the x,y,z coordinates to write to the file. The exported points are only the x,y,z coordinates of the point objects. For more information on reading and writing from Python, see the [Python Methods in File Objects documentation](https://docs.python.org/2/tutorial/inputoutput.html#methods-of-file-objects) -------------------------------------------------------------------------------- # License Agreement Source: https://developer.rhino3d.com/en/guides/cpp/license-agreement/ This guide provides the license agreement of C/C++ SDK. #### Rhino C/C++ Software Development Kit (SDK) Copyright (c) 1993-2023 Robert McNeel & Associates. All Rights Reserved. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software. THIS SOFTWARE IS PROVIDED "AS IS" WITHOUT EXPRESS OR IMPLIED WARRANTY. ALL IMPLIED WARRANTIES OF FITNESS FOR ANY PARTICULAR PURPOSE AND OF MERCHANTABILITY ARE HEREBY DISCLAIMED. Rhinoceros and openNURBS are a registered trademarks of Robert McNeel & Associates. -------------------------------------------------------------------------------- # List of Points in Python Source: https://developer.rhino3d.com/en/guides/rhinopython/python-rhinoscriptsyntax-list-points/ This guide provides an overview of a rhinoscriptsyntax list of Point Geometry in Python. ## Lists of Points Many rhinoscriptsyntax functions require a list of points as an argument or return a list of [Point3d](/guides/rhinopython/python-rhinoscriptsyntax-points) structures. For example the 'DivideCurve()' function will return a list of points: ```python import rhinoscriptsyntax as rs obj = rs.GetObject("Select a curve") if obj: points = rs.DivideCurve(obj, 4) print(points[0]) ``` There are a number of ways to access the information in these lists. Use an index to access any one of the points as in the line: ```python print(points[0]) # Returns a Point3d structure ``` With Python, it is easy to use the `for` loop to walk through the list and print out the coordinates for each point, ```python for i in points: print(i) ``` It is also possible to use nested indexes to access a specific coordinate of a point in the list. This example will access the Y coordinate of the second point in the list: ```python print(points[1][1]) ``` Using the .Y property on the [Point3d](/guides/rhinopython/python-rhinoscriptsyntax-points) also would work: ```python print(points[1].Y) ``` To add a point to this list, first create the point3d with `CreatePoint()`, then [append](https://docs.python.org/2/tutorial/datastructures.html) it: ```python points.append(rs.CreatePoint(1.0, 2.0, 3.0)) for i in points: print(i) ``` ## Related Topics - [What is Python and RhinoScript?](/guides/rhinopython/what-is-rhinopython) - [Python Points](/guides/rhinopython/python-rhinoscriptsyntax-points) - [Python Vectors](/guides/rhinopython/python-rhinoscriptsyntax-vectors) - [Python Lines](/guides/rhinopython/python-rhinoscriptsyntax-lines) - [Python Planes](/guides/rhinopython/python-rhinoscriptsyntax-planes) - [Python Objects](/guides/rhinopython/python-rhinoscriptsyntax-objects) -------------------------------------------------------------------------------- # Moving to .NET Core Source: https://developer.rhino3d.com/en/guides/rhinocommon/moving-to-dotnet-core/ This guide walks you through making the transition to .NET Core in Rhino 8 and Rhino 9 Rhino 8 introduced the open source [.NET Core Runtime](https://github.com/dotnet/runtime) for running .NET code on both Windows and Mac, replacing the [mono runtime](https://www.mono-project.com) previously used on Mac and .NET Framework on Windows. **Rhino 9** continues this transition with .NET 10 as the default runtime, and **.NET Framework is deprecated**. .NET Framework runtime and plugins may still load and run in Rhino 9, but support is not guaranteed to continue in future versions. **All plugin developers should migrate to .NET 10.** On Windows, Rhino 8 still offered an optional .NET Framework fallback for compatibility with third-party plugins or Rhino.Inside scenarios. That fallback remains available in Rhino 9 but is deprecated and should not be relied upon for new or updated plugins. Most plugins are already compatible when running in .NET Core without any recompilation, but in the case of any incompatibilities you may need to update your plugin. ## Advantages of .NET Core for Rhino Using .NET Core allows Rhino and plugins to take advantage of many [performance improvements](https://devblogs.microsoft.com/dotnet/announcing-dotnet-10/) which will make just about all .NET code execute much faster. This can potentially provide huge productivity gains with computational libraries or large data sets. Additionally, using .NET Core on Mac eliminates a lot of compatibility issues between the Mac and Windows versions of Rhino making it easier to make plugins work on both platforms. ## Choosing the .NET Runtime on Windows **The .NET Framework runtime is deprecated in Rhino 9.** It still works today, and plugins targeting .NET Framework continue to load and run, but the runtime itself is not guaranteed to remain available in future releases. You should **migrate your plugins to .NET 10** — do not ship new or updated plugins targeting .NET Framework only. In **Rhino 8**, there may be reasons to continue using .NET Framework on Windows, such as needing third-party plugins that are not compatible with .NET Core yet. The disadvantages are that Rhino may run a little slower in certain use cases, and that you won't be able to use newer plugins that target .NET Core only. Rhino 8 initially shipped with the .NET 7.0.0 runtime. Rhino 8.12 or later has the option to use a manually installed .NET 8 runtime, and Rhino 8.20 installs and defaults to using .NET 8.0.14 or later. In **Rhino 8**, there are a few ways to select the runtime and version: 1. Use the `SetDotNetRuntime` command, then restart Rhino. 1. Pass either `/netcore` or `/netfx` as an argument when launching `Rhino.exe`. This overrides the `SetDotNetRuntime` setting. 1. Pass either `/netcore-8` or `/netcore-7` to specify a particular .NET Core version. This requires that the chosen version is manually installed if it's different from Rhino's default. In **Rhino 9**, `/netcore` targets .NET 10 and is the default. The `/netfx` flag and `SetDotNetRuntime` command still work but are deprecated — use of .NET Framework in Rhino 9 is strongly discouraged, so migrate to .NET 10 as soon as possible. ## Rhino.Inside When using Rhino.Inside, the runtime Rhino uses is the same as the host application. The runtime a given host requires depends on its version — for example, Revit has moved through several .NET runtimes over its recent releases: | Revit Version | .NET Runtime | | --- | --- | | Revit 2024 and earlier | .NET Framework 4.8 | | Revit 2025 (2025.0–2025.4) | .NET 8 | | Revit 2026 (2026.0–2026.4) | .NET 8 | | Revit 2025.5, Revit 2026.5, and later | .NET 10 | | Revit 2027 and later | .NET 10 | Autodesk migrated Revit 2025 and 2026 from .NET 8 to .NET 10 (starting with the 2025.5 and 2026.5 updates) to follow Microsoft's long-term support lifecycle, since .NET 8 support ends in November 2026. This means Rhino.Inside.Revit runs on whichever runtime the hosting Revit version requires: .NET Framework when hosted in Revit 2024 or earlier, .NET 8 in Revit 2025/2026 prior to their .5 updates, and .NET 10 in Revit 2025.5/2026.5 and Revit 2027 or later. In Rhino 9, .NET Framework is deprecated but still works, so Rhino.Inside hosts that require .NET Framework can continue to run while you plan a migration to .NET 10. Continued use of .NET Framework in Rhino 9 is strongly discouraged. Custom Rhino.Inside applications should migrate to .NET 10 to take advantage of the performance improvements and to support Rhino 9. ### BinaryFormatter [`BinaryFormatter`](https://learn.microsoft.com/en-us/dotnet/api/system.runtime.serialization.formatters.binary.binaryformatter) serialization is disabled by default in .NET 9 and later, so any code that relies on it will throw when running under .NET 10 — including when hosted in Revit versions that use .NET 10 (Revit 2025.5/2026.5 and Revit 2027 or later). When running Rhino standalone, Rhino enables `BinaryFormatter` support so existing plugins that depend on it continue to work. However, when Rhino is hosted inside another application via Rhino.Inside, that support is not enabled by the host — for example, Rhino.Inside.Revit running in a .NET 10 version of Revit will not have `BinaryFormatter` available by default. `BinaryFormatter` is insecure and its use is discouraged. Ideally, migrate away from it to a safer serializer. If your application still requires it, you can re-enable it in your own process by referencing the [`System.Runtime.Serialization.Formatters` compatibility package](https://learn.microsoft.com/en-ca/dotnet/standard/serialization/binaryformatter-migration-guide/#use-the-compatibility-package). ## Checking if your plugin is compatible Rhino 8 and Rhino 9 will automatically scan plugins for any known API breakages when running in .NET Core, and will provide a report of the specific assemblies and APIs that are not compatible. To check manually, you can use the `compat.exe` tool on each of your plugin assemblies: For Rhino 8: ``` "C:\Program Files\Rhino 8\System\netcore\compat.exe" -q --check-system-assemblies MyPlugin.rhp ``` For Rhino 9: ``` "C:\Program Files\Rhino 9\System\netcore\compat.exe" -q --check-system-assemblies MyPlugin.rhp ``` You can also use Microsoft's [upgrade assistant](https://learn.microsoft.com/en-us/dotnet/core/porting/upgrade-assistant-overview) to analyze your project for compatibility issues. ## Migrating your plugin Many plugins won't need any changes to run in Rhino 8 with .NET Core, but if they do it is recommended to multi-target your plugin(s) for .NET 4.8, and .NET 8.0 so that it can run in either runtime on Windows. For **Rhino 9**, you should target **.NET 10.0**. Since .NET Framework is deprecated in Rhino 9, multi-targeting .NET 4.8 is only necessary if you also need to support Rhino 8 on Windows with the .NET Framework fallback, or Rhino 7 and earlier. For Mac-specific plugins you can target .NET 8.0 (Rhino 8) or .NET 10.0 (Rhino 9) as .NET Core is the only runtime available on Mac. If you want the plugin to be compatible with Rhino 7, target .NET 4.8 and use multi-targeting. | Rhino Version | Recommended Target | Notes | |---|---|---| | Rhino 7 | `net48` | .NET Framework or Mono | | Rhino 8 (Windows/Mac, .NET Core) | `net8.0` | Default from Rhino 8.20 | | Rhino 8 (Windows, .NET Framework) | `net48` | Deprecated path, avoid for new work | | Rhino 9 (Windows/Mac) | `net10.0` | .NET Framework deprecated, but still works | Since Rhino 8 can run natively on Apple Silicon or on Intel, you must compile your .NET assemblies for AnyCPU, and any native binaries need to be compiled as a [Universal Binary](https://developer.apple.com/documentation/apple-silicon/building-a-universal-macos-binary). Rhino 9 on Mac runs only on Apple Silicon (arm64) — Intel Macs are no longer supported. Even so, targeting AnyCPU is still recommended, especially for code shared across platforms and Rhino versions, so the same assemblies continue to run everywhere without per-architecture builds. If your plugin uses any unavailable or non-working APIs when running in .NET Core, some code changes may be necessary. This is especially true with many 3rd party libraries that have different versions of their library for .NET 4.8 and .NET Core. The compat report will show you which APIs and libraries you need to avoid or update. If you [multi-target your project](https://learn.microsoft.com/en-us/nuget/create-packages/multiple-target-frameworks-project-file) to .NET 4.8, .NET 8.0 and .NET 10.0, you can easily find and resolve compatibility issues during compilation. ## Developing on Windows You can develop Rhino plugins on Windows with either [Visual Studio Code](https://code.visualstudio.com/) or Visual Studio. - For **Rhino 8**, use **Visual Studio 2022** or Visual Studio Code. - For **Rhino 9**, use **Visual Studio 2026** or Visual Studio Code. Visual Studio 2026 also works for Rhino 8. To get started, follow the [Your First Plugin (Windows)](/guides/rhinocommon/your-first-plugin-windows/) guide. ### Debugging .NET Core on Windows Visual Studio determines the debugging runtime by the project's target framework, so you need to [multi-target your project](https://learn.microsoft.com/en-us/nuget/create-packages/multiple-target-frameworks-project-file) to debug in .NET Core. This is the recommended approach going forward. With Visual Studio Code, use the `coreclr` debugger type from the C# Dev Kit extension. Our [project templates](https://github.com/mcneel/RhinoVisualStudioExtensions?tab=readme-ov-file#rhinocommon-visual-studio-extensions) automatically create a multi-targeted plugin, create launch configurations for both VS and VS Code, and have options to include a yak packaging step for multiple Rhino versions. ## Developing on Mac On Mac, you will need to use [Visual Studio Code](https://code.visualstudio.com/). Follow the [Your First Plugin (Mac)](/guides/rhinocommon/your-first-plugin-mac/#setting-up-debug) ## Discussions Jump over to our [developer discourse channel](https://discourse.mcneel.com/c/rhino-developer/3) to ask questions regarding the move to .NET Core. -------------------------------------------------------------------------------- # NURBS Geometry Overview Source: https://developer.rhino3d.com/en/guides/opennurbs/nurbs-geometry-overview/ This guide is brief overview of NURBS geometry from a mathematical and technical perspective. ## What is NURBS Geometry? NURBS curves and surfaces behave in similar ways and share terminology. Since curves are easiest to describe, we will cover them in detail. A NURBS curve is defined by four things: degree, control points, knots, and an evaluation rule. ## Degree The degree is a positive whole number. This number is usually 1, 2, 3 or 5, but can be any positive whole number. NURBS lines and polylines are usually degree 1, NURBS circles are degree 2, and most free-form curves are degree 3 or 5. Sometimes the terms linear, quadratic, cubic, and quintic are used. Linear means degree 1, quadratic means degree 2, cubic means degree 3, and quintic means degree 5. You may see references to the order of a NURBS curve. The order of a NURBS curve is positive whole number equal to (degree+1). Consequently, the degree is equal to order-1. It is possible to increase the degree of a NURBS curve and not change its shape. Generally, it is not possible to reduce a NURBS curve’s degree without changing its shape. ## Control Points The control points are a list of at least degree+1 points. One of easiest ways to change the shape of a NURBS curve is to move its control points. The control points have an associated number called a weight. With a few exceptions, weights are positive numbers. When a curve’s control points all have the same weight (usually 1), the curve is called non-rational, otherwise the curve is called rational. The R in NURBS stands for rational and indicates that a NURBS curve has the possibility of being rational. In practice, most NURBS curves are non-rational. A few NURBS curves, circles and ellipses being notable examples, are always rational. ## Knots The knots are a list of degree+N-1 numbers, where N is the number of control points. Sometimes this list of numbers is called the knot vector. In this term, the word vector does not mean 3‑D direction. This list of knot numbers must satisfy several technical conditions. The standard way to ensure that the technical conditions are satisfied is to require the numbers to stay the same or get larger as you go down the list and to limit the number of duplicate values to no more than the degree. For example, for a degree 3 NURBS curve with 11 control points, the list of numbers 0,0,0,1,2,2,2,3,7,7,9,9,9 is a satisfactory list of knots. The list 0,0,0,1,2,2,2,2,7,7,9,9,9 is unacceptable because there are four 2s and four is larger than the degree. The number of times a knot value is duplicated is called the knot’s multiplicity. In the preceding example of a satisfactory list of knots, the knot value 0 has multiplicity three, the knot value 1 has multiplicity one, the knot value 2 has multiplicity three, the knot value 3 has multiplicity one, the knot value 7 has multiplicity two, and the knot value 9 has multiplicity three. A knot value is said to be a full-multiplicity knot if it is duplicated degree many times. In the example, the knot values 0, 2, and 9 have full multiplicity. A knot value that appears only once is called a simple knot. In the example, the knot values 1 and 3 are simple knots. If a list of knots starts with a full multiplicity knot, is followed by simple knots, terminates with a full multiplicity knot, and the values are equally spaced, then the knots are called uniform. For example, if a degree 3 NURBS curve with 7 control points has knots 0,0,0,1,2,3,4,4,4, then the curve has uniform knots. The knots 0,0,0,1,2,5,6,6,6 are not uniform. Knots that are not uniform are called non‑uniform. The N and U in NURBS stand for non‑uniform and indicate that the knots in a NURBS curve are permitted to be non-uniform. Duplicate knot values in the middle of the knot list make a NURBS curve less smooth. At the extreme, a full multiplicity knot in the middle of the knot list means there is a place on the NURBS curve that can be bent into a sharp kink. For this reason, some designers like to add and remove knots and then adjust control points to make curves have smoother or kinkier shapes. Since the number of knots is equal to (N+degree‑1), where N is the number of control points, adding knots also adds control points and removing knots removes control points. Knots can be added without changing the shape of a NURBS curve. In general, removing knots will change the shape of a curve. ## Knots & Control Points A common misconception is that each knot is paired with a control point. This is true only for degree 1 NURBS (polylines). For higher degree NURBS, there are groups of 2 x degree knots that correspond to groups of degree+1 control points. For example, suppose we have a degree 3 NURBS with 7 control points and knots 0,0,0,1,2,5,8,8,8. The first four control points are grouped with the first six knots. The second through fifth control points are grouped with the knots 0,0,1,2,5,8. The third through sixth control points are grouped with the knots 0,1,2,5,8,8. The last four control points are grouped with the last six knots. Some modelers that use older algorithms for NURBS evaluation require two extra knot values for a total of degree+N+1 knots. When Rhino is exporting and importing NURBS geometry, it automatically adds and removes these two superfluous knots as the situation requires. ## Evaluation Rule A curve evaluation rule is a mathematical formula that takes a number and assigns a point. The NURBS evaluation rule is a formula that involves the degree, control points, and knots. In the formula there are some things called B-spline basis functions. The B and S in NURBS stand for “basis spline.” The number the evaluation rule starts with is called a parameter. You can think of the evaluation rule as a black box that eats a parameter and produces a point location. The degree, knots, and control points determine how the black box works. ## References The following are references to fundamental work on NURBS: - Bohm, Wolfgang, Gerald Farin, Jurgen Kahman (1984). *A survey of curve and surface methods in CAGD*, Computer Aided Geometric Design Vol 1. 1-60 - DeBoor, Carl. (1978). *A Practical Guide To Splines*, Springer Verlag; ISBN: 0387953663 - Farin, Gerald. (1997). *Curves and Surfaces for Computer-Aided Geometric Design: A Practical Guide*, 4th edition, Academic Press; ISBN: 0122490541 ## Related Topics - [Non-uniform rational B-spline (WikiPedia)](https://en.wikipedia.org/wiki/Non-uniform_rational_B-spline) -------------------------------------------------------------------------------- # Object Types Source: https://developer.rhino3d.com/en/guides/cpp/object-types/ This guide is an overview of the Rhino geometric object types. ## Overview `CRhinoObject` is the base class for all runtime Rhino geometric objects. `ON_Geometry` is the base class for all geometry class. All `CRhinoObject` derived object maintain a `ON_Geometry` derived class as a data member. The following is an inventory of the geometric objects you are most likely to encounter in Rhino: - `CRhinoAnnotationObject` - Virtual base class for annotation objects. - `ON_Annotation2` - `CRhinoAngularDimension` - Angular dimension. - `ON_AngularDimension2` - `CRhinoAnnotationLeader` - Annotation leader. - `ON_Leader2` - `CRhinoLinearDimension` - Linear dimension. - `ON_LinearDimension2` - `CRhinoOrdinateDimension` - Ordinate dimension. - `ON_OrdinateDimension2` - `CRhinoRadialDimension` - Radial dimension. - `ON_RadialDimension2` - `CRhinoAnnotationText` - Annotation text. - `ON_TextEntity2` - `CRhinoBrepObject` - Surface or polysurface. - `ON_Brep` - `CRhinoClippingPlaneObject` - Clipping plane. - `ON_ClippingPlane` - `CRhinoCurveObject` - Curve. - `ON_Curve` - Virtual base class for curve objects. - `ON_ArcCurve` - `ON_CurveOnSurface` - `ON_CurveProxy` - `ON_LineCurve` - `ON_NurbsCurve` - `ON_PolyCurve` - `ON_PolylineCurve` - `CRhinoDetailViewObject` - Detail view. - `ON_DetailView` - `CRhinoExtrusionObject` - Lightweight extrusion - `ON_Extrusion` - `CRhinoGripObject` - Grip object. Note grip objects are not stored in the document. - `ON_Point` - `CRhinoHatch` - Area hatching. - `ON_Hatch` - `CRhinoInstanceObject` - Block instance. - `ON_InstanceRef` - `CRhinoLight` - Rendering light. - `ON_Light` - `CRhinoMeshObject` - Polygon mesh. - `ON_Mesh` - `CRhinoPointCloudObject` - Point cloud. - `ON_PointCloud` - `CRhinoPointObject` - Point. - `ON_Point` - `CRhinoSurfaceObject` - Untrimmed surface. - `ON_Surface` - `ON_NurbsSurface` - `ON_PlaneSurface` - `ON_RevSurface` - `ON_SumSurface` - `CRhinoTextDot` - Text dot. - `ON_TextDot` -------------------------------------------------------------------------------- # Python Data Types Source: https://developer.rhino3d.com/en/guides/rhinopython/python-datatypes/ This guide is an overview of Python Data Types. ## Overview Python has five standard Data Types: * [Numbers](#numbers) * [String](#string) * [List](#list) * [Tuple](#tuple) * [Dictionary](#dictionary) Python sets the variable type based on the value that is assigned to it. Unlike more riggers languages, Python will change the variable type if the variable value is set to another value. For example: ```python var = 123 # This will create a number integer assignment var = 'john' # the `var` variable is now a string type. ``` ## Numbers Python numbers variables are created by the standard Python method: ```python var = 382 ``` Most of the time using the standard Python number type is fine. Python will automatically convert a number from one type to another if it needs. But, under certain circumstances that a specific number type is needed (ie. complex, hexidecimal), the format can be forced into a format by using additional syntax in the table below: | Type | | |Format | | | Description | |:--------|:-:|:-:|:-|:-:|:-:|:--------| | int | | | a = 10 | | | Signed Integer | | long | | | a = 345L | | | (L) Long integers, they can also be represented in octal and hexadecimal | | float | | | a = 45.67 | | | (.) Floating point real values | | complex | | | a = 3.14J | | | (J) Contains integer in the range 0 to 255. | Most of the time Python will do variable conversion automatically. You can also use Python conversion functions [(int(), long(), float(), complex())](https://docs.python.org/2/library/stdtypes.html#id2) to convert data from one type to another. In addition, the `type` function returns information about how your data is stored within a variable. ```python message = "Good morning" num = 85 pi = 3.14159 print(type(message)) # This will return a string print(type(num)) # This will return an integer print(type(pi)) # This will return a float ``` ## String Create string variables by enclosing characters in quotes. Python uses single quotes `'` double quotes `"` and triple quotes `"""` to denote literal strings. Only the triple quoted strings `"""` also will automatically continue across the end of line statement. ```python firstName = 'john' lastName = "smith" message = """This is a string that will span across multiple lines. Using newline characters and no spaces for the next lines. The end of lines within this string also count as a newline when printed""" ``` Strings can be accessed as a whole string, or a substring of the complete variable using brackets '[]'. Here are a couple examples: ```python var1 = 'Hello World!' var2 = 'RhinoPython' print (var1[0]) # this will print the first character in the string an `H` print (var2[1:5]) # this will print the substring 'hinoP` ``` Python can use a special syntax to format multiple strings and numbers. The string formatter is quickly covered here because it is seen often and it is important to recognize the syntax. ```python print ("The item {} is repeated {} times".format(element,count)) ``` The `{}` are placeholders that are substituted by the variables `element` and `count` in the final string. This compact syntax is meant to keep the code more readable and compact. Python is currently transitioning to the format syntax above, but python can use an older syntax, which is being phased out, but is still seen in some example code: ```python print ("The item %i is repeated %i times"% (element,count)) ``` For more information on the string formatter in Python see the article: [PyFormat Website](https://pyformat.info/) For a more detailed look at string variables in Python, see the [Tutorialspoint Python Tutorial on stings.](https://www.tutorialspoint.com/python/python_strings.htm) Note: this article does use the older formatting syntax, but has a lot useful information. ## List Lists are a very useful variable type in Python. A list can contain a series of values. List variables are declared by using brackets `[ ]` following the variable name. ```python A = [ ] # This is a blank list variable B = [1, 23, 45, 67] # this list creates an initial list of 4 numbers. C = [2, 4, 'john'] # lists can contain different variable types. ``` All lists in Python are zero-based indexed. When referencing a member or the length of a list the number of list elements is always the number shown plus one. ```python mylist = ['Rhino', 'Grasshopper', 'Flamingo', 'Bongo'] B = len(mylist) # This will return the length of the list which is 3. The index is 0, 1, 2, 3. print (mylist[1]) # This will return the value at index 1, which is 'Grasshopper' print (mylist[0:2]) # This will return the first 3 elements in the list. ``` You can assign data to a specific element of the list using an index into the list. The list index starts at zero. Data can be assigned to the elements of an array as follows: ```python mylist = [0, 1, 2, 3] mylist[0] = 'Rhino' mylist[1] = 'Grasshopper' mylist[2] = 'Flamingo' mylist[3] = 'Bongo' print (mylist[1]) ``` Lists aren't limited to a single dimension. Although most people can't comprehend more than three or four dimensions. You can declare multiple dimensions by separating an with commas. In the following example, the MyTable variable is a two-dimensional array : ```python MyTable = [[], []] ``` In a two-dimensional array, the first number is always the number of rows; the second number is the number of columns. For a detailed look at managing lists, take a look at the article - [Python Lists - Google developer](https://developers.google.com/edu/python/lists) - [TutorialPoint Python Lists](https://www.tutorialspoint.com/python/python_lists.htm) ## Tuple Tuples are a group of values like a list and are manipulated in similar ways. But, tuples are fixed in size once they are assigned. In Python the fixed size is considered immutable as compared to a list that is dynamic and mutable. Tuples are defined by parenthesis (). ```python myGroup = ('Rhino', 'Grasshopper', 'Flamingo', 'Bongo') ``` Here are some advantages of tuples over lists: 1. Elements to a tuple. Tuples have no append or extend method. 2. Elements cannot be removed from a tuple. 3. You can find elements in a tuple, since this doesn’t change the tuple. 4. You can also use the in operator to check if an element exists in the tuple. 5. Tuples are faster than lists. If you're defining a constant set of values and all you're ever going to do with it is iterate through it, use a tuple instead of a list. 6. It makes your code safer if you “write-protect” data that does not need to be changed. It seems tuples are very restrictive, so why are they useful? There are many datastructures in Rhino that require a fixed set of values. For instance a Rhino point is a list of 3 numbers `[34.5, 45.7, 0]`. If this is set as tuple, then you can be assured the original 3 number structure stays as a point `(34.5, 45.7, 0)`. There are other datastructures such as lines, vectors, domains and other data in Rhino that also require a certain set of values that do not change. Tuples are great for this. For more information on Tuples, see the [TutorialPoint Python Tutorial on Tuples](https://www.tutorialspoint.com/python/python_tuples.htm) and the [Dive in Python section on tuples](http://getpython3.com/diveintopython3/native-datatypes.html#tuples). ## Dictionary Dictionaries in Python are lists of `Key`:`Value` pairs. This is a very powerful datatype to hold a lot of related information that can be associated through `keys`. The main operation of a dictionary is to extract a value based on the `key` name. Unlike lists, where index numbers are used, dictionaries allow the use of a `key` to access its members. Dictionaries can also be used to sort, iterate and compare data. Dictionaries are created by using braces ({}) with pairs separated by a comma (,) and the key values associated with a colon(:). In Dictionaries the `Key` must be unique. Here is a quick example on how dictionaries might be used: ```python room_num = {'john': 425, 'tom': 212} room_num['john'] = 645 # set the value associated with the 'john' key to 645 print (room_num['tom']) # print the value of the 'tom' key. room_num['isaac'] = 345 # Add a new key 'isaac' with the associated value print (room_num.keys()) # print out a list of keys in the dictionary print ('isaac' in room_num) # test to see if 'issac' is in the dictionary. This returns true. ``` Dictionaries can be more complex to understand, but they are great to store data that is easy to access. To find out more about using dictionaries see the [Python Fundamentals - Dictionaries](/guides/rhinopython/python-dictionaries/) ## Related topics - [What are Python and RhinoScript?](/guides/rhinopython/what-is-rhinopython) - [Python Variables](/guides/rhinopython/python-variables/) - [Python Dictionaries](/guides/rhinopython/python-dictionaries/) -------------------------------------------------------------------------------- # Python Variables Source: https://developer.rhino3d.com/en/guides/rhinopython/python-variables/ This guide provides an overview of Python variables. ## Overview A variable is a convenient placeholder that refers to a computer memory location where you can store program information that may change during the time your script is running. For example, you might create a variable called ClickCount to store the number of times a user performs a certain operation. When a variable is stored in memory, the interpreter will allocate a certain amount of space for each variable type. Where the variable is stored in computer memory is unimportant. What is important is that you know that a variable has a type, and you refer to a variable by name to see or change its value. In Python, variables are always one of the five fundamental data types: * [Numbers](/guides/rhinopython/python-datatypes/#numbers) * [String](/guides/rhinopython/python-datatypes/#string) * [List](/guides/rhinopython/python-datatypes/#list) * [Tuple](/guides/rhinopython/python-datatypes/#tuple) * [Dictionary](/guides/rhinopython/python-datatypes/#dictionary) For a detailed look at each variable type see [Python Variable Types](/guides/rhinopython/python-datatypes/) in this guide. While each variable has its own properties and methods, there are common methods we use to deal with all variables in Python. ## Declaration In Python, variables are created the first time a value is assigned to them. For example: ```python number = 10 string = "This is a string" ``` You declare multiple variables by separating each variable name with a comma. For example: ```python a, b = True, False ``` This is the same the multiple line declaration of: ```python a = True b = False ``` ## Naming Restrictions Variable names follow the standard rules for naming anything in Python. A variable name: - Must begin with an alphabetic character (A -Z) or an underscore (\_). - Cannot contain a period(.), @, $, or %. - Must be unique in the scope in which it is declared. - Python is case sensitive. So "selection" and " Selection" are two different variables. Best practices for all Python naming can be found in the (Style Guide for Python Naming Conventions)[https://www.python.org/dev/peps/pep-0008/#naming-conventions] ## Scope & Lifetime Scope of a variable defines where that variable can be accessed in your code. For instance a `global` variable can be accessed from anywhere in your code. A `local` variable can only be accessed within the function it was declared in. Generally a variable's scope is determined by where you declare it. When you declare a variable within a procedure, only code within that procedure can access or change the value of that variable. It has local scope and is a procedure-level variable. If you declare a variable outside a procedure, you make it recognizable to all the procedures in your script. This is a global variable, and it has global scope. Here are few examples: ```python global_var = True def function1(): local_var = False print (global_var) print (local_var) function1() # this runs the function print (global_var) # this works because global_var is accessible print (local_var) # this gives an error because we are outside function1 ``` It is important to be careful when declaring variables. It is easy to create duplicate variable names that do not reference the correct values. For instance do not declare a global variable this way: ```python g_var = 'True' def function2(): g_var = 'False' print ('inside the function var is ', g_var) print ('outside the function var is ', g_var) ``` The example above will create a `Global` variable named `g_var`. When dropping in the `function2` function, there will be a second `local` variable created named `g_var` with a different value. The proper way to work with a global variable is to be very explicit with the `global` statement in the `local` scope: ```python g_var = "Global" def function2(): g_var = "Local" print ('inside the function var is ', g_var) return; function2() print ('outside the function var is ', g_var) ``` For more scope example see the (Notes on Python Variables)[http://www.saltycrane.com/blog/2008/01/python-variable-scope-notes/] The lifetime of a variable depends on how long it exists. The lifetime of a `global` variable extends from the time it is declared until the time the script is finished running. At procedure level, a variable exists only as long as you are in the procedure. When the procedure exits, the variable is destroyed. Local variables are ideal as temporary storage space when a procedure is executing. You can have local variables of the same name in several different procedures because each is recognized only by the procedure in which it is declared. ## Assigning Values Values are assigned to variables creating an expression as follows: the variable is on the left side of the expression and the value you want to assign to the variable is on the right. For example: ```python B = 200 ``` The same value can be assigned to multiple variables at the same time: ```python a = b = c = 1 ``` And multiple variables can be assigned different values on a single line: ```python a, b, c = 1, 2, "john" ``` This is the same as: ```python a = 1 b = 2 c = "john" ``` ## Scalar Variables & Lists Much of the time, you only want to assign a single value to a variable you have declared. A variable containing a single value is a scalar variable. Other times, it is convenient to assign more than one related value to a single variable. Then you can create a variable that can contain a series of values. This is called an list variable. List variables and scalar variables are declared in the same way, except that the declaration of an array variable uses brackets `[ ]` following the variable name. ```python A = [ ] # This is a blank list variable B = [1, 23, 45, 67] # this list creates an initial list of 4 numbers. C = [2, 4, 'john'] # lists can contain different variable types. ``` For a detailed look at managing lists, take a look at the the [Python List Datatype Article](/guides/rhinopython/python-datatypes/#list) ## Related topics - [VBScript Data Types](/guides/rhinoscript/vbscript-datatypes/) - [VBScript Procedures](/guides/rhinoscript/vbscript-procedures/) -------------------------------------------------------------------------------- # Render Engine Integration - ChangeQueue Source: https://developer.rhino3d.com/en/guides/rhinocommon/render-engine-integration-changequeue/ This guide, the third of a series, discusses using the ChangeQueue to digest file content for a render engine. ## Overview This is part three in the series on render engine integration in Rhinoceros 3D using RhinoCommon (v6). 1. [Setting up the plug-in](/guides/rhinocommon/render-engine-integration-introduction/) 1. [Modal Rendering](/guides/rhinocommon/render-engine-integration-modal/) 1. ChangeQueue (this guide) 1. [Interactive render - viewport integration](/guides/rhinocommon/render-engine-integration-interactive-viewport/) 1. Preview render *(forthcoming)* If you have not already read parts [one]((/guides/rhinocommon/render-engine-integration-introduction/)) and [two]((/guides/rhinocommon/render-engine-integration-modal/)), please do so before proceeding. ## Converting the Document When it comes to converting a 3dm document to a form our render engine understands there are two options: the hard way, or the easy way. The hard way would be to just take the `RhinoDoc` given in the `Render()` function and then iterate over the many different tables with `DocObjects` and do the conversion then and there. For geometry and render content it probably won't be too hard, but when it comes to Blocks and Block instances it all becomes quite complex very quickly. Since I don't like complex I'll just go with the easy way. The [code for this plug-in version](https://github.com/mcneel/rhino-developer-samples/tree/6/rhinocommon/cs/SampleCsRendererIntegration/MockingBird/MockingBirdChangeQueue) can be found at [the MockingBird Git repository](https://github.com/mcneel/rhino-developer-samples/tree/6/rhinocommon/cs/SampleCsRendererIntegration/MockingBird). ## ChangeQueue The `ChangeQueue` is a central way of getting the 3dm in an already pre-digested format. Among things it will handle Blocks and their instances all for you. The `ChangeQueue` is also usable for both production (modal) rendering and interactive real-time rendering in the viewport. Even preview scene rendering can be done through the `ChangeQueue` mechanism, meaning that preview rendering can be easily added without too much hassle. We'll come to that later though. Setting up the `ChangeQueue` is pretty straight-forward. A class deriving from `Rhino.Render.ChangeQueue.ChangeQueue` is needed and a set off Apply-functions need to be implemented. ```cs public class MockingChangeQueue : ChangeQueue { // for a regular rhino document (i.e. currently // active) // The constructor can look like you want, as long as the plugin ID, // document serial number and view info are given, needed for // the base class public MockingChangeQueue(Guid pluginId, uint docRuntimeSerialNumber, ViewInfo viewinfo) : base(pluginId, docRuntimeSerialNumber, viewinfo) { } /// /// The camera information. /// /// new viewport info protected override void ApplyViewChange(ViewInfo viewInfo) { var vp = viewInfo.Viewport; int near, far; var screenport = vp.GetScreenPort(out near, out far); RhinoApp.WriteLine($"Camera @ {vp.CameraLocation}, direction {vp.CameraDirection}"); RhinoApp.WriteLine($"\twith near {near} and far {far}"); RhinoApp.WriteLine($"\tand {screenport}"); } protected override void ApplyEnvironmentChanges(RenderEnvironment.Usage usage) { // background - when camera ray doesn't hit any geometry // skylight - image-based lighting // reflection - what is seen in reflections var env = EnvironmentForid(EnvironmentIdForUsage(usage)); RhinoApp.WriteLine(env != null ? $"{usage} {env.Name}" : $"No env for {usage}"); // retrieving textures is with RenderMaterial, refer to HandleRenderMaterial() } /// /// Lights in the scene, including any automatic lighting /// (will be CameraDirectional) /// /// List of Lights protected override void ApplyLightChanges(List lightChanges) { foreach (var light in lightChanges) { RhinoApp.WriteLine($"A {light.ChangeType} light. {light.Data.Name}, {light.Data.LightStyle}"); if (light.Data.LightStyle == LightStyle.CameraDirectional) { RhinoApp.WriteLine("Use ChangeQueue.ConvertCameraBasedLightToWorld() to convert light transform to world"); RhinoApp.WriteLine($"\told location {light.Data.Location}, direction {light.Data.Direction}"); ConvertCameraBasedLightToWorld(this, light, GetQueueView()); RhinoApp.WriteLine($"\tnew location {light.Data.Location}, direction {light.Data.Direction}"); } } } /// /// Get all geometry data. /// /// List of Mesh instance IDs /// List of Meshes to add protected override void ApplyMeshChanges(Guid[] deleted, List added) { RhinoApp.WriteLine($"Received {added.Count} new meshes, {deleted.Length} for deletion"); foreach (var m in added) { var totalVerts = 0; var totalFaces = 0; var totalQuads = 0; var meshIndex = 0; RhinoApp.WriteLine($"\t{m.Id()} with {m.GetMeshes().Length} submeshes"); foreach (var sm in m.GetMeshes()) { RhinoApp.WriteLine($"\t\tmesh index {meshIndex} mesh with {sm.Vertices.Count} verts, {sm.Faces.Count} faces ({sm.Faces.QuadCount} quads)."); totalVerts += sm.Vertices.Count; totalFaces += sm.Faces.Count; totalQuads += sm.Faces.QuadCount; RhinoApp.WriteLine($"\t\tFor material we remember ({m.Id()},{meshIndex}) as identifier. Connect dots in ApplyMeshInstanceChanged"); meshIndex++; } RhinoApp.WriteLine($"\t{totalVerts} verts, {totalFaces} faces (of which {totalQuads} quads)"); } } /// /// Mesh instances added or deleted. Mesh instances here really means the /// objects in a scene. More than one object can reference the same geometry. /// For a single-shot render (production render) this is also where /// materials for the scene are provided. /// /// Objects to delete, a list of unsigned ints /// List of MeshInstances (objects) protected override void ApplyMeshInstanceChanges(List deleted, List addedOrChanged) { RhinoApp.WriteLine($"Received {addedOrChanged.Count} mesh instances to be either added or changed"); foreach (var mi in addedOrChanged) { var mat = MaterialFromId(mi.MaterialId); RhinoApp.WriteLine($"\tAdd or change object {mi.InstanceId} uses mesh <{mi.MeshId}, {mi.MeshIndex}>, and material {mi.MaterialId}, named {mat.Name})"); HandleRenderMaterial(mat); } // For single-shot rendering there won't be deletions. } private void HandleRenderMaterial(RenderMaterial material) { RhinoApp.WriteLine($"\t\tMaterial {material.Name} is a {material.TypeName} ({material.TypeDescription})"); var diffchan = material.TextureChildSlotName(RenderMaterial.StandardChildSlots.Diffuse); var difftex = material.FindChild(diffchan) as RenderTexture; if (difftex != null) { RhinoApp.WriteLine($"\t\t\ta diffuse texture was found {difftex.Name}, hash {difftex.RenderHashWithoutLocalMapping}"); RhinoApp.WriteLine($"\t\t\tprojection {difftex.GetProjectionMode()}, env mapping {difftex.GetInternalEnvironmentMappingMode()}"); RhinoApp.WriteLine($"\t\t\tlocal mapping xform {difftex.LocalMappingTransform}"); var texeval = difftex.CreateEvaluator(RenderTexture.TextureEvaluatorFlags.DisableLocalMapping); int u, v, w; difftex.PixelSize(out u, out v, out w); // for procedural textures there's no u/v/w, so check for that and set // to some acceptable defaults. if (u == 0) u = 1024; if (v == 0) v = 1024; if (w == 0) w = 1; RhinoApp.WriteLine($"\t\t\tTexture size {u}x{v}x{w}"); } } } ``` The function in the given snippet are the most basic apply functions one should need to integrate a production render engine. The functions all have been provided with clear documenting comments. An instance of this new `MockingChangeQueue` will be created when the `MockingRenderContext` is instantiated. ```cs public MockingChangeQueue ChangeQueue { get; private set; } public MockingRenderContext(PlugIn plugIn, RhinoDoc doc) { // set up view info ViewInfo viewInfo = new ViewInfo(doc.Views.ActiveView.ActiveViewport); ChangeQueue = new MockingChangeQueue(plugIn.Id, doc.RuntimeSerialNumber, viewInfo); } ``` To use this new `ChangeQueue` the `Render()`function needs to be adapted slightly - the `CreateWorld()` function on our `MockingChangeQueue` instance should be called once on the main thread. This is to ensure proper functionality of the `ChangeQueue` mechanism. ```cs protected override Result Render(RhinoDoc doc, RunMode mode, bool fastPreview) { // initialise our render context MockingRenderContext rc = new MockingRenderContext(this, doc); // initialise our pipeline implementation RenderPipeline pipeline = new MockingRenderPipeline(doc, mode, this, rc); // query for render resolution var renderSize = RenderPipeline.RenderSize(doc); // set up view info ViewInfo viewInfo = new ViewInfo(doc.Views.ActiveView.ActiveViewport); // set up render window rc.RenderWindow = pipeline.GetRenderWindow(); // add a wireframe channel for curves/wireframes/annotation etc. rc.RenderWindow.AddWireframeChannel(doc, viewInfo.Viewport, renderSize, new Rectangle(0, 0, renderSize.Width, renderSize.Height)); // set correct size rc.RenderWindow.SetSize(renderSize); // prime the ChangeQueue. We do it here, since this *has* to // happen on the main thread. rc.ChangeQueue.CreateWorld(); // now fire off render thread. var renderCode = pipeline.Render(); // note that the rendering isn't complete yet, rather the pipeline.Render() // call starts a rendering thread. Here we essentially check whether // starting that thread went ok. if (renderCode != RenderPipeline.RenderReturnCode.Ok) { RhinoApp.WriteLine("Rendering (mockingbird modal+changequeue) failed:" + rc.ToString()); return Result.Failure; } // all ok, so we are apparently rendering. return Result.Success; } ``` Loading the 3dm test file and running the `_Render` command will yield output something like: ```sh Command: _Render Camera @ 43.1114947706824,-74.6734897875561,49.7823265250374, direction -43.3,75,-50 with near 0 and far 1 and {X=0,Y=0,Width=781,Height=387} No env for Background Skylighting Studio ReflectionAndRefraction Studio Received 2 new meshes, 0 for deletion 0c35babb-ca2a-4b09-9ab6-72610f38717f with 6 submeshes mesh index 0 mesh with 4 verts, 2 faces (0 quads). For material we remember (0c35babb-ca2a-4b09-9ab6-72610f38717f,0) as identifier. Connect dots in ApplyMeshInstanceChanged mesh index 1 mesh with 4 verts, 2 faces (0 quads). For material we remember (0c35babb-ca2a-4b09-9ab6-72610f38717f,1) as identifier. Connect dots in ApplyMeshInstanceChanged mesh index 2 mesh with 4 verts, 2 faces (0 quads). For material we remember (0c35babb-ca2a-4b09-9ab6-72610f38717f,2) as identifier. Connect dots in ApplyMeshInstanceChanged mesh index 3 mesh with 4 verts, 2 faces (0 quads). For material we remember (0c35babb-ca2a-4b09-9ab6-72610f38717f,3) as identifier. Connect dots in ApplyMeshInstanceChanged mesh index 4 mesh with 4 verts, 2 faces (0 quads). For material we remember (0c35babb-ca2a-4b09-9ab6-72610f38717f,4) as identifier. Connect dots in ApplyMeshInstanceChanged mesh index 5 mesh with 4 verts, 2 faces (0 quads). For material we remember (0c35babb-ca2a-4b09-9ab6-72610f38717f,5) as identifier. Connect dots in ApplyMeshInstanceChanged 24 verts, 12 faces (of which 0 quads) 47bcaad4-c0c0-461b-be76-42a312a566db with 1 submeshes mesh index 0 mesh with 9557 verts, 9944 faces (8136 quads). For material we remember (47bcaad4-c0c0-461b-be76-42a312a566db,0) as identifier. Connect dots in ApplyMeshInstanceChanged 9557 verts, 9944 faces (of which 8136 quads) Received 7 mesh instances to be either added or changed Add or change object 4184240262 uses mesh <0c35babb-ca2a-4b09-9ab6-72610f38717f, 0>, and material 2763457527, named Custom 001) Material Custom 001 is a Custom (Custom material.) a diffuse texture was found 3D Checker Texture 001, hash 2924537820 projection MappingChannel, env mapping Automatic local mapping xform R0=(1,0,0,0), R1=(0,1,0,0), R2=(0,0,1,0), R3=(0,0,0,1) Texture size 1024x1024x1 Add or change object 1104812003 uses mesh <0c35babb-ca2a-4b09-9ab6-72610f38717f, 1>, and material 1789231709, named Plaster 001) Material Plaster 001 is a Plaster (Plaster material.) Add or change object 2271356598 uses mesh <47bcaad4-c0c0-461b-be76-42a312a566db, 0>, and material 2348445746, named Metal 001) Material Metal 001 is a Metal (Metal material.) Add or change object 3468198068 uses mesh <0c35babb-ca2a-4b09-9ab6-72610f38717f, 5>, and material 2304042105, named Gem 001) Material Gem 001 is a Gem (Gem material.) Add or change object 1980032977 uses mesh <0c35babb-ca2a-4b09-9ab6-72610f38717f, 4>, and material 2304042105, named Gem 001) Material Gem 001 is a Gem (Gem material.) Add or change object 1399830541 uses mesh <0c35babb-ca2a-4b09-9ab6-72610f38717f, 2>, and material 2304042105, named Gem 001) Material Gem 001 is a Gem (Gem material.) Add or change object 3956531048 uses mesh <0c35babb-ca2a-4b09-9ab6-72610f38717f, 3>, and material 2304042105, named Gem 001) Material Gem 001 is a Gem (Gem material.) ``` ## Next Steps This is part three in the series on render engine integration in Rhinoceros using RhinoCommon. The next guide is [Render Engine Integration - Interactive Viewport](/guides/rhinocommon/render-engine-integration-interactive-viewport/). ## Related Topics - [Render Engine Integration - Introduction](/guides/rhinocommon/render-engine-integration-introduction/) - [Render Engine Integration - Modal](/guides/rhinocommon/render-engine-integration-modal/) - [Render Engine Integration - Interactive Viewport](/guides/rhinocommon/render-engine-integration-interactive-viewport/). - Preview render *(forthcoming)* -------------------------------------------------------------------------------- # Simple Geometry Component Source: https://developer.rhino3d.com/en/guides/grasshopper/simple-geometry-component/ This guide demonstrates how to use some of the simpler geometry types and classes in RhinoCommon & Grasshopper. We'll discuss how to deal with different access levels of input data and invalid Structs vs. invalid and null Classes. ## Overview This component will perform a simple Circle-Line Split operation. We'll retrieve a single Circle and a single Line input, make sure the data is valid, project the line onto the circle plane, determine whether or not the Split operation is valid and then output the two arcs on either side of the slicing line. ## Prerequisites We will not be dealing with any of the basics of component development. Please make sure you have read the [Your First Component](/guides/grasshopper/your-first-component-windows), [Simple Component](/guides/grasshopper/simple-component), and [Simple Mathematics Component](/guides/grasshopper/simple-mathematics-component) guides before starting this one. Before you start, create a new class that derives from `Grasshopper.Kernel.GH_Component`, as outlined in the [Simple Component](/guides/grasshopper/simple-component) guide. ## Input Parameters This part of the component is very similar to the [Simple Mathematics Component](/guides/grasshopper/simple-mathematics-component), except this time there will be no default values. ```cs ... protected override void RegisterInputParams(GH_Component.GH_InputParamManager pManager) { pManager.AddCircleParameter("Circle", "C", "The circle to slice", GH_ParamAccess.item); pManager.AddLineParameter("Line", "L", "Slicing line", GH_ParamAccess.item); } ... ``` ```vbnet ... Protected Overrides Sub RegisterInputParams(ByVal pManager As GH_Component.GH_InputParamManager) pManager.AddCircleParameter("Circle", "C", "The circle to slice", GH_ParamAccess.item) pManager.AddLineParameter("Line", "L", "Slicing line", GH_ParamAccess.item) End Sub ... ``` The first parameter is of type `Param_Circle` and the data it contains will consist solely of `GH_Circle`. `GH_Circle` is a class that wraps the `Rhino.Geometry.Circle` structure. It provides methods that allow Grasshopper to incorporate RhinoCommon circles into the default GUI. These methods include Archiving, Previewing, Baking and Casting (Converting) functions. However, when accessing data inside a `Param_Circle` parameter, you are not limited to the `GH_Circle` type, as we shall see. The second parameter is of type `Param_Line` and it works very similar to the `Param_Circle` type discussed above. ## Output Parameters Our component will output two arcs on success, or nulls on failure. ```cs ... protected override void RegisterOutputParams(GH_Component.GH_OutputParamManager pManager) { pManager.AddArcParameter("Arc A", "A", "First Split result.", GH_ParamAccess.item); pManager.AddArcParameter("Arc B", "B", "Second Split result.", GH_ParamAccess.item); } ... ``` ```vbnet ... Protected Overrides Sub RegisterOutputParams(ByVal pManager As GH_Component.GH_OutputParamManager) pManager.AddArcParameter("Arc A", "A", "First Split result.", GH_ParamAccess.item) pManager.AddArcParameter("Arc B", "B", "Second Split result.", GH_ParamAccess.item) End Sub ... ``` ## Solve Instance The `SolveInstance()` implementation for this component is responsible for the following steps: 1. Declare placeholder variables for the input data. 1. Retrieve input data. 1. Test input data for validity. 1. Project line segment onto circle plane. 1. Test projected segment for validity. 1. Solve intersections for circle and projected segment. 1. Abort on insufficient intersections. 1. Create the slice arcs. 1. Assign arcs to the output parameters. Steps 2 and 9 however can be approached from different directions. First I'll show you the recommended approach and then we'll have a look at the alternatives: ```cs ... protected override void SolveInstance(IGH_DataAccess DA) { // 1. Declare placeholder variables and assign initial invalid data. // This way, if the input parameters fail to supply valid data, we know when to abort. Rhino.Geometry.Circle circle = Rhino.Geometry.Circle.Unset; Rhino.Geometry.Line line = Rhino.Geometry.Line.Unset; // 2. Retrieve input data. if (!DA.GetData(0, ref circle)) { return; } if (!DA.GetData(1, ref line)) { return; } // 3. Abort on invalid inputs. if (!circle.IsValid) { return; } if (!line.IsValid) { return; } // 4. Project line segment onto circle plane. line.Transform(Rhino.Geometry.Transform.PlanarProjection(circle.Plane)); // 5. Test projected segment for validity. if (line.Length < Rhino.RhinoMath.ZeroTolerance) { AddRuntimeMessage(GH_RuntimeMessageLevel.Error, "Line could not be projected onto the Circle plane"); return; } // 6. Solve intersections and 7. Abort if there are less than two intersections. double t1; double t2; Rhino.Geometry.Point3d p1; Rhino.Geometry.Point3d p2; switch (Rhino.Geometry.Intersect.Intersection.LineCircle(line, circle, out t1, out p1, out t2, out p2)) { case Rhino.Geometry.Intersect.LineCircleIntersection.None: AddRuntimeMessage(GH_RuntimeMessageLevel.Warning, "No intersections were found"); return; case Rhino.Geometry.Intersect.LineCircleIntersection.Single: AddRuntimeMessage(GH_RuntimeMessageLevel.Warning, "Only a single intersection was found"); return; } // 8. Create slicing arcs. double ct; circle.ClosestParameter(p1, out ct); Rhino.Geometry.Vector3d tan = circle.TangentAt(ct); // 9. Assign output arcs. DA.SetData(0, new Rhino.Geometry.Arc(p1, tan, p2)); DA.SetData(1, new Rhino.Geometry.Arc(p1, -tan, p2)); } ... ``` ```vbnet ... Protected Overrides Sub SolveInstance(ByVal DA As Grasshopper.Kernel.IGH_DataAccess) '1. Declare placeholder variables and assign initial invalid data. ' This way, if the input parameters fail to supply valid data, we know when to abort. Dim circle As Rhino.Geometry.Circle = Rhino.Geometry.Circle.Unset Dim line As Rhino.Geometry.Line = Rhino.Geometry.Line.Unset '2. Retrieve input data. If (Not DA.GetData(0, circle)) Then Return If (Not DA.GetData(1, line)) Then Return '3. Abort on invalid inputs. If (Not circle.IsValid) Then Return If (Not line.IsValid) Then Return '4. Project line segment onto circle plane. line.Transform(Rhino.Geometry.Transform.PlanarProjection(circle.Plane)) '5. Test projected segment for validity. If (line.Length < Rhino.RhinoMath.ZeroTolerance) Then AddRuntimeMessage(GH_RuntimeMessageLevel.Error, "Line could not be projected onto the Circle plane") Return End If '6. Solve intersections and 7. Abort if there are less than two intersections. Dim t1 As Double Dim p1 As Rhino.Geometry.Point3d Dim t2 As Double Dim p2 As Rhino.Geometry.Point3d Select Case Rhino.Geometry.Intersect.Intersection.LineCircle(line, circle, t1, p1, t2, p2) Case Rhino.Geometry.Intersect.LineCircleIntersection.None AddRuntimeMessage(GH_RuntimeMessageLevel.Warning, "No intersections were found") Return Case Rhino.Geometry.Intersect.LineCircleIntersection.Single AddRuntimeMessage(GH_RuntimeMessageLevel.Warning, "Only a single intersection was found") Return End Select '8. Create slicing arcs. Dim ct As Double circle.ClosestParameter(p1, ct) Dim tan As Rhino.Geometry.Vector3d = circle.TangentAt(ct) '9. Assign output arcs. DA.SetData(0, New Rhino.Geometry.Arc(p1, tan, p2)) DA.SetData(1, New Rhino.Geometry.Arc(p1, -tan, p2)) End Sub ... ``` As I mentioned before, the first input parameter is of type `Param_Circle` and it contains data of type `GH_Circle`. But when we're accessing the parameter via the `DA.GetData(0, circle)` method, we're using `Rhino.Geometry.Circle` instead of `GH_Circle`. The `DA.GetData()` method is capable of converting data from the intrinsic parameter type into requested types, provided the conversion makes sense. `GH_Circle` to `Rhino.Geometry.Circle` is a perfectly valid conversion, `GH_Circle` to `Rhino.Geometry.Transform` would not be. The Grasshopper Component SDK has been designed on the premise that the bulk of all components that operate on data only care about the data itself, not how it is wrapped up inside the Grasshopper data structures. The conversion routines that translate `GH_Circle` data into `Rhino.Geometry.Circle` data (and obviously also `GH_Number` into `System.Double`, and `GH_Colour` into `System.Drawing.Color` etc.), have been highly optimised and should be used in almost all circumstances. If however you feel the need to get access to the `GH_Circle` data directly, you can retrieve that instance in the same fashion: ```cs ... Grasshopper.Kernel.Data.GH_Circle circle = null; if (!DA.GetData(0, circle)) { return; } ... ``` ```vbnet ... Dim circle As Grasshopper.Kernel.Data.GH_Circle = Nothing If (Not DA.GetData(0, circle)) Then Return ... ``` Note that a single instance of `GH_Circle` may be shared among any number of parameters in Grasshopper, and thus changing one will change data everywhere. If you request `Rhino.Geometry.Circle`, `System.Double` or `System.Drawing.Color` instead of `GH_Circle`, `GH_Number` or `GH_Colour` you won't have to worry about this pitfall since you will always be given an object that has been disassociated from the (potentially shared) wrapper type. Similarly, you are allowed to store output data in different formats as well. Instead of... ```cs DA.SetData(0, new Rhino.Geometry.Arc(p1, tan, p2)); ``` ```vbnet DA.SetData(0, New Rhino.Geometry.Arc(p1, tan, p2)) ``` ...you could also provide an instance of `Grasshopper.Kernel.Types.GH_Arc`: ```cs Grasshopper.Kernel.Types.GH_Arc gh_arc = new Grasshopper.Kernel.Types.GH_Arc(new Rhino.Geometry.Arc(p1, tan, p2)); DA.SetData(0, gh_arc); ``` ```vbnet Dim gh_arc As New Grasshopper.Kernel.Types.GH_Arc(New Rhino.Geometry.Arc(p1, tan, p2)) DA.SetData(0, gh_arc) ``` ## Related Topics - [Your First Component](/guides/grasshopper/your-first-component-windows) - [Simple Component](/guides/grasshopper/simple-component) - [Simple Mathematics Component](/guides/grasshopper/simple-mathematics-component) -------------------------------------------------------------------------------- # Textures and Mappings Source: https://developer.rhino3d.com/en/guides/cpp/textures-and-mappings/ This guide discusses materials, textures, and texture mapping using C/C++. ## Overview Broadly speaking, there are six concepts that are important to understand when dealing with materials, textures, and mappings: - **Texture Bitmap**: A bitmap image, usually saved in a file. - **Texture Coordinates**: In Rhino these are 2d and 3d points that are saved in an `ON_Mesh` in the `m_T[]` or `m_TC[]` arrays. They should always be set by a texture mapping and never modified directly. - **Texture Mapping**: A function that sets texture coordinates. Persistent texture mappings are stored in `CRhinoDoc::m_texture_mapping_table[]`. - **Surface parameters**: A set of 2d points stored in an `ON_Mesh` in the `m_S[]` array. They are used by the surface parameter mapping. - **Render Material**: A collection of rendering color and shading information, including the names of texture bitmaps. Rendering materials are stored in `CRhinoDoc::m_material_table[]`. - **Object Attributes**: Attributes of a Rhino object, including the rendering materials and texture mappings the object uses, are stored in the `CRhinoObjectAttributes` class returned by `CRhinoObject::Attributes()`. ## Sample The following sample creates a material with a bitmap texture, then modifies a mesh object's attributes, sets up surface parameters and applies a surface parameter mapping so the bitmap is projected onto the mesh along the world Z axis... ```cpp CRhinoDoc* pDoc = context.Document(); if (nullptr == pDoc) return CRhinoCommand::failure; CRhinoDoc& doc = *pDoc; // Id of the currently active render plug-in const UUID renderPlugInId = RhinoApp().CurrentRenderPlugIn()->PlugInID(); // Create a material with a texture bitmap ON_Texture tex; tex.m_image_file_reference.SetFullPath(L"C:/my-texture-folder/sample-texture.bmp", true); tex.m_bOn = true; tex.m_type = ON_Texture::TYPE::bitmap_texture; tex.m_mode = ON_Texture::MODE::modulate_texture; tex.m_mapping_channel_id = 1; ON_Material mat; mat.m_diffuse.SetRGB(150, 0, 0); mat.m_specular.SetRGB(200, 200, 200); mat.m_shine = 0.5 * ON_Material::MaxShine; mat.AddTexture(tex); int mat_index = doc.m_material_table.AddMaterial(mat); if (mat_index < 0) return CRhinoCommand::failure; // Select a mesh to modify CRhinoGetObject go; go.SetGeometryFilter(ON::mesh_object); go.SetCommandPrompt(L"Select a mesh"); go.GetObjects(1, 1); if (CRhinoCommand::success != go.CommandResult()) return go.CommandResult(); const CRhinoMeshObject* mesh0_object = CRhinoMeshObject::Cast(go.Object(0).Object()); if (0 == mesh0_object) return CRhinoCommand::failure; const ON_Mesh* mesh0 = mesh0_object->Mesh(); if (0 == mesh0) return CRhinoCommand::failure; // Copy the mesh ON_Mesh* mesh1 = new ON_Mesh(*mesh0); ON_BoundingBox bbox = mesh1->BoundingBox(); ON_Interval x_extents(bbox.m_min.x, bbox.m_max.x); ON_Interval y_extents(bbox.m_min.y, bbox.m_max.y); // Set up surface parameters. // They will be used by a surface parameter mapping. const int vertex_count = mesh1->m_V.Count(); mesh1->m_S.Reserve(vertex_count); mesh1->m_S.SetCount(0); for (int vi = 0; vi < vertex_count; vi++) { const ON_3dPoint& V = mesh0->m_V[vi]; ON_2dPoint& tc = mesh1->m_S.AppendNew(); tc.x = (float)x_extents.NormalizedParameterAt(V.x); tc.y = (float)y_extents.NormalizedParameterAt(V.y); } // Adjust surface packing settings so that surface parameter // mapping will create vertex coordinates 1-to-1 with the // surface parameters. mesh1->m_srf_domain[0].Set(0.0, 1.0); mesh1->m_srf_domain[1].Set(0.0, 1.0); mesh1->m_srf_scale[0] = 0.0; mesh1->m_srf_scale[1] = 0.0; mesh1->m_packed_tex_domain[0].Set(0.0, 1.0); mesh1->m_packed_tex_domain[1].Set(0.0, 1.0); mesh1->m_packed_tex_rotate = false; // Update the mesh CRhinoMeshObject* mesh1_object = new CRhinoMeshObject(); mesh1_object->SetMesh(mesh1); doc.ReplaceObject(CRhinoObjRef(mesh0_object), mesh1_object); // Make a copy of the object attributes in order to apply some changes ON_3dmObjectAttributes att = mesh1_object->Attributes(); // Update the object to use the new material att.m_material_index = mat_index; att.SetMaterialSource(ON::material_from_object); // Add new texture mapping to the document texture mapping table const int textureMappingIndex = doc.m_texture_mapping_table.AddTextureMapping(ON_TextureMapping::SurfaceParameterTextureMapping); // Look up the mapping id of the newly added texture mapping const UUID textureMappingId = doc.m_texture_mapping_table[textureMappingIndex].Id(); // Remove all texture mappings from the object attributes att.m_rendering_attributes.m_mappings.Destroy(); // Add the newly added texture mapping on the mapping channel that the texture uses att.m_rendering_attributes.AddMappingChannel(renderPlugInId, tex.m_mapping_channel_id, textureMappingId); // Apply the modified attributes to the mesh object doc.ModifyObjectAttributes(CRhinoObjRef(mesh1_object), att); doc.Redraw(); ``` -------------------------------------------------------------------------------- # The Hops Component Source: https://developer.rhino3d.com/en/guides/compute/hops-component/ Hops adds functions to Grasshopper. ## Overview Hops is a component for Grasshopper in Rhino 7 and Rhino 8 for Windows. Hops adds external **functions** to Grasshopper. Like other programming languages, functions let you: * Simplify complex algorithms by using the same function multiple times. * Eliminate duplicate component combinations by placing common combinations in a function. * Share Grasshopper documents with other team members. * Reference Grasshopper documents across multiple projects. * Solve external documents in parallel, potentially speeding up large projects. * Run asynchronously on long running calculations without blocking Rhino and Grasshopper interactions. Hops functions are stored as separate Grasshopper documents. The Hops component will adapt its inputs and outputs to match the function specified. During calculation Hops solves the definition in a separate process, then returns the outputs to the current document. ## How to use Hops Windows ### Installing Hops: There are a few ways to install hops on your machine. 1. [Install Hops](rhino://package/search?name=hops) (This will launch Rhino) 1. Or, type `PackageManager` on the Rhino command line. 1. Then, search for “Hops” 1. Select Hops and then Install ### Create a Hops Function Hops functions are Grasshopper documents with special inputs and outputs. #### Defining Inputs Hops inputs are created using the **Get Components**. The available Get components in Grasshopper are found under *Params Tab > Util Group*: The name of the component is used for the name of the Hops input parameter. So, in the example above, we have three Get Number components with names A, B, and C. Each of these Get components become input parameters when Hops compiles the definition. Each Get component has a context menu (right-click) with various settings. * **Component Name** - This is the name that will be assigned to the input of the Hops component. * **Prompt** - This input will be the tooltip that is displayed when you hover over this parameter on the Hops component. * **Enable/Disable** - Enable or disable this component. * **At Least** - This is only used for the Grasshopper Player and will define the minimum number of values to accept for this input. * **At Most** - This will define the maximum number of values to accept for this input (Default = 1). If this value is set to 1, the Hops component will treat this input as *Item Access*. However, if this value is more than one (or unset) Hops will treat this input as *List Access*. * **Tree Access** - This will define whether you expect to pass a Data Tree into this input. This input is only used in Hops and if it is set to True, then this value will supercede the *At Most* value set above. * **Minimum & Maximum** - These optional inputs are only used for the Grasshopper Player and will clamp numeric inputs to these bounds. * **Presets** - This optional input is only used for the Grasshopper Player and are only found on the *String*, *Integer*, and *Number* Get components. This dialog allows you to set a list of predefined options which will be displayed to the user via the command line prompt. #### Defining Outputs Hops outputs can be defined using the **Context Bake** or **Context Print** components. The name of the input parameter on either of the Context components will be used as the name of the output parameter when Hops gets computed. ### Use the Hops component 1. Place the Grasshopper *Params Tab > Util Group > Hops component* onto the canvas. 1. Right-click the Hops Component, then click Path. 1. Select a Hops Function. Note: this can be a Grasshopper definition on your computer, on a remote computer, or a REST endpoint. 1. The component will show the inputs and outputs. 1. Use the new component like any other Grasshopper component. ### A note about Hops for macOS users The Hops component works in tandem with a local instance of a [Rhino.compute](https://developer.rhino3d.com/guides/compute/) server which, typically, is spun up whenever you launch Grasshopper. However, this server will not run on macOS which means there are two other options for you to consider. You can: 1. make a REST API call to a remote server running on Windows. 1. make a REST API call to a python ghhops_server running locally. The first option (making a call to a remote Windows server) will require some setup modifications to the remote machine. For more information on how to setup a remote server, checkout the section on [Remote Machine Configuration](../hops-component/#remote-machine-configuration). The second option (making a call to a local python server) is covered in more detail in the section called [Calling a CPython Server](../hops-component/#calling-a-cpython-server). ## Hops Settings ### Application Settings The Hops settings control how Hops runs on an application level. It is available through the Grasshopper File pulldown > Preferences > Solver. * **Hops-Compute server URL** - List the IP address or URL of any remote machines or Compute servers. * **API Key** - The API key is a string of text that is secret to your compute server and your applications that are using the compute API e.g. `b8f91f04-3782-4f1c-87ac-8682f865bf1b`. It is optional if you are testing locally, but should be used in a production environment. It is basically how the compute server ensures that the API calls are coming from your apps only. You can enter any string that is unique and secret to you and your compute apps. Make sure to keep this in a safe place. * **Max Concurrent requests** - Used to limit the number of active requests in asynchronous situations. This way hops doesn't make thousands of requests while the original request is being processed. * **Clear Hops Memory cache** - Clears all previously stored solutions from memory. * **Hide Rhino.Compute Console Window** - Hops will solve in the background, but showing the window can be helpful for troubleshooting. * **Launch Local Rhino.Compute at Start** - Use this for remote machines when Compute needs to start before any requests are sent. * **Child Process Count** - Used to limit the number of requests for additional parallel processes. May want to set this to the number of cores available. * **Function Sources** - This section lets you add a path (either to a local directory or a URL) to frequently used Hops functions (ie. Grasshopper definitions). As of Rhino version 7.13, the active model tolerances (ie. absolute distance and angle tolerances) are passed from Hops to the running instance of Rhino.compute as part of the JSON request. ### Component Settings Right_click on the Hops component to select any number of options that control how Hops runs. * **Parallel Computing** - Pass each item to a new parallel node if available. * **Path...** - Add the location of the GH function to be solved. This may be a file name, IP address or URL. * **Show Input: Path** - Makes the Path an input on the component so Path can be set through the GH Canvas. * **Show Input: Enabled** - Shows an Enabled input that runs the component based on a `True` or `False` Boolean value. * **Asynchronous** - The user interface will not be blocked while waiting for a remote process to solve and the definition will be updated when the solve is complete. * **Cache In Memory** - Previous solutions are stored in memory on the local machine to improve performance if the same inputs were previously calculated. * **Cache On Server** - Previous solutions are stored on the remote server to improve performance. Currently available on Remote Hops services only. ## Remote Machine Configuration By default Hops will use your local computer to solve Grasshopper functions. However, it is possible to setup remote computers (ie. servers) or virtual machines for Hops to call. In order to make API calls to a remote machine, please follow this [guide on setting up a production environment](../deploy-to-iis/). ## Calling a CPython Server Use Hops to call into CPython. Some advantages of this component: 1. Call into CPython libraries including Numpy and SciPy 1. Use some of the newest libraries available for CPython such as TensorFlow. 1. Create re-usable functions and parallel processing. 1. Supports real debugging modes including breakpoints. 1. Full support of Visual Studio Code. 1. Other applications and services that support a Python API can also use the libraries included here. 1. The Hops component attempts to detect when inputs and outputs have changed on a server and will rebuild itself. ### Getting started with CPython in Grasshopper This Python module helps you create Python (specifically CPython) functions and use them inside your Grasshopper scripts using the new Hops components. **Required:** 1. [Rhino 7.4 or newer.](https://www.rhino3d.com/download/) 1. [CPython 3.8 or above](https://www.python.org/downloads/) 1. [Hops Component for Grasshopper](https://developer.rhino3d.com/guides/grasshopper/hops-component/) 1. [Visual Studio Code](https://code.visualstudio.com/) highly recommended **Video Tutorial:** This module can use its built-in default HTTP server to serve the functions as Grasshopper components, or act as a middleware to a [Flask](https://flask.palletsprojects.com/en/1.1.x/) app. It can also work alongside [Rhino.Inside.CPython](https://discourse.mcneel.com/t/rhino-inside-python/78987) to give full access to the [RhinoCommon API](https://developer.rhino3d.com/api/). ## Frequently Asked Questions: #### Can you nest Hops functions within other functions? Yes, it is possible to nest Hops function within other functions. This is called _recursion_ and the default recursion limit is set to 10. #### Does it cost money to use Hops? Hops is free to use. #### Can Hops be used with Grasshopper Player to make commands? Yes, Hops functions can use Context Bake and Context Print components to create Rhino commands in Grasshopper Player. #### Does Hops support parallel processing? Yes, Hops by default will launch a parallel process for each branch of a datatree input stream. #### What input and output types does Hops support? (It supports all common types, ask about other ones if you need them) Hops passes standard Grasshopper data types (Strings, Numbers, Lines, etc...). For other datatypes such as images or EPW weather files use a string for the file name so that the external function might also read in the same file. #### Can plugin components run in Hops Functions? Yes, all the installed Grasshopper plugins can run within a Hops Function. #### Can this be used for extremely long calculations within a function? Yes, each Hops component has an option to solve asynchronously. The user interface will not be blocked while waiting for a remote process to solve and the definition will be updated when the solve is complete. Hops will wait for all function calls to return before passing the outputs to the downstream components. -------------------------------------------------------------------------------- # VBScript Variable Hoisting Source: https://developer.rhino3d.com/en/guides/rhinoscript/vbscript-variable-hoisting/ This guides discusses variable scoping and hoisting in VBScript. ## Problem Consider the following VBScript code, which does not work: ```vbnet Option Explicit s = "Hello" ``` Now, consider the following working code: ```vbnet Option Explicit s = "Hello" Dim s ``` Why can a variable be used before declaring it in VBScript? ## Solution Consider this code: ```vbnet Dim s s = Foo(123) Function Foo(x) Foo = x + 345 End Function ``` Here the function is being used before it is declared. Similarly, variables can be used before they are declared. The behaviour is by design. Variable declarations and functions are logically "hoisted" to the top of their scope in VBScript. Also, declaring a variable twice in the same script block is illegal, but redefinition in another block is legal. Procedures may be redeclared at will except if the procedure is in a class, in which case redeclaration is illegal. The following is legal in VBScript: ```vbnet s = Foo(123) If Blah Then Function Foo(x) Foo = x + 345 End Function End If ``` ## Details This is not recommended, but it *is* legal: ```vbnet Dim i For i = 1 To 2 Rhino.Print c Next Const c = 10 ``` And this works too: ```vbnet For i = 1 To 2 Rhino.Print c Dim i Next Const c = 10 ``` But, this fails with a "name redefined" error: ```vbnet For i = 1 To 2 Rhino.Print c Const c = 10 Dim i Next ``` In conclusion, in VBScript: - Variable declarations are logically hoisted to the top of the scope. - Constants are evaluated at code compilation time; the constants' values are injected into the code. -------------------------------------------------------------------------------- # VBScript Variables Source: https://developer.rhino3d.com/en/guides/rhinoscript/vbscript-variables/ This guide provides an overview of VBScript variables. ## Overview A variable is a convenient placeholder that refers to a computer memory location where you can store program information that may change during the time your script is running. For example, you might create a variable called ClickCount to store the number of times a user performs a certain operation. Where the variable is stored in computer memory is unimportant. What is important is that you only have to refer to a variable by name to see or change its value. In VBScript, variables are always of one fundamental data type: [Variant](/guides/rhinoscript/vbscript-datatypes/). ## Declaration You declare variables explicitly in your script using the `Dim` statement, the `Public` statement, and the `Private` statement. For example: ```vbnet Dim AngleDegrees ``` You declare multiple variables by separating each variable name with a comma. For example: ```vbnet Dim Top, Bottom, Left, Right ``` ## Naming Restrictions Variable names follow the standard rules for naming anything in VBScript. A variable name: - Must begin with an alphabetic character. - Cannot contain an embedded period. - Must not exceed 255 characters. - Must be unique in the scope in which it is declared. ## Scope & Lifetime A variable's scope is determined by where you declare it. When you declare a variable within a procedure, only code within that procedure can access or change the value of that variable. It has local scope and is a procedure-level variable. If you declare a variable outside a procedure, you make it recognizable to all the procedures in your script. This is a script-level variable, and it has script-level scope. The lifetime of a variable depends on how long it exists. The lifetime of a script-level variable extends from the time it is declared until the time the script is finished running. At procedure level, a variable exists only as long as you are in the procedure. When the procedure exits, the variable is destroyed. Local variables are ideal as temporary storage space when a procedure is executing. You can have local variables of the same name in several different procedures because each is recognized only by the procedure in which it is declared. ## Assigning Values Values are assigned to variables creating an expression as follows: the variable is on the left side of the expression and the value you want to assign to the variable is on the right. For example: ```vbnet B = 200 ``` ## Scalar Variables & Arrays Much of the time, you only want to assign a single value to a variable you have declared. A variable containing a single value is a scalar variable. Other times, it is convenient to assign more than one related value to a single variable. Then you can create a variable that can contain a series of values. This is called an array variable. Array variables and scalar variables are declared in the same way, except that the declaration of an array variable uses parentheses `( )` following the variable name. In the following example, a single-dimension array containing 11 elements is declared: ```vbnet Dim A(10) ``` Although the number shown in the parentheses is 10, all arrays in VBScript are zero-based, so this array actually contains 11 elements. In a zero-based array, the number of array elements is always the number shown in parentheses plus one. This kind of array is called a fixed-size array. You assign data to each of the elements of the array using an index into the array. Beginning at zero and ending at 10, data can be assigned to the elements of an array as follows: ```vbnet A(0) = 256 A(1) = 324 A(2) = 100 '. . . A(10) = 55 ``` Similarly, the data can be retrieved from any element using an index into the particular array element you want. For example: ```vbnet '. . . SomeVariable = A(8) '. . . ``` Arrays aren't limited to a single dimension. You can have as many as 60 dimensions, although most people can't comprehend more than three or four dimensions. You can declare multiple dimensions by separating an array's size numbers in the parentheses with commas. In the following example, the MyTable variable is a two-dimensional array consisting of 6 rows and 11 columns: ```vbnet Dim MyTable(5, 10) ``` In a two-dimensional array, the first number is always the number of rows; the second number is the number of columns. You can also declare an array whose size changes during the time your script is running. This is called a dynamic array. The array is initially declared within a procedure using either the `Dim` statement or using the `ReDim` statement. However, for a dynamic array, no size or number of dimensions is placed inside the parentheses. For example: ```vbnet Dim MyArray() ReDim AnotherArray() ``` To use a dynamic array, you must subsequently use `ReDim` to determine the number of dimensions and the size of each dimension. In the following example, `ReDim` sets the initial size of the dynamic array to 25. A subsequent `ReDim` statement resizes the array to 30, but uses the `Preserve` keyword to preserve the contents of the array as the resizing takes place. ```vbnet ReDim MyArray(25) ' . . . ReDim Preserve MyArray(30) ``` There is no limit to the number of times you can resize a dynamic array, although if you make an array smaller, you lose the data in the eliminated elements. ## Related topics - [VBScript Data Types](/guides/rhinoscript/vbscript-datatypes/) - [VBScript Procedures](/guides/rhinoscript/vbscript-procedures/) -------------------------------------------------------------------------------- # What is a Rhino Plugin? Source: https://developer.rhino3d.com/en/guides/general/what-is-a-rhino-plugin/ This guide outlines what a Rhino plugin is and what forms it comes in. A Rhino plugin is a software module that extends the functionality of Rhino or Grasshopper by adding commands, features, or capabilities. A Rhino plugin is a Dynamic Link Library, or DLL. On Windows, a Rhino plugin built with the [C/C++ SDK](/guides/cpp/what-is-the-cpp-sdk/) is a regular DLL using shared MFC DLL. On Windows and Mac, a Rhino plugin built with the [RhinoCommon SDK](/guides/rhinocommon/what-is-rhinocommon/) is a .NET assembly. Examples of Rhino plugins include [Grasshopper](http://www.grasshopper3d.com), [Brazil](http://brazil.rhino3d.com/), [Flamingo](http://nxt.flamingo3d.com/), and [Bongo](http://bongo.rhino3d.com/). See [food4rhino.com](http://www.food4rhino.com/) for more. ## Types of Plugins Rhino supports five different types of plugins: 1. *General Utility*: A general purpose utility that can contain one or more commands. 1. *File Import*: Imports data from other file formats into Rhino; can support more that one format. 1. *File Export*: Exports data from Rhino to other file formats; can support more than one format. 1. *Custom Rendering*: Applies materials, textures, and lights to a scene to produce rendered images. 1. *3D Digitizing*: Interfaces with 3D digitizing devices, such as those made by MicroScribe, Faro, & Romer. ***Note***: File Import, File Export, Custom Rendering and 3D Digitizing plugins are all specialized enhancements to the General Utility plugin. Thus, all plugin types can contain one or more commands. ## Plugin Compatibility For Rhino to successfully load and run your plugin, several conditions must be met: 1. The "RhinoSdkVersion" number of your plugin must match the "RhinoSdkVersion" of Rhino. 1. The "RhinoSdkServiceRelease" number of Rhino must be greater than or equal to the "RhinoSdkServiceRelease" of your plugin. We occasionally make changes to our SDKs. When we do, we change the "RhinoSdkServiceRelease" number. As a plugin developer you are unlikely to encounter a problem with the first condition. This would occur, for instance, if a user tried to load a plugin built for Rhino 6 in Rhino 4. You may however, occassionally encounter problems with the second condition. If you compiled your plugin using the 6.2 (RhinoSdkVersion.RhinoSdkServiceRelease) SDK and a user running a 6.1 Rhino tries to run it, they will get an error message and the plugin will refuse to load. If your customer gets this message, they need to get the latest Rhino (could be 6.2 or greater in this example) and that should resolve the problem. ## Related topics - [Developer Prerequisites](/guides/general/rhino-developer-prerequisites) -------------------------------------------------------------------------------- # Your First Component (Mac) Source: https://developer.rhino3d.com/en/guides/grasshopper/your-first-component-mac/ This guide walks you through your first Grasshopper component for Rhino for Mac using RhinoCommon and Visual Studio Code. ## Prerequisites It is presumed you already have the necessary tools installed and are ready to go. If you are not there yet, see [Installing Tools (Mac)](/guides/rhinocommon/installing-tools-mac). ## HelloGrasshopper We will use Visual Studio Code and the dotnet Rhino Grasshopper template to create a new, basic, Grasshopper component called _HelloGrasshopper_. If you are familiar with Visual Studio Code, these step-by-step instructions may be overly detailed for you. The executive summary: create a new Solution using the Grasshopper Component dotnet template, build and run, and then make a change. We are presuming you have never used Visual Studio Code before, so we'll go through this one step at a time. ### Download the required template 1. Launch Visual Studio Code. 1. Open _Visual Studio Code's Terminal_ via _Terminal (menu entry)_ > _New Terminal_, or using the command palette _(⌘ ⇧ P)_ and search for "Terminal". 1. Inside Terminal, run: ```pwsh dotnet new install Rhino.Templates ``` ### Starting the Project 1. Create a folder on your mac where you would like your project to live. Name the folder `HelloGrasshopper`. 1. If you have not done so already, _launch Visual Studio Code_. 1. Now we can open our new folder, navigate to _File_ > _Open Folder_ and choose the folder we just created. 1. Open Terminal via _Terminal_ > _New Terminal_, or using the command palette _(⌘ ⇧ P)_ and search for "Terminal". 1. Enter the following command into the Terminal: ```pwsh dotnet new grasshopper --version 8 -sample ``` 1. In our Folder explorer, we should see the project appear as Visual Studio Code discovers the files. 1. Expand the Solution Explorer, this is the best way to interact with C# projects on Mac in Visual Studio Code. ### Boilerplate Build Older Rhino Templates do not have System.Drawing.Common referenced. To add them to your project run the command **dotnet add package System.Drawing.Common -v 7.0.0** in the terminal. 1. Before we do anything, let's _Run and Debug_ HelloGrasshopper to make sure everything is working as expected. We'll just build the boilerplate Plugin template. Click the _Run and Debug_ button on the left hand side of Visual Studio Code and then the green play button in the newly opened panel. ![New Project](https://developer.rhino3d.com/images/your-first-component-mac-01.png) 1. _Rhinoceros and Grasshopper_ launch. 1. We will find the HelloGrasshopper Component under **Category / SubCategory** ![Solution Anatomy](https://developer.rhino3d.com/images/your-first-component-mac-02.png) 4. Adding the component to the canvas will run the component and output some interesting geometry in the Rhino Viewport ![Solution Anatomy](https://developer.rhino3d.com/images/your-first-component-mac-03.png) 5. Press Stop Debugging _(⇧ F5)_, in Visual Studio Code, signified by the Red Square in the debug toolbar. This stops the debugging session. Now let's n take a look at the Plugin Anatomy. ### Component Anatomy Use the **Solution Explorer** to expand the **Solution** (_.sln_) so that it looks like this... ![Solution Anatomy](https://developer.rhino3d.com/images/your-first-component-mac-04.png) 1. The **HelloGrasshopper** solution (_.sln_) 1. The **HelloGrasshopper** project (_.csproj_) has the same name as its parent solution...this is the project that was created for us by the template earlier. 1. **References**: Just as with most projects, you will be referencing other libraries. The template added the necessary references to create a basic Grasshopper component. 1. **Grasshopper** is the Rhino for Mac main grasshopper DLL. Classes in this DLL are subclassed and used by your custom component. 1. **HelloGrasshopperComponent.cs** is where a custom `Grasshopper.Kernal.GH_Component` subclass is defined. Your project may contain multiple subclasses of GH*Component if you want to ship multiple components in a single \_gha*. 1. **HelloGrasshopperInfo.cs** defines general information about this _gha_. ### Debugging 1. Add a breakpoint to line 75 of _HelloGrasshopperComponent.cs_. You set breakpoints in Visual Studio Code by clicking in the gutter to the left of the line numbers. ![Set a breakpoint](https://developer.rhino3d.com/images/your-first-component-mac-05.png) 1. _Run and Debug_. our project. The breakpoint will become an empty circle, this is because our code has not been loaded yet. Once we hit the breakpoint once and continue, the code will be loaded until we end our Debug session. ![Set a breakpoint](https://developer.rhino3d.com/images/your-first-component-mac-06.png) 1. Rhino and Grasshopper should open, if Grasshopper does not open, click "New Model" and run the _Grasshopper_ command. 1. Place our sample component _HelloGrasshopperComponent_ and as soon as you do, you should hit your breakpoint and rhino/Grasshopper will pause (You may need to drag the Grasshopper window out of the way to see Visual Studio Code) ![Hit a breakpoint](https://developer.rhino3d.com/images/your-first-component-mac-07.png) 1. With Rhino/Grasshopper paused, in _Visual Studio Code_ we will see _Locals_ under _Variables_. You can inspect all of the values for the variables in your component. ![Locals panel](https://developer.rhino3d.com/images/your-first-component-mac-08.png) 1. Let's Continue Execution in Rhino and Grasshopper by pressing the Green _Play_ button in the Debug Bar 1. Control is passed back to _Rhino / Grasshopper_ and your command finishes. Now _Stop_ _(⇧ F5)_ the debugging session as before. 1. **Remove** the breakpoint you created above by clicking on it in the gutter. **Congratulations!** You have just built your first Grasshopper component for Rhino for Mac. **Now what?** ## Next Steps You've built a component library from boilerplate code, but what about putting together a new simple component "from scratch" and adding it to your project? (Component libraries are made up of multiple components after all). Next, check out the [Simple Component](/guides/grasshopper/simple-component) guide. Try debugging your new grasshopper plugin on [Windows](/guides/grasshopper/your-first-component-windows/), all plugins using the new templates are now cross-platform by default. ### Adding components A single gha can contain more than one [GH_Component](https://mcneel.github.io/grasshopper-api-docs/api/grasshopper/html/T_Grasshopper_Kernel_GH_Component.htm) derived class (and commonly does). Dotnet has support for adding more custom components to your project. 1. Open _Visual Studio Code's Terminal_ via _Terminal (menu entry)_ > _New Terminal_, or using the command palette _(⌘ ⇧ P)_ and search for "Terminal". 1. Inside Terminal, run: ```pwsh dotnet new ghcomponent -n "NewComponent" ``` 1. A new component will appear called _NewComponent_ ## Related topics This article is focused on initial setup and debugging a Grasshopper component in Rhino for Mac. For further reading on customizing your component please see: - [Grasshopper](/guides/grasshopper/csharp-essentials/) - [Distributing your Plugin](/guides/yak/creating-a-rhino-plugin-package/) -------------------------------------------------------------------------------- # Your First Plugin (Windows) Source: https://developer.rhino3d.com/en/guides/rhinocommon/your-first-plugin-windows/ This guide walks you through your first plugin for Rhino for Windows using RhinoCommon and Visual Studio. It is presumed you already have the necessary tools installed and are ready to go. If you are not there yet, see [Installing Tools (Windows)](/guides/rhinocommon/installing-tools-windows). ## Barebones plugin We will use the _RhinoCommon Plugin for Rhino 3D (C#)_ project template to create a new general purpose plugin. The project template generates the code for a functioning plugin. Follow these steps to build the plugin. ### Plugin Template 1. Launch _Visual Studio_ and navigate to _File_ > _New_ > _Project..._. 2. From the _Create a new project_ dialog, select the _RhinoCommon Plugin for Rhino 3D (C#)_ template from the list of installed templates and click _Next_. ![New Project Template](https://developer.rhino3d.com/images/your-first-plugin-windows-01.png) 3. Type the project name as shown below. You can enter a different name if you want. The template uses the project name when it creates files and classes. If you enter a different name, your files and classes will have a name different from that of the files and classes mentioned in this tutorial. Don’t forget to choose a location to store the project. When finished, click _Create_. ![New Project Configure](https://developer.rhino3d.com/images/your-first-plugin-windows-02.png) 4. Upon clicking _Create_, the _New Rhino C# Plugin_ dialog will appear. By default, the template will create a _General Utility_ plugin. ![Plugin Settings](https://developer.rhino3d.com/images/your-first-plugin-windows-03.png) 5. The _New RhinoCommon .NET Plugin_ dialog allows you to modify a number of settings used by the template when generating the plugin source code: 1. **Plugin name**: Modify this field if you want to change the name of the plugin's class name. 1. **Command class name:**: Modify this field if you want to change the name of the plugin's initial command class name. 1. **Plugin type**: Select the [type of plugin](/guides/general/what-is-a-rhino-plugin) that you want the template to create. 1. **Minimum Target Version**: Select the lowest Rhino version to target. 1. **Build Yak Package**: Toggle true to have Rhino build a Yak package automatically from your project 1. **Include VS Code launch/tasks json files**: Includes `.vscode` folder so the plugin can be debugged in VSCode as well (Very handy for Mac). 1. **Wndows UI**: If you are planning on using Windows Forms or WPF for user interface, instead of Eto, then check the approprate box. 6. For this tutorial, just accept the default settings. Click the _Finish_ button, and the template begins to generate your plugin project’s folders, files, and classes. When the template is finished, look through the plugin project using _Visual Studio’s Solution Explorer_... ### Plugin Anatomy Use the _Solution Explorer_ to expand the _Solution_ (_.sln_) so that it looks like this. ![Plugin Anatomy](https://developer.rhino3d.com/images/your-first-plugin-windows-04.png) 1. **HelloRhinoCommon**: The plugin project (_.csproj_), which has the same name as its parent solution. The project that was created for us by the project template we just used. 1. **Dependencies**: Contains references to both the .NET Framework 4.8 and .NET 7.0 assemblies required by the plugin. The project obtains RhinoCommon dependencies via [NuGet](https://www.nuget.org/packages/rhinocommon). 1. **Properties**: Contains the _AssemblyInfo.cs_ source file. This file contains the meta-data, such as author and copyright, that appears in Rhino's [Plugin Manager](https://docs.mcneel.com/rhino/8/help/en-us/index.htm#options/plug-ins.htm). 1. **Embedded Resources**: Contains icons, bitmaps, and other non-code resources you want embedded in your plugin. 1. **HelloRhinoCommonCommand.cs**: The initial plugin command, which inherits from `Rhino.Commands.Command`. 1. **HelloRhinoCommonPlugin.cs**: The plugin class, which inherits from `Rhino.Plugins.Plugin`. ### Testing 1. From _Visual Studio_, navigate to _Debug_ > _Start Debugging_. You can also press _F5_, or click the _Debug_ button on Visual Studio's toolbar. This will load Rhino. 1. From within Rhino, navigate to _Tools_ > _Options_. Navigate to the _Plugins_ page under _Rhino Options_ and install your plugin. ![Rhino Options](https://developer.rhino3d.com/images/your-first-plugin-windows-05.png) 1. Once your plugin is loaded, close the options dialog and run your _HelloRhinoCommonCommand_ command. 1. You have finished creating your first plugin! ## Adding Additional Commands Rhino plugins can contain any number of commands. Commands are created by inheriting a new class from `Rhino.Commands.Command`. Note, Command classes must return a unique command name. If you try to use a command name that is already in use, then your command will not work. To add a new Rhino command to your plugin project, right-click on the project, in Visual Studio’s Solution Explorer, and click Add > New Item…. From the Add New Item dialog, select _Empty RhinoCommon Command for Rhino 3D (C++)_, specify the name of the command, and click _Add_. ![Rhino Command](https://developer.rhino3d.com/images/your-first-plugin-windows-06.png) ### Example The following example code demonstrates a simple command class that essentially does nothing: ```cs using Rhino; using Rhino.Commands; using Rhino.Geometry; using Rhino.Input; using Rhino.Input.Custom; namespace HelloRhinoCommon { public class HelloRhinoCommonCommand : Command { public HelloRhinoCommonCommand() { // Rhino only creates one instance of each command class defined in a // plug-in, so it is safe to store a refence in a static property. Instance = this; } ///The only instance of this command. public static HelloRhinoCommonCommand Instance { get; private set; } ///The command name as it appears on the Rhino command line. public override string EnglishName => "HelloRhinoCommonCommand"; protected override Result RunCommand(RhinoDoc doc, RunMode mode) { // TODO: start here modifying the behaviour of your command. // --- RhinoApp.WriteLine("The {0} command will add a line right now.", EnglishName); Point3d pt0; using (GetPoint getPointAction = new GetPoint()) { getPointAction.SetCommandPrompt("Please select the start point"); if (getPointAction.Get() != GetResult.Point) { RhinoApp.WriteLine("No start point was selected."); return getPointAction.CommandResult(); } pt0 = getPointAction.Point(); } Point3d pt1; using (GetPoint getPointAction = new GetPoint()) { getPointAction.SetCommandPrompt("Please select the end point"); getPointAction.SetBasePoint(pt0, true); getPointAction.DynamicDraw += (sender, e) => e.Display.DrawLine(pt0, e.CurrentPoint, System.Drawing.Color.DarkRed); if (getPointAction.Get() != GetResult.Point) { RhinoApp.WriteLine("No end point was selected."); return getPointAction.CommandResult(); } pt1 = getPointAction.Point(); } doc.Objects.AddLine(pt0, pt1); doc.Views.Redraw(); RhinoApp.WriteLine("The {0} command added one line to the document.", EnglishName); // --- return Result.Success; } } } ``` ## Next Steps If you'd like to push your exciting new plugin to Yak so that everyone can use it, check out the [Creating a Yak Package](/guides/yak/creating-a-rhino-plugin-package/) guide. Try debugging your new plugin on [Mac](/guides/rhinocommon/your-first-plugin-mac/), all plugins using the new templates are now cross-platform by default. ## Related topics - [Installing Tools (Windows)](/guides/rhinocommon/installing-tools-windows) -------------------------------------------------------------------------------- # 2 Python Essentials Source: https://developer.rhino3d.com/en/guides/rhinopython/primer-101/2-python-essentials/ ## 2.1 Language Origin Like conversational languages, programming languages group together in clusters. Python is a high level language, indicating that the language was designed to be easy for humans to understand. On the opposite end of the spectrum are extremely low level languages, (often referred to as machine-code), that are most definitely not easy to understand. In between are languages such as C or C++ which offer layers of abstraction above machine-code. As I mentioned, Python is a step even higher, meaning that it is far easier to read (closer to the English language) and we don't need to manage difficult functionality like memory allocation, or declaring variables! Lucky us. Python was first released in 1991, since then it has grown to become freely available with a user-group exceeding tens of thousands. The Python documentation claims, " Python plays well with others," " Python runs everywhere," " Python is friendly... and easy to learn" and " Python is Open!" For more information about the Python programming language and its development see: [http://www.python.org](http://www.python.org). Assuming that you might be reading these pages without any prior programming experience whatsoever, I still dare guess that the following example will not give you much trouble: ```python import rhinoscriptsyntax as rs somenumber = rs.GetReal("Line length") line = rs.AddLine( (0,0,0), (somenumber,0,0) ) if line is None: print("Something went wrong") else: print("Line curve inserted with id", line) ``` Of course you might have no conception of what [0,0,0] actually means and you might be confused by rs.GetReal() but on the whole it is pretty much the same as the English you use at the grocers: ``` Ask Rhino to assign a number to something called 'somenumber'. Tell Rhino to add a line from the world origin to the point on the x-axis indicated by 'somenumber' print a success message ``` Translating Python code to and from regular English should not be very difficult, at least not at this featherweight level. It is possible to convolute the code so that it becomes unreadable, but this is not something you should take pride in. The syntax resembles English for a good reason, I suggest we stick to it. As mentioned before, there are three things the syntax has to support, and the above script uses them all: 1. Flow control » Depending on the outcome of the second line, some lines are not run 2. Variable data » somenumber is used to store a variable number 3. Communication » The user is asked to supply information and is informed about the result ## 2.2 Flow Control We use flow-control in Python to skip certain lines of code or to execute others more than once. We can also use flow-control to jump to different lines in our script and back again. You can add conditional statements to your code which allow you to shield off certain portions. If…Else…Else If structures are examples of conditional statements, but we'll discuss them later. A typical conditional statement is: ``` You have to be this tall (1.5m) to ride the roller coaster. ``` This line of ‘code’ uses a condition (taller than 1.5m) to evaluate whether or not you are allowed to ride the roller coaster. Conditional statements like this can be strung together indefinitely. We could add a maximum height as well, or a weight limitation, or a ban on spectacles or heart-conditions. Instead of skipping lines we can also repeat lines. We can do this a fixed number of times: ``` Add 5 tea-spoons of cinnamon. ``` Or again use a conditional evaluation: ``` Keep adding milk until the dough is kneadable. ``` The repeating of lines is called ‘Looping’ in coding slang. There are several loop types available but they all work more or less the same. They will be covered in detail later on. ## 2.3 Variable Data Whenever we want our code to be dynamic we have to make sure it can handle all kinds of different situations. In order to do that we must have the ability to store variables. For instance we might want to store a selection of curves in our 3D model so we can delete them at a later stage. Or perhaps our script needs to add a line from the mouse pointer to the origin of the 3D scene. Or we need to check the current date to see whether or not our software has expired. This is information which was not available at the time the script was written. Whenever we need to store data or perform calculations or logic operations we need variables to remember the results. Since these operations are dynamic we cannot add them to the script prior to execution. We need placeholders. In the example on the previous page the thing named *"somenumber"* is a placeholder for a number. It starts out by being just a name without any data attached to it, but it will be assigned a numeric value in the line: ```python somenumber = rs.GetNumber("Line length") ``` Then later on we retrieve that specific value when we add the line curve to Rhino: ```python curve = rs.AddLine([0,0,0], [somenumber,0,0]) ``` All the other coordinates that define the line object are hard-coded into the script. There is no limit to how often a variable can be read or re-assigned a new value, but it can never contain more than one value and there’s no undo system for retrieving past values. Apart from numbers we can also store other types of data in variables. For the time being, we’ll restrict ourselves to the four most essential ones, plus a special one which is used for error-trapping: 1. Integers 2. Doubles 3. Booleans 4. Strings 5. Null variable ### 2.3.1 Integers and Doubles Integers and Doubles are both numeric variable types, meaning they can be used to store numbers. They cannot store the same kind of numbers, which is why we ended up with more than one type. Integers can only be used to store whole numbers. Their range extends from roughly minus two- billion to roughly plus two-billion. Every whole number between these extremes can be represented using an integer. Integers are used almost exclusively for counting purposes (as opposed to calculations). Doubles are numeric variables which can store numbers with decimals. Doubles can be used to represent numbers as large as 1.8×10308 and as small as 5.0×10-324, though in practise the range of numbers which can be accurately represented is much smaller. Those of you who are unfamiliar with scientific notation need not to worry, I shall not make a habit out of this. It is enough to know that the numeric range of doubles is truly enormous. The set of all possible Double and Integer numbers is not continuous; it has gaps. There exists no Integer between zero and one and there exists no Double between zero and 5.0×10-324. The fact that the size of the gap is so much smaller with Doubles is only because we’ve picked a number close to zero. As we move towards bigger and bigger numbers, the gaps between two adjacent Double values will become bigger as well and as we approach the limits of the range, the gaps are big enough to fit the Milky Way. 2×10300 minus one billion is still 2×10300, so beware when using extremely large numbers. Normally, we never stray into the regions where 32-bit computing starts to break down, we tend to restrict ourselves to numbers we can actually cope with. The Python syntax for working with numeric variables should be very familiar: ```python x = 15 + 26 # x equals 41 x = 15 + 26 * 2.33 # x equals 75.58 x = math.sin(15 + 26) + math.sqrt(2.33) # x equals 1.368 ``` You can use the `print()` method to display the result of these computations. The `print()` method will display the value in the command-line: ```python x = 2 * math.sin(15 + 26) + math.log(55) print(x) ``` Of course you can also use numeric variables on the right hand side of the equation: ```python x = x + 1 x = math.sin(y) + math.sqrt(0.5 * y) ``` The first line of code will increment the current value of x by one, the second line will assign a value to x which depends on the value of y. If y equals 34 for example, x will become 4.65218831173768. Note, there is a special shortcut in Python that allows you to define multiple variables in a single line of code: ```python x, y, z = [1,2,3] print(x) # returns 1 print(y) # returns 2 print(z) # returns 3 ``` ### 2.3.2 Booleans Numeric variables can store a whole range of different numbers. Boolean variables can only store two values mostly referred to as Yes or No, True or False. Obviously we never use booleans to perform calculations because of their limited range. We use booleans to evaluate conditions... remember? ``` You have to be taller than 1.5m to ride the roller coaster. ``` "Taller than 1.5m" is the condition in this sentence. This condition is either True or False. You are either taller than 1.5m or you are not. Since most of the Flow-control code in a script is based on conditional statements, booleans play a very important role. Let’s take a look at the looping example: ``` Keep adding milk until the dough is kneadable. ``` The condition here is that the dough has to be kneadable. Let’s assume for the moment that we added something (an algorithm) to our script that could evaluate the current consistency of the dough. Then our first step would be to use this algorithm so we would know whether or not to add milk. If our algorithm returns False (I.e. "the dough isn’t kneadable") then we will have to add some milk. After we added the milk we will have to ask again and repeat these steps until the algorithm returns True (the dough is kneadable). Then we will know there is no more milk needed and that we can move on to the next step in making our Apfelstrudel. In Python we never write "0" or "1" or "Yes" or "No", for boolean values we always use "True" or "False". ```python if curve is None: print("Something went terribly wrong!") ``` This will return either True or False, only if the result is True (the curve is None) will the code pass into the conditional statement and print "Something went terribly wrong!." ### 2.3.3 Strings Strings are used to store text. Whenever you add quotes around stuff in Python, it automatically becomes a String. So if we encapsulate a number in quotes, it will become text: ```python variable1 = 5 variable2 = "5" ``` You could print these variables to the command line and they would both look like 5, but the String variable behaves differently once we start using it in calculations: ```python print(variable1 + variable2) # Results in an "Unsupported Operand Type" Error ``` Python throws an error when we try to add a String variable to an Integer Variable. We must first convert the string to an integer, then we can add them together. ```python print(variable1 + int(variable2)) # Results in 10 ``` When you need to store text, you have no choice but to use Strings. The syntax for Strings is quite simple, but working with Strings can involve some very tricky code. For the time being we’ll only focus on simple operations such as assignment and concatenation: ```python import math a = "Apfelstrudel" print(a) a = "Apfel" + "strudel" print(a) a = "4" + " " + "Apfelstrudel" print(a) a = "The square root of 2.0 = " + str(math.sqrt(2.0)) print(a) ``` Internally, a String is stored as a series of characters. Every character (or 'char') is taken from the Unicode table, which stores a grand total of ~100.000 different characters. The index into the unicode table for the question mark for example is 63, lowercase e is 101 and the blank space is 32: Further down the road we'll be dealing with some advanced String functions which will require a basic understanding of how Strings work, but while we are still just using the simple stuff, it's good enough to know it just works the way you expect it to. Strings are used heavily in Python since object IDs are always written as strings. Object IDs are those weird codes that show up in the Object Property Details: *D7EFCF0A-DB47-427D-9B6B-44EC0670C573*. IDs are designed to be absolutely unique for every object which will ever exist in this universe, which is why we can use them to safely and unambiguously identify objects in the document. ### 2.3.4 None Variable Whenever we ask Rhino a question which might not have an answer, we need a way for Rhino to say "I don't know". Using the example on page 5: ```python curve = rs.AddLine([0,0,0], [somenumber,0,0]) ``` It is not a certainty that a curve was created. If the user enters zero when he is asked to supply the value for somenumber, then the startpoint of the line would be coincident with the endpoint. Rhino does not like zero-length lines and will not add the object to the document. This means that the return value of `rs.AddLine()` is not a valid object ID. Almost all methods in Rhino will return a None variable if they fail, this way we can add error-checks to our script and take evasive action when something goes wrong. ```python curve = rs.AddLine([0,0,0], [somenumber,0,0]) if not curve: print "Something went terribly wrong!" ``` The statement, `if not x` in Python will return a value True if the variable "curve" is *None*, 0 or an empty list. ### 2.3.5 Using Variables Conventionally, whenever we intend to use variables in a script, we would have to declare them first. However, with Python, we are relieved of this duty and we can simply create and use variables without initially declaring them. Python also does not require that we declare the type of variable we are using, as in other programming languages. Both of these qualities emphasize why Python is such a quick and easy to learn language. So, to declare a variable we simply write: ```python a = "Apfelstrudel" ``` When using a variable, you choose the name and then set it equal to a value (Number, String, Boolean etc).The name you get to pick yourself. In the example above we have used a, which is not the best of all possible choices. For one, it doesn't tell us anything about what the variable is used for or what kind of data it contains. A better name would be `strFood`. The str prefix indicates that we are dealing with a String variable here and the Food bit is hopefully fairly obvious. A widely used system for variable prefixes is as follows: Don't worry about all those weird variable types, some we will get to in later chapters, others you will probably never use. The scope (sometimes called "lifetime") of a variable refers to the region of the script where it is accessible. Whenever you declare a variable inside a function, only that one function can read and write to it. Variables go 'out of scope' whenever their containing function terminates. 'Lifetime' is not a very good description in my opinion, since some variables may be very much alive, yet unreachable due to being in another scope. But we'll worry about scopes once we get to function declarations. For now, let's just look at an example with proper variable usage: ```python complaint = "I don't like " food = "Apfelstrudel. " nag = "Can I go now?" print(complaint, food, nag) ``` An important note to reiterate is Python's case sensitivity. Unlike other languages, in Python "Apfelstrudel", "apfelstrudel" and "ApfelStrudel" are not equivalent, this is also true for all variable names, functions, classes and any other part of the code. Just remember to be very careful with upper and lower case letters! Now, high time for an example. We'll be using the macro from page 2, but we'll replace some of the hard coded numbers with variables for added flexibility. This script looks rather intimidating, but keep in mind that the messy looking bits (line 10 and beyond) are caused by the script trying to mimic a macro, which is a bit like trying to drive an Aston-Martin down the sidewalk. Usually, we talk to Rhino directly without using the command-line and the code looks much friendlier: ```python import rhinoscriptsyntax as rs major_radius = rs.GetReal("Major radius", 10.0, 1.0, 1000.0) minor_radius = rs.GetReal("Minor radius", 2.0, 0.1, 100.0) sides = rs.GetInteger("Number of sides", 6, 3, 20) point1 = " w" + str(major_radius) + ",0,0" point2 = " w" + str(major_radius + minor_radius) + ",0,0" rs.Command("_SelNone") rs.Command("_Polygon _NumSides=" + str(sides) + " w0,0,0" + point1) rs.Command("_SelLast") rs.Command("-_Properties _Object _Name Rail _Enter _Enter") rs.Command("_SelNone") rs.Command("_Polygon _NumSides=" + str(sides) + point1 + point2) rs.Command("_SelLast") rs.Command("_Rotate3D w0,0,0 w1,0,0 90") rs.Command("-_Properties _Object _Name Profile _Enter _Enter") rs.Command("_SelNone") rs.Command(" _-Sweep1 _-SelName Rail _-SelName Profile _Enter _Enter _Closed=Yes _Enter") rs.Command("_-SelName Rail") rs.Command("_-SelName Profile") rs.Command("_Delete") ```
Line Description
2.5 This is where we ask the user to enter a number value ("Real" is another word for "Double"). We supply the *rs.GetReal()* method with four fixed values, one string and three doubles. The string will be displayed in the command-line and the first double (10.0) will be available as the default option: We're also limiting the numeric domain to a value between one and a thousand. If the user attempts to enter a larger number, Rhino will claim it's too big:
7...8 On these lines we're creating the strings, based on the values of `dblMajorRadius` and `dblMinorRadius`. If we assume the user has chosen the default values in all cases, `dblMajorRadius` will be 10.0 and `dblMinorRadius` will be 2.0, which means that `strPoint2` will look like " w12,0,0".
10...23 This is the same as the macro on page 3, except that we've replaced some bits with variables and there are three extra lines at the bottom which get rid of the construction geometry (so we can run the script more than once without it breaking down).
## Next Steps There are the basics of the Python datastructures, next learn the Python's [script anatomy](/guides/rhinopython/primer-101/3-script-anatomy/). -------------------------------------------------------------------------------- # 2 RhinoScript Essentials Source: https://developer.rhino3d.com/en/guides/rhinoscript/primer-101/2-vbscript-essentials/ ## 2.1 Language Origin Like conversational languages, programming languages group together in clusters. There are language families and language generations. VBScript is a member of the BASIC language family which in turn is a third generation group. ‘Third generation’ indicates that the language was designed to be easy for humans to understand. First and second generation languages (often referred to as machine-code), are most definitely not easy to understand. Just so you know the difference between second and third generation code, here is an example of second generation assembly: | | | | | | :--- | --- | --- | -------------: | | mov | [ebx], ecx | | add | ebx, 4 | | loop | init_loop | | push | dword FirstMsg | | call | _puts | | pop | ecx | | push | dword 10 | | push | dword array | | call | _print_array | | add | esp, 8 | Lucky us. Incidentally, BASIC stands for Beginner’s All-purpose Symbolic Instruction Code and was first developed in 1963 in order to drag non-science students into programming. As you can see above, even assembly already makes heavy use of English vocabulary but although we are familiar with the words, it is not possible for laymen to decipher the commands. Assuming that you might be reading these pages without any prior programming experience whatsoever, I still dare guess that the following example will not give you much problems: ``` somenumber = Rhino.GetReal("Line length") line = Rhino.AddLine(Array(0,0,0), Array(somenumber,0,0)) If IsNull(line) Then Rhino.Print "Something went wrong" Else Rhino.Print "Line curve inserted" End If ``` Of course you might have no conception of what `Array(0,0,0)` actually means and you might be confused by `Rhino.GetNumber(0)` or `IsNull()`, but on the whole it is pretty much the same as the English you use at the grocers: Ask Rhino to assign a number to something called 'somenumber'. Tell Rhino to add a line from the world origin to the point on the x-axis indicated by 'somenumber' If the result of the previous operation was not a curve object, then print a failure message otherwise print a success message Translating VBScript code to and from regular English should not be very difficult, at least not at this featherweight level. It is possible to convolute the code so that it becomes unreadable, but this is not something you should take pride in. The syntax resembles English for a good reason, I suggest we stick to it. As mentioned before, there are three things the syntax has to support, and the above script uses them all: Flow control » Depending on the outcome of the second line, some lines are not run Variable data » somenumber is used to store a variable number Communication » The user is asked to supply information and is informed about the result ## 2.2 Flow Control We use flow-control in VBScript to skip certain lines of code or to execute others more than once. We can also use flow-control to jump to different lines in our script and back again. You can add conditional statements to your code which allow you to shield off certain portions. If…Then…Else and Select…Case structures are examples of conditional statements, but we'll discuss them later. A typical conditional statement is: ``` You have to be this tall (1.5m) to ride the roller coaster. ``` This line of ‘code’ uses a condition (taller than 1.5m) to evaluate whether or not you are allowed to ride the roller coaster. Conditional statements like this can be strung together indefinitely. We could add a maximum height as well, or a weight limitation, or a ban on spectacles or heart-conditions. Instead of skipping lines we can also repeat lines. We can do this a fixed number of times: ``` Add 5 tea-spoons of cinnamon. ``` Or again use a conditional evaluation: ``` Keep adding milk until the dough is kneadable. ``` The repeating of lines is called ‘Looping’ in coding slang. There are several loop types available but they all work more or less the same. They will be covered in detail later on. ## 2.3 Variable Data Whenever we want our code to be dynamic we have to make sure it can handle all kinds of different situations. In order to do that we must have the ability to store variables. For instance we might want to store a selection of curves in our 3Dmodel so we can delete them at a later stage. Or perhaps our script needs to add a line from the mouse pointer to the origin of the 3D scene. Or we need to check the current date to see whether or not our software has expired. This is information which was not available at the time the script was written. Whenever we need to store data or perform calculations or logic operations we need variables to remember the results. Since these operations are dynamic we cannot add them to the script prior to execution. We need placeholders. In the example on the previous page the thing named "somenumber" is a placeholder for a number. It starts out by being just a name without any data attached to it, but it will be assigned a numeric value in the line: ``` somenumber = Rhino.GetNumber("Line length") ``` Then later on we retrieve that specific value when we add the line curve to Rhino: ``` curve = Rhino.AddLine(Array(0,0,0), Array(somenumber,0,0)) ``` All the other coordinates that define the line object are hard-coded into the script. There is no limit to how often a variable can be read or re-assigned a new value, but it can never contain more than one value and there’s no undo system for retrieving past values. Apart from numbers we can also store other types of data in variables. For the time being, we’ll restrict ourselves to the four most essential ones, plus a special one which is used for error-trapping: 1. Integers 2. Doubles 3. Booleans 4. Strings 5. Null variable ### 2.3.1 Integers and Doubles Integers and Doubles are both numeric variable types, meaning they can be used to store numbers. They cannot store the same kind of numbers, which is why we ended up with more than one type. Integers can only be used to store whole numbers. Their range extends from roughly minus two- billion to roughly plus two-billion. Every whole number between these extremes can be represented using an integer. Integers are used almost exclusively for counting purposes (as opposed to calculations). Doubles are numeric variables which can store numbers with decimals. Doubles can be used to represent numbers as large as 1.8×10308 and as small as 5.0×10-324, though in practise the range of numbers which can be accurately represented is much smaller. Those of you who are unfamiliar with scientific notation need not to worry, I shall not make a habit out of this. It is enough to know that the numeric range of doubles is truly enormous. he set of all possible Double and Integer numbers is not continuous; it has gaps. There exists no Integer between zero and one and there exists no Double between zero and 5.0×10-324. The fact that the size of the gap is so much smaller with Doubles is only because we’ve picked a number close to zero. As we move towards bigger and bigger numbers, the gaps between two adjacent Double values will become bigger as well and as we approach the limits of the range, the gaps are big enough to fit the Milky Way. 2×10300 minus one billion is still 2×10300, so beware when using extremely large numbers. Normally, we never stray into the regions where 32-bit computing starts to break down, we tend to restrict ourselves to numbers we can actually cope with. The VBScript syntax for working with numeric variables should be very familiar: ```vb x = 15 + 26 # x equals 41 x = 15 + 26 * 2.33 # x equals 75.58 x = math.sin(15 + 26) + math.sqrt(2.33) # x equals 1.368 ``` You can use the `Rhino.Print()` method to display the result of these computations. `Rhino.Print()` will display the value in the command-line: ``` x = 2 * Sin(15 + 26) + Log(55) Rhino.Print x ``` Of course you can also use numeric variables on the right hand side of the equation: ``` x = x + 1 x = Sin(y) + Sqr(0.5 * y) ``` The first line of code will increment the current value of x by one, the second line will assign a value to x which depends on the value of y. If *y* equals 34 for example, *x* will become *4.65218831173768+. ### 2.3.2 Booleans umeric variables can store a whole range of different numbers. Boolean variables can only store two values mostly referred to as Yes or No, True or False. Obviously we never use booleans to perform calculations because of their limited range. We use booleans to evaluate conditions... remember? ``` You have to be taller than 1.5m to ride the roller coaster. ``` "taller than 1.5m" is the condition in this sentence. This condition is either True or False. You are either taller than 1.5m or you are not. Since most of the Flow-control code in a script is based on conditional statements, booleans play a very important role. Let’s take a look at the looping example: ``` Keep adding milk until the dough is kneadable. ``` The condition here is that the dough has to be kneadable. Let’s assume for the moment that we added something (an algorithm) to our script that could evaluate the current consistency of the dough. Then our first step would be to use this algorithm so we would know whether or not to add milk. If our algorithm returns False (I.e. "the dough isn’t kneadable") then we will have to add some milk. After we added the milk we will have to ask again and repeat these steps until the algorithm returns True (the dough is kneadable). Then we will know there is no more milk needed and that we can move on to the next step in making our Apfelstrudel. The example on page 5 used a VBScript function which returns a boolean value: ``` IsNull(ObjectID) ``` This method requires us to pass in a variable -any variable- and it will return True if that variable contains no data. If `IsNull()` returns `True` it means that Rhino was unable to successfully complete the task we assigned it, which in turn indicates something somewhere went astray. In VBScript we never write "0" or "1" or "Yes" or "No", we always use "True" or "False". Please note that there is no need to compare the result of IsNull() to True or False: ``` If IsNull(curve) = True Then… ``` This is unnecessary code. Something which is already True does not need to be compared to True in order to become really True. ### 2.3.3 Strings Strings are used to store text. Whenever you add quotes around stuff in VBScript, it automatically becomes a String. So if we encapsulate a number in quotes, it will become text: ``` variable1 = 5 variable2 = "5" ``` You could print these variables to the Rhino command history and they would both look like 5, but the String variable behaves differently once we start using it in calculations: ``` Rhino.Print (variable1 + variable1) » Results in 10 Rhino.Print (variable2 + variable2) » Results in 55 ``` In the first line, the plus-sign recognizes the variables on either side as numerical ones and it will perform a numeric addition (5 + 5 = 10). On the second line however, the variables on either side are Strings and the plus sign will concatenate them (I.e. append the one on the right to the one on the left). You must always pay attention to what type of variable you are using. When you need to store text, you have no choice but to use Strings. Strings are not limited length-wise (well, they are, but my guess is you will not run into the two-billion characters limit anytime soon). The syntax for Strings is quite simple, but working with Strings can involve some very tricky code. For the time being we’ll only focus on simple operations such as assignment and concatenation: ``` a = "Apfelstrudel" » Apfelstrudel a = "Apfel" & "strudel" » Apfelstrudel a = "4" & " " & "Apfelstrudel" » 4 Apfelstrudel a = "The square root of 2.0 = " & Sqr(2.0) » The square root of 2.0 = 1.4142135623731 ``` Internally, a String is stored as a series of characters. Every character (or 'char') is taken from the Unicode table, which stores a grand total of ~100.000 different characters. The index into the unicode table for the question mark for example is 63, lowercase e is 101 and the blank space is 32: Further down the road we'll be dealing with some advanced String functions which will require a basic understanding of how Strings work, but while we are still just using the simple stuff, it's good enough to know it just works the way you expect it to. Note that in VBScript we can append numeric values to Strings, but not the other way around. The ampersand sign (&) is used to join several variables into a single String. You could also use the plus sign to do this, but I prefer to restrict the usage of + to numeric operations only. When using & you can treat numeric variables as Strings: ``` a = 5 + 7 » a equals 12 b = 5 & 7 » b equals 57 ``` Strings are used heavily in RhinoScript since object IDs are always written as strings. Object IDs are those weird codes that show up in the Object Property Details: D7EFCF0A-DB47-427D-9B6B-44EC0670C573. IDs are designed to be absolutely unique for every object which will ever exist in this universe, which is why we can use them to safely and unambiguously identify objects in the document. ### 2.3.4 None Variable Whenever we ask Rhino a question which might not have an answer, we need a way for Rhino to say "I don't know". Using the example on page 5: ```VB curve = Rhino.AddLine(Array(0,0,0), Array(somenumber,0,0)) ``` It is not a certainty that a curve was created. If the user enters zero when he is asked to supply the value for somenumber, then the startpoint of the line would be coincident with the endpoint. Rhino does not like zero-length lines and will not add the object to the document. This in turn means that the return value of Rhino.AddLine() is not a valid object ID. Almost all methods in Rhino will return a Null variable if they fail, this way we can add error-checks to our script and take evasive action when something goes wrong. Every variable in a script can become a Null variable and we check them with the IsNull() function: ```VB curve = Rhino.AddLine(Array(0,0,0), Array(somenumber,0,0)) If IsNull(curve) Then Rhino.Print "Something went terribly wrong!" End If ``` IsNull() will pop up a lot in examples to come, and it is always to check whether or not something went according to plan. ### 2.3.5 Using Variables Whenever we intend to use variables in a script, we have to declare them first. When your boss asks you to deliver a package to Mr. Robertson, your first reaction is probably "who on earth is Mr. Robertson?". The VBScript interpreter is not that different from you or me. It likes to be told in advance what all those words mean you are about to fling at it. So when we write: ``` a = "Apfelstrudel" ``` We should not be surprised when we are asked "what on earth is a?". This line of code assigns a value to a variable which has not been declared. We normally declare a variable using the Dim keyword. The only exception to this rule is if we want to declare global variables. But we don't yet. Whenever a variable is declared, it receives a unique name, a scope and a default value. The name you get to pick yourself. In the example above we have used a, which is not the best of all possible choices. For one, it doesn't tell us anything about what the variable is used for or what kind of data it contains. A better name would be strFood. The str prefix indicates that we are dealing with a String variable here and the Food bit is hopefully fairly obvious. A widely used system for variable prefixes is as follows: Don't worry about all those weird variable types, some we will get to in later chapters, others you will probably never use. The scope (sometimes called "lifetime") of a variable refers to the region of the script where it is accessible. Whenever you declare a variable inside a function, only that one function can read and write to it. Variables go 'out of scope' whenever their containing function terminates. 'Lifetime' is not a very good description in my opinion, since some variables may be very much alive, yet unreachable due to being in another scope. But we'll worry about scopes once we get to function declarations. For now, let's just look at an example with proper variable declaration: ```vb Dim strComplaint, strNag, strFood strComplaint = "I don't like " strFood = "Apfelstrudel. " strNag = "Can I go now?" Call Rhino.Print(strComplaint & strFood & strNag) ``` As you will have noticed, we can declare multiple variables using a single Dim keyword if we comma-separate them. Though technically you could jam all your variable declarations onto a single line, it is probably a good idea to only group comparable variables together. Incidentally, the default value of all variables is always a specially reserved value called vbEmpty. It means the variable contains no data and it cannot be used in operations. Before you can use any of your variables, you must first assign them a value. Now, high time for an example. We'll be using the macro from page 2, but we'll replace some of the hard coded numbers with variables for added flexibility. This script looks rather intimidating, but keep in mind that the messy looking bits (line 10 and beyond) are caused by the script trying to mimic a macro, which is a bit like trying to drive an Aston-Martin down the sidewalk. Usually, we talk to Rhino directly without using the command-line and the code looks much friendlier: ```vb Dim dblMajorRadius, dblMinorRadius Dim intSides dblMajorRadius = Rhino.GetReal("Major radius", 10.0, 1.0, 1000.0) dblMinorRadius = Rhino.GetReal("Minor radius", 2.0, 0.1, 100.0) intSides = Rhino.GetInteger("Number of sides", 6, 3, 20) Dim strPoint1, strPoint2 strPoint1 = " w" & dblMajorRadius & ",0,0" strPoint2 = " w" & (dblMajorRadius + dblMinorRadius) & ",0,0" Rhino.Command "_SelNone" Rhino.Command "_Polygon _NumSides=" & intSides & " w0,0,0" & strPoint1 Rhino.Command "_SelLast" Rhino.Command "-_Properties _Object _Name Rail _Enter _Enter" Rhino.Command "_SelNone" Rhino.Command "_Polygon _NumSides=" & intSides & strPoint1 & strPoint2 Rhino.Command "_SelLast" Rhino.Command "_Rotate3D w0,0,0 w1,0,0 90" Rhino.Command "-_Properties _Object _Name Profile _Enter _Enter" Rhino.Command "_SelNone" Rhino.Command "-_Sweep1 -_SelName Rail -_SelName Profile _Enter _Closed=Yes Enter" Rhino.Command "-_SelName Rail" Rhino.Command "-_SelName Profile" Rhino.Command "_Delete" ```
Line Description
1...2 Here we declare three variables. By the looks of it two doubles and one integer (prefixes, prefixes!).
4 This is where we ask the user to enter a number value ("Real" is another word for "Double"). We supply the Rhino.GetReal() method with four fixed values, one string and three doubles. The string will be displayed in the command-line and the first double (10.0) will be available as the default option: We're also limiting the numeric domain to a value between one and a thousand. If the user attempts to enter a larger number, Rhino will claim it's too big:
5...6 These lines are fairly similar. On line 7 we ask the user for an integer instead of a double.
8 We're declaring two string variables, which will be used to store a text representation of a coordinate. Since we're going to use these coordinates more than once I thought it prudent to create them ahead of time so we don't have to concatenate the strings over and over again..
9...10 On these lines we're creating the strings, based on the values of `dblMajorRadius` and `dblMinorRadius`. If we assume the user has chosen the default values in all cases, `dblMajorRadius` will be 10.0 and `dblMinorRadius` will be 2.0, which means that `strPoint2` will look like " w12,0,0".
12...25 This is the same as the macro on page 3, except that we've replaced some bits with variables and there are three extra lines at the bottom which get rid of the construction geometry (so we can run the script more than once without it breaking down).
## Next Steps There are the basics of the RhinoScript datastructures, next learn the [script anatomy](/guides/rhinoscript/primer-101/3-script-anatomy/). -------------------------------------------------------------------------------- # Accessing Rendering Assets Source: https://developer.rhino3d.com/en/guides/opennurbs/accessing-rendering-assets/ OpenNURBS and Rhino3dm provide direct access to rendering information without the need for Rhino. OpenNURBS and Rhino3dm now provide direct access to rendering information without the need for Rhino. (Prior to this, the only way to access this information outside of Rhino was by getting the data as XML and parsing it.) This guide outlines classes used to access these rendering assets and provides examples. ## Overview In C++, the following objects are accessible through the use of `ONX_Model`. In C# the equivalent functionality is provided by `File3dm` which under the hood is a wrapper around `ONX_Model`.
C/C++ C# Equivalent Purpose
ON_RenderContent File3dmRenderContent Provides access to generic render content settings.
ON_RenderMaterial File3dmRenderMaterial Provides access to settings specific to render materials.
ON_RenderEnvironment File3dmRenderEnvironment Provides access to settings specific to render environments.
ON_RenderTexture File3dmRenderTexture Provides access to settings specific to render textures.
ON_Decals Rhino.Render.Decals Provides access to a collection of decals stored on object attributes.
ON_Decal Rhino.Render.Decal Provides access to settings for an individual decal in the collection.
ON_Dithering Rhino.Render.Dithering Provides access to dithering settings.
ON_EmbeddedFile File3dmEmbeddedFile Provides access to embedded texture files.
ON_GroundPlane Rhino.Render.GroundPlane Provides access to ground plane settings.
ON_LinearWorkflow Rhino.Render.LinearWorkflow Provides access to gamma and linear workflow settings.
ON_RenderChannels Rhino.Render.RenderChannels Provides access to render channels settings.
ON_SafeFrame Rhino.Render.SafeFrame Provides access to safe frame settings.
ON_Skylight Rhino.Render.Skylight Provides access to skylighting settings.
ON_Sun Rhino.Render.Sun Provides access to sun settings and sun position calculations.
ON_PostEffects Rhino.Render.PostEffects.PostEffectCollection Provides access to the list of post effects.
ON_PostEffect Rhino.Render.PostEffects.PostEffectData Provides access to settings for an individual post effect in the list.
## Decals To access [decals](/guides/cpp/what-is-the-rdk/#decals) you need to get the `3dmObjectAttributes` object for the model geometry component that you are interested in and then get the decal collection from its attributes. You can then iterate over all the decals in the collection and get and set their properties: ```cs var file3dm = File3dm.Read(filename_in); var e = file3dm.Manifest.GetEnumerator( Rhino.DocObjects.ModelComponentType.ModelGeometry); while (e.MoveNext()) { if (e.Current is File3dmObject obj) { foreach (var decal in obj.Attributes.Decals) { Console.WriteLine(" Mapping: {0}", decal.DecalMapping); Console.WriteLine(" Projection: {0}", decal.DecalProjection); Console.WriteLine(" Origin: {0}", decal.Origin); Console.WriteLine(" Transparency: {0}", decal.Transparency); } } } ``` ```py import _rhino3dm model = _rhino3dm.File3dm.Read(filename_in) for obj in model.Objects: for decal in obj.Attributes.Decals: print(decal.Mapping) print(decal.Projection) print(decal.Origin) print(decal.Transparency) print() ``` ```js // node.js import * as fs from 'fs' //only if running in node.js import rhino3dm from 'rhino3dm' const rhino = await rhino3dm() const buffer = fs.readFileSync( filename_in ) /* if you are running this in a browser, comment out the first line (the fs import) and replace the above line with these two lines: const file = await fetch( filename_in ) const buffer = await file.arrayBuffer() */ const arr = new Uint8Array( buffer ) const file3dm = rhino.File3dm.fromByteArray( arr ) const objects = file3dm.objects() for( let i = 0; i < objects.count; i ++ ) { const attributes = objects.get( i ).attributes() for( let j = 0; j < attributes.decals().count; j ++ ) { const decal = attributes.decals().get( j ) console.log( `Decal Mapping: ${decal.mapping.constructor.name}` ) console.log( `Decal Projection: ${decal.projection.constructor.name}`) console.log( `Decal Origin: ${decal.origin}` ) console.log( `Decal Transparency: ${decal.transparency}` ) } } ``` ```cpp ONX_Model model; model.Read(filename_in); ONX_ModelComponentIterator it(model, ON_ModelComponent::Type::ModelGeometry); const auto* component = it.FirstComponent(); while (nullptr != component) { auto* mgc = ON_ModelGeometryComponent::Cast(component); if (nullptr != mgc) { auto* attr = mgc->ExclusiveAttributes(); if (nullptr != attr) { const auto& da = attr->GetDecalArray(); for (int i = 0; i < da.Count(); i++) { auto* decal = da[i]; const auto mapping = decal->Mapping(); const auto origin = decal->Origin(); } } } component = it.NextComponent(); } ``` ## Dithering [Dithering](/guides/cpp/rdk-dithering-classes) information is accessed through `3dmRenderSettings`. To access it, you need to get the render settings and call its `Dithering` method/property to get a reference to a `Dithering` object. You can then read or write the settings by calling the various methods on the `Dithering` object. Any changes you make will persist and can be saved to a 3dm file: ```cs var file3dm = File3dm.Read(filename_in); var dit = file3dm.Settings.RenderSettings.Dithering; Console.WriteLine("Enabled: {0}", dit.Enabled); Console.WriteLine("Method: {0}", dit.Method); dit.Enabled = true; dit.Method = Dithering.Methods.FloydSteinberg; ``` ```py import _rhino3dm model = _rhino3dm.File3dm.Read(filename_in) dit = model.Settings.RenderSettings.Dithering print(dit.Enabled) print(dit.Method) dit.Enabled = True dit.Method = _rhino3dm.DitheringMethods.FloydSteinberg ``` ```js // node.js import * as fs from 'fs' //only if running in node.js import rhino3dm from 'rhino3dm' const rhino = await rhino3dm() const buffer = fs.readFileSync( filename_in ) /* if you are running this in a browser, comment out the first line (the fs import) and replace the above line with these two lines: const file = await fetch( filename_in ) const buffer = await file.arrayBuffer() */ const arr = new Uint8Array( buffer ) const file3dm = rhino.File3dm.fromByteArray( arr ) const dithering = file3dm.settings().renderSettings().dithering console.log( `Dithering Enabled: ${ dithering.enabled }` ) console.log( `Dithering Method: ${ dithering.method.constructor.name }` ) dithering.enabled = true dithering.method = rhino.DitheringMethods.SimpleNoise console.log( `Dithering Enabled: ${ dithering.enabled }` ) console.log( `Dithering Method: ${ dithering.method.constructor.name }` ) ``` ```cpp ONX_Model model; model.Read(filename_in); auto& dit = model.m_settings.m_RenderSettings.Dithering(); const auto on = dit.Enabled(); const auto method = dit.Method(); dit.SetEnabled(true); dit.SetMethod(ON_Dithering::Methods::FloydSteinberg); model.Write(filename_out); ``` ## Ground Plane [Ground plane](/guides/cpp/rdk-ground-plane-classes/) information is accessed through `3dmRenderSettings`. To access it, you need to get the render settings and call its `GroundPlane` method/property to get a reference to a `GroundPlane` object. You can then read or write the settings by calling the various methods on the `GroundPlane` object. Any changes you make will persist and can be saved to a 3dm file: ```cs var file3dm = File3dm.Read(filename_in); var gp = file3dm.Settings.RenderSettings.GroundPlane; Console.WriteLine("Enabled: {0}", gp.Enabled); Console.WriteLine("Altitude: {0}", gp.Altitude); gp.Enabled = false; model.Write(filename_out); ``` ```py import _rhino3dm model = _rhino3dm.File3dm.Read(filename_in) gp = model.Settings.RenderSettings.GroundPlane print(gp.Enabled) print(gp.Altitude) gp.Enabled = False ``` ```js // node.js import * as fs from 'fs' //only if running in node.js import rhino3dm from 'rhino3dm' const rhino = await rhino3dm() const buffer = fs.readFileSync( filename_in ) /* if you are running this in a browser, comment out the first line (the fs import) and replace the above line with these two lines: const file = await fetch( filename_in ) const buffer = await file.arrayBuffer() */ const arr = new Uint8Array( buffer ) const file3dm = rhino.File3dm.fromByteArray( arr ) const gp = file3dm.settings().renderSettings().groundPlane console.log( `Ground Plane Enabled: ${ gp.enabled }` ) console.log( `Ground Plane Altitude: ${ gp.altitude }` ) gp.enabled = false ``` ```cpp ONX_Model model; model.Read(filename_in); auto& gp = model.m_settings.m_RenderSettings.GroundPlane(); const auto on = gp.Enabled(); const auto alt = gp.Altitude(); gp.SetEnabled(false); model.Write(filename_out); ``` ## Linear Workflow [Linear workflow](/guides/cpp/rdk-linear-workflow-classes/) information is accessed through `3dmRenderSettings`. To access it, you need to get the render settings and call its `LinearWorkflow` method/property to get a reference to a `LinearWorkflow` object. You can then read or write the settings by calling the various methods on the `LinearWorkflow` object. Any changes you make will persist and can be saved to a 3dm file: ```cs var file3dm = File3dm.Read(filename_in); var lw = file3dm.Settings.RenderSettings.LinearWorkflow; Console.WriteLine("PostProcessGammaOn: {0}", lw.PostProcessGammaOn); Console.WriteLine("PostProcessGamma: {0}", lw.PostProcessGamma); lw.PostProcessGamma = 3.4f; model.Write(filename_out); ``` ```py import _rhino3dm model = _rhino3dm.File3dm.Read(filename_in) lw = model.Settings.RenderSettings.LinearWorkflow print(lw.PostProcessGammaOn) print(lw.PostProcessGamma) lw.PostProcessGamma = 3.4 ``` ```js // node.js import * as fs from 'fs' //only if running in node.js import rhino3dm from 'rhino3dm' const rhino = await rhino3dm() const buffer = fs.readFileSync( filename_in ) /* if you are running this in a browser, comment out the first line (the fs import) and replace the above line with these two lines: const file = await fetch( filename_in ) const buffer = await file.arrayBuffer() */ const arr = new Uint8Array( buffer ) const file3dm = rhino.File3dm.fromByteArray( arr ) const lw = file3dm.settings().renderSettings().linearWorkflow console.log( `Linear Workflow Post Proces Gamma On: ${ lw.postProcessGammaOn }` ) console.log( `Linear Workflow Post Proces Gamma: ${ lw.postProcessGamma }` ) lw.PostProcessGamma = 3.4 ``` ```cpp ONX_Model model; model.Read(filename_in); auto& lw = model.m_settings.m_RenderSettings.LinearWorkflow(); const auto on = lw.PostProcessGammaOn(); const auto gamma lw.PostProcessGamma(); lw.SetPostProcessGamma(3.4f); model.Write(filename_out); ``` ## Render Channels Render channel information is accessed through `3dmRenderSettings`. To access it, you need to get the render settings and call its `RenderChannels` method/property to get a reference to a `RenderChannels` object. You can then read or write the settings by calling the various methods on the `RenderChannels` object. Any changes you make will persist and can be saved to a 3dm file: ```cs var file3dm = File3dm.Read(filename_in); var lw = file3dm.Settings.RenderSettings.RenderChannels; Console.WriteLine("Mode: {0}", rch.Mode); rch.Mode = RenderChannels.Modes.Custom; model.Write(filename_out); ``` ```py import _rhino3dm model = _rhino3dm.File3dm.Read(filename_in) rch = model.Settings.RenderSettings.RenderChannels print(rch.Mode) rch.Mode = _rhino3dm.RenderChannelsModes.Custom ``` ```js // node.js import * as fs from 'fs' //only if running in node.js import rhino3dm from 'rhino3dm' const rhino = await rhino3dm() const buffer = fs.readFileSync( filename_in ) /* if you are running this in a browser, comment out the first line (the fs import) and replace the above line with these two lines: const file = await fetch( filename_in ) const buffer = await file.arrayBuffer() */ const arr = new Uint8Array( buffer ) const file3dm = rhino.File3dm.fromByteArray( arr ) const rch = file3dm.settings().renderSettings().renderChannels console.log( `renderChannels mode: ${ rch.mode.constructor.name }` ) console.log( `renderChannels customIds: ${ rch.customIds }` ) rch.mode = rhino.RenderChannelsModes.Custom ``` ```cpp ONX_Model model; model.Read(filename_in); auto& rch = model.m_settings.m_RenderSettings.RenderChannels(); const auto mode = rch.Mode(); rch.SetMode(ON_RenderChannels::Modes::Automatic); model.Write(filename_out); ``` ## Safe Frame [Safe Frame](/guides/cpp/rdk-safe-frame-classes/) information is accessed through `3dmRenderSettings`. To access it, you need to get the render settings and call its `SafeFrame` method/property to get a reference to a `SafeFrame` object. You can then read or write the settings by calling the various methods on the `SafeFrame` object. Any changes you make will persist and can be saved to a 3dm file: ```cs var file3dm = File3dm.Read(filename_in); var sf = file3dm.Settings.RenderSettings.SafeFrame; Console.WriteLine("On: {0}", sf.Enabled); Console.WriteLine("ActionFrameXScale: {0}", sf.ActionFrameXScale); sf.ActionFrameXScale = 0.45; model.Write(filename_out); ``` ```py import _rhino3dm model = _rhino3dm.File3dm.Read(filename_in) sf = model.Settings.RenderSettings.SafeFrame print(sf.Enabled) print(sf.ActionFrameXScale) sf.ActionFrameXScale = 0.45 ``` ```js // node.js import * as fs from 'fs' //only if running in node.js import rhino3dm from 'rhino3dm' const rhino = await rhino3dm() const buffer = fs.readFileSync( filename_in ) /* if you are running this in a browser, comment out the first line (the fs import) and replace the above line with these two lines: const file = await fetch( filename_in ) const buffer = await file.arrayBuffer() */ const arr = new Uint8Array( buffer ) const file3dm = rhino.File3dm.fromByteArray( arr ) const sf = file3dm.settings().renderSettings().safeFrame console.log( `Safe Frame Enabled: ${ sf.enabled }` ) console.log( `Safe Frame ActionFrameXScale: ${ sf.actionFrameXScale }` ) sf.actionFrameXScale = 0.45 ``` ```cpp ONX_Model model; model.Read(filename_in); auto& sf = model.m_settings.m_RenderSettings.SafeFrame(); const auto on = sf.On(); const auto xs = sf.ActionFrameXScale(); sf.SetOn(true); sf.SetActionFrameXScale(0.1f); model.Write(filename_out); ``` ## Skylight [Skylight](/guides/cpp/rdk-skylight-classes/) information is accessed through `3dmRenderSettings`. To access it, you need to get the render settings and call its `Skylight` method/property to get a reference to a `Skylight` object. You can then read or write the settings by calling the various methods on the `Skylight` object. Any changes you make will persist and can be saved to a 3dm file: ```cs var file3dm = File3dm.Read(filename_in); var sl = file3dm.Settings.RenderSettings.Skylight; Console.WriteLine("Enabled: {0}", sl.Enabled); Console.WriteLine("ShadowIntensity: {0}", sl.ShadowIntensity); sl.ShadowIntensity = 1.4; model.Write(filename_out); ``` ```py import _rhino3dm model = _rhino3dm.File3dm.Read(filename_in) sl = model.Settings.RenderSettings.Skylight print(sl.Enabled) print(sl.ShadowIntensity) sl.ShadowIntensity = 1.4 ``` ```js // node.js import * as fs from 'fs' //only if running in node.js import rhino3dm from 'rhino3dm' const rhino = await rhino3dm() const buffer = fs.readFileSync( filename_in ) /* if you are running this in a browser, comment out the first line (the fs import) and replace the above line with these two lines: const file = await fetch( filename_in ) const buffer = await file.arrayBuffer() */ const arr = new Uint8Array( buffer ) const file3dm = rhino.File3dm.fromByteArray( arr ) const sl = file3dm.settings().renderSettings().skylight console.log( `Skylight Enabled: ${ sl.enabled }` ) console.log( `Skylight Shadow Intensity: ${ sl.shadowIntensity }` ) sl.shadowIntensity = 0.45 ``` ```cpp ONX_Model model; model.Read(filename_in); auto& sl = model.m_settings.m_RenderSettings.Skylight(); const auto on = sl.Enabled(); const auto si = sl.ShadowIntensity(); sl.SetEnabled(false); sl.SetShadowIntensity(1.4); model.Write(filename_out); ``` ## Sun [Sun](/guides/cpp/rdk-sun-classes/) information is accessed through `3dmRenderSettings`. To access it, you need to get the render settings and call its `Sun` method/property to get a reference to a `Sun` object. You can then read or write the settings by calling the various methods on the `Sun` object. Any changes you make will persist and can be saved to a 3dm file: ```cs var file3dm = File3dm.Read(filename_in); var sun = file3dm.Settings.RenderSettings.Sun; Console.WriteLine("On: {0}", sun.Enabled); Console.WriteLine("Time zone: {0}", sun.TimeZone); var dt = sun.GetDateTime(DateTimeKind.Local); var format = "LocalDateTime: {0}.{1}.{2} {3}:{4}"; var message = string.Format(CultureInfo.InvariantCulture, format, dt.Year, dt.Month, dt.Day, dt.Hour, dt.Minute); Console.WriteLine(message); Console.WriteLine("Place sun observer at the Greenwich observatory in London"); sun.Latitude = 51.4769; sun.Longitude = -0.0005; sun.TimeZone = 0.0; model.Write(filename_out); ``` ```py import _rhino3dm model = _rhino3dm.File3dm.Read(filename_in) sun = model.Settings.RenderSettings.Sun; print(sun.EnableOn); print(sun.TimeZone); print(sun.Year, sun.Month, sun.Day, sun.Hours) print(sun.Azimuth) print("Place sun observer at the Greenwich observatory in London") sun.Latitude = 51.4769; sun.Longitude = -0.0005; print(sun.Azimuth) sun.TimeZone = 0.0; ``` ```js // node.js import * as fs from 'fs' //only if running in node.js import rhino3dm from 'rhino3dm' const rhino = await rhino3dm() const buffer = fs.readFileSync( filename_in ) /* if you are running this in a browser, comment out the first line (the fs import) and replace the above line with these two lines: const file = await fetch( filename_in ) const buffer = await file.arrayBuffer() */ const arr = new Uint8Array( buffer ) const file3dm = rhino.File3dm.fromByteArray( arr ) const sun = file3dm.settings().renderSettings().sun console.log( `Sun enableOn: ${ sun.enableOn }` ) console.log( `Sun timeZone: ${ sun.timeZone }` ) console.log( `Sun azimuth: ${ sun.azimuth }` ) console.log( `Sun year: ${ sun.year }` ) console.log( `Sun month: ${ sun.month }` ) console.log( `Sun day: ${ sun.day }` ) console.log( `Sun hours: ${ sun.hours }` ) console.log('Place sun observer at the Greenwich observatory in London') sun.latitude = 51.4769 sun.longitude = -0.0005 console.log( `Sun azimuth: ${ sun.azimuth }` ) sun.timeZone = 0.0; ``` ```cpp ONX_Model model; model.Read(filename_in); auto& sun = model.m_settings.m_RenderSettings.Sun(); const auto on = sun.EnableOn(); const auto tz = sun.TimeZone(); sun.SetEnableOn(true); sun.SetTimeZone(2.0); sun.SetLocalDateTime(2022, 5, 1, 12.51); model.Write(filename_out); ``` The sun object is able to do sun calculations and, in fact, will automatically do them whenever "manual control" is disabled (this is the default). Whenever certain properties are modified, the sun object will automatically compute the sun’s azimuth and altitude in the sky. The calculation is deferred until you actually ask for the azimuth or altitude whereupon it is performed once and cached until the next time something changes. The properties that cause the calculation to be performed are the direction of north and also any properties that change the observer’s date, time, location, time zone or daylight saving values. When manual control is enabled, the azimuth and altitude values are under the control of the programmer and no calculations are performed. The direction of north and the observer’s latitude and longitude are stored inside an earth anchor point object. This object is inside the 3dmSettings of the `ONX_Model`/`File3dm` and is therefore loaded and saved with the model. ## Post Effects [Post effect](/guides/cpp/rdk-post-effects/) information is accessed through `3dmRenderSettings`. To access it, you need to get the render settings and call its `PostEffects` method/property to get a reference to a post effect collection object. This object allows you to iterate over the post effects, change the order of post effects and get and set the post effect selection. Iterating over the post effects retrieves individual post effect objects. You can access and change post effect properties by calling the various methods on the post effect object. Any changes you make will persist and can be saved to a 3dm file: ```cs var file3dm = File3dm.Read(filename_in); var post_effects = file3dm.Settings.RenderSettings.PostEffects; foreach (var post_effect in post_effects) { Console.WriteLine(pep.LocalName); Console.WriteLine("--------------------"); Console.WriteLine("Id : {0}", pep.Id); Console.WriteLine("Type : {0}", pep.Type); Console.WriteLine("On : {0}", pep.On); Console.WriteLine("Shown: {0}", pep.Shown); // If the post effect has a radius parameter, change its value. var p = pep.GetParameter("radius"); if (p != null) { pep.SetParameter("radius", 0.33); } } ``` ```py import _rhino3dm model = _rhino3dm.File3dm.Read(filename_in) post_effects = model.Settings.RenderSettings.PostEffects for pe in post_effects : print(pe.LocalName) print("--------------------") print("Id : ", pe.Id) print("Type : ", pe.Type) print("On : ", pe.On) print("Shown: ", pe.Shown) print("") ``` ```js // node.js import * as fs from 'fs' //only if running in node.js import rhino3dm from 'rhino3dm' const rhino = await rhino3dm() const buffer = fs.readFileSync( filename_in ) /* if you are running this in a browser, comment out the first line (the fs import) and replace the above line with these two lines: const file = await fetch( filename_in ) const buffer = await file.arrayBuffer() */ const arr = new Uint8Array( buffer ) const file3dm = rhino.File3dm.fromByteArray( arr ) const pes = file3dm.settings().renderSettings().postEffects for( let i = 0; i < pes.count; i ++ ) { const pe = pes.get( i ) console.log( `Post Effect localName: ${ pe.localName }` ) console.log( `Post Effect id: ${ pe.id }` ) console.log( `Post Effect type: ${ pe.type.constructor.name }` ) console.log( `Post Effect on: ${ pe.on }` ) console.log( `Post Effect shown: ${ pe.shown }` ) console.log() } ``` ```cpp ONX_Model model; model.Read(filename_in); auto& post_effects = model.m_settings.m_RenderSettings.PostEffects(); ON_SimpleArray post_effect_array; post_effects.GetPostEffects(ON_PostEffect::Types::Early, post_effect_array); for (int i = 0; i < post_effect_array.Count(); i++) { auto& post_effect = *post_effect_array[i]; if (post_effect.LocalName().CompareNoCase(L"bloom") == 0) { post_effect.SetParameter(SS_PEP_BLOOM_INTENSITY, 123.0f); const auto v = post_effect.GetParameter(SS_PEP_BLOOM_INTENSITY); ASSERT(v.AsFloat() == 123.0f); } } model.Write(filename_out); ``` ## Render Content ["Render content"](/guides/cpp/rdk-render-content/) is an abstraction that represents one of three different kinds of object. A render content can be a: 1. Render Material 1. Render Environment 1. Render Texture Render contents are model components and, in C++, can be accessed by iterating over the model components in a model. In C# you iterate over lists of each kind. Each kind is a subclass of the render content class. The base class contains all methods/properties that are common to all three kinds, and each subclass contains only the methods/properties specific to that kind. ```cs var file3dm = File3dm.Read(filename_in); foreach (var rm in file3dm.RenderMaterials) { Console.WriteLine("{0}", rm.TypeName); IConvertible p = rm.GetParameter("ior"); if (p != null) { Console.WriteLine("Setting IOR and transparency"); rm.SetParameter("ior", 2.5); rm.SetParameter("transparency", 0.5); } } foreach (var re in file3dm.RenderEnvironments) { var env = re.ToEnvironment(); var col = env.BackgroundColor; Console.WriteLine("Color: {0}, {1}, {2}", col.R, col.G, col.B); } foreach (var rt in file3dm.RenderTextures) { var tex = rt.ToTexture(); Console.WriteLine("{0}", tex.FileReference.FullPath); } ``` ```py import _rhino3dm model = _rhino3dm.File3dm.Read(filename_in) for rc in model.RenderContent : if isinstance(rc, _rhino3dm.RenderMaterial) : print(rc.TypeName) print(rc.GetParameter("ior")) print("Setting IOR and transparency") _ = rc.SetParameter("ior", "2.5") _ = rc.SetParameter("transparency", "0.5") if isinstance(rc, _rhino3dm.RenderEnvironment) : e = rc.ToEnvironment() print(e.BackgroundColor) if isinstance(rc, _rhino3dm.RenderTexture) : print(rc.Filename) ``` ```js // node.js import * as fs from 'fs' //only if running in node.js import rhino3dm from 'rhino3dm' const rhino = await rhino3dm() const buffer = fs.readFileSync( filename_in ) /* if you are running this in a browser, comment out the first line (the fs import) and replace the above line with these two lines: const file = await fetch( filename_in ) const buffer = await file.arrayBuffer() */ const arr = new Uint8Array( buffer ) const file3dm = rhino.File3dm.fromByteArray( arr ) for ( let i = 0; i < file3dm.renderContent().count; i ++ ) { const rc = file3dm.renderContent().get(i) switch( rc.kind ) { case 'material': console.log( `Render Material ior: ${ rc.getParameter( "ior" ) }` ) console.log( 'Setting IOR and transparency' ) rc.setParameter( 'ior', '2.5' ) rc.setParameter( 'transparency', '0.5' ) break case 'environment': console.log( `Render Environment Background Color: ${ rc.getParameter( 'background-color' ) }` ) break case 'texture': console.log( `Render Texture FileName: ${ rc.fileName }` ) break } } ``` ```cpp ONX_Model model; model.Read(filename_in); ONX_ModelComponentIterator it(model, ON_ModelComponent::Type::RenderContent); auto* component = it.FirstComponent(); while (nullptr != component) { const auto* mat = ON_RenderMaterial::Cast(component); if (nullptr != mat) { const auto typeName = mat->TypeName(); wprintf(L"Type: %s\n", static_cast(typeName)); const auto v = mat->GetParameter(ON_MATERIAL_IOR); if (!v.IsNull()) { wprintf(L"Setting IOR and transparency\n"); auto* mat_write = const_cast(mat); mat_write->SetParameter(ON_MATERIAL_IOR, 2.5); mat_write->SetParameter(ON_MATERIAL_TRANSPARENCY_AMOUNT, 0.5); } } component = it.NextComponent(); } component = it.FirstComponent(); while (nullptr != component) { auto* env = ON_RenderEnvironment::Cast(component); if (nullptr != env) { const auto on_env = env->ToOnEnvironment(); const auto col = on_env.BackgroundColor(); wprintf(L"Color: %u, %u, %u\n", col.Red(), col.Green(), col.Blue()); } component = it.NextComponent(); } component = it.FirstComponent(); while (nullptr != component) { auto* tex = ON_RenderTexture::Cast(component); if (nullptr != tex) { const auto on_tex = tex->ToOnTexture(); const auto& file = on_tex.m_image_file_reference.FullPath(); wprintf(L"%s\n", static_cast(file)); } component = it.NextComponent(); } ``` ## Embedded Files Embedded Files (not to be confused with Embedded Bitmaps) are files that are embedded inside a 3dm file. When a render texture is file-based (i.e., most non-procedural textures), then by default, Rhino saves a copy of the referenced file in the 3dm file. This can be disabled during Save As by unchecking the Save textures check box. These files can be accessed by the programmer by iterating over model components of type `EmbeddedFile`. Although the embedded file object contains a number of methods for loading and saving files in different ways, iterating over the model is probably the most useful way to use it: ```cs var file3dm = File3dm.Read(filename_in); foreach (var ef in file3dm.EmbeddedFiles) { var dir = System.IO.Path.GetDirectoryName(ef.Filename); var file = System.IO.Path.GetFileName(ef.Filename); var new_file = System.IO.Path.Combine(dir, "CopyOf_" + file); ef.SaveToFile(new_file); } ``` ```py import os import _rhino3dm model = _rhino3dm.File3dm.Read(filename_in) for ef in model.EmbeddedFiles : dir = os.path.dirname(ef.Filename) file = os.path.basename(ef.Filename) new_file = os.path.join(dir, "CopyOf_" + file) ef.Write(new_file) ``` ```js // node.js import * as fs from 'fs' //only if running in node.js import rhino3dm from 'rhino3dm' const rhino = await rhino3dm() const buffer = fs.readFileSync( filename_in ) /* if you are running this in a browser, comment out the first line (the fs import) and replace the above line with these two lines: const file = await fetch( filename_in ) const buffer = await file.arrayBuffer() */ const arr = new Uint8Array( buffer ) const file3dm = rhino.File3dm.fromByteArray( arr ) const embeddedFiles = file3dm.embeddedFiles() for( let i = 0; i < embeddedFiles.count; i ++ ) { const ef = embeddedFiles.get( i ) console.log(`Embedded File fileName: ${file.fileName}`) } ``` ```cpp ONX_Model model; model.Read(filename_in); ONX_ModelComponentIterator it(model, ON_ModelComponent::Type::EmbeddedFile); const auto* component = it.FirstComponent(); while (nullptr != component) { const auto* ef = ON_EmbeddedFile::Cast(component); if (nullptr != ef) { const auto filename = ef->Filename(); const auto dir = ON_FileSystemPath::VolumeAndDirectoryFromPath(filename); const auto file = ON_FileSystemPath::FileNameFromPath(filename, true); const auto new_file = dir + L"CopyOf_" + file; ef->SaveToFile(new_file); } component = it.NextComponent(); } ```d -------------------------------------------------------------------------------- # Brep Data Structure Source: https://developer.rhino3d.com/en/guides/cpp/brep-data-structure/ This guide discusses the Boundary Representation (B-rep) in the context of openNURBS. ## Conceptual diagram ![BRep Data Structure](https://developer.rhino3d.com/images/brep-data-structure-01.png) ## Sample ```cpp //Given a brep and a face index const ON_Brep* brep; const int face_index = 0; ON_SimpleArray face_ti_list; //trims indeces list ON_SimpleArray face_ei_list; //edges indeces list //Get the BrepFace of the given index const ON_BrepFace* face = brep->Face(face_index); if( 0 == face ) return false; //Get the loop of the face for( int fli = 0; fli < face->LoopCount(); fli++ ) { const ON_BrepLoop* loop = face->Loop( fli ); if( 0 == loop ) continue; for( int lti = 0; lti < loop->TrimCount(); lti++ ) { //Find the trim const ON_BrepTrim* trim = loop->Trim( lti ); if( 0 == trim ) continue; face_ti_list.Append( trim->m_trim_index ); //Find the edge of that trim //Each trim has exactly one edge attached to it const ON_BrepEdge* edge = trim->Edge(); if( 0 == edge ) continue; face_ei_list.Append( edge->m_edge_index ); } } ``` -------------------------------------------------------------------------------- # Calling Compute with JavaScript Source: https://developer.rhino3d.com/en/guides/compute/compute-javascript-getting-started/ This guide covers all the necessary tools required to get started with the Rhino Compute Service through JavaScript. By the end of this guide, you should have all the tools installed necessary for using the [Rhino Compute](https://www.rhino3d.com/compute) through JavaScript. The libraries will run on on all major browsers as well as node.js. ## Setting up a Compute Project using JavaScript There are a few client side tools which need to be referenced that are essential to communicate with the Compute server. These include: - **rhino3dm.js** - Is part of the Rhino3dm project. It is a Javascript wrapper and web assembly (WASM) for [openNURBS](https://developer.rhino3d.com/guides/opennurbs/) which contains the functions to read and write Rhino Geometry Objects. - **compute-rhino3d.js** - This is a work in progress package which is meant to add classes available in RhinoCommon, but not available through rhino3dm.js. Compute-rhino3d makes calls into the McNeel Cloud Compute server for these functions. It handles all the transaction authorizations and JSON data conversion. In a browser based application `index.html` would look like this: ``` ``` In the script.js, import the libraries: ``` // Import libraries import rhino3dm from 'rhino3dm' import { RhinoCompute } from 'rhinocompute' // Load rhino3dm const rhino = await rhino3dm() console.log('Loaded rhino3dm.') // Your code ... ``` For a node.js application, first install the libraries with npm: `npm i rhino3dm compute-rhino3d` Then reference them in your script: ``` // Import libraries import rhino3dm from 'rhino3dm' import RhinoCompute from 'compute-rhino3d' // Load rhino3dm const rhino = await rhino3dm() console.log('Loaded rhino3dm.') ``` ## The first use of Compute Examples of using JavaScript to access compute can be found in the [Javascript Sample repo](https://github.com/mcneel/rhino-developer-samples/tree/8/compute/js) # Next Steps *Congratulations!* You have the tools to use [Rhino Compute server](https://www.rhino3d.com/compute). *Now what?* 1. To see the transactional nature of Compute, read through [compute.rhino3d.js](https://files.mcneel.com/rhino3dm/js/latest/compute.rhino3d.js) 1. See a list of the [2400+ API calls](https://compute.rhino3d.com/sdk) available for compute.rhino3d.com. 1. Download the [Compute Samples repo from GitHub](https://github.com/mcneel/rhino-developer-samples/tree/8/compute). 1. The libraries are still very new and changing rapidly. Give them a try or get involved. Ask any questions or share what you are working on the [Compute Discussion Forum](https://discourse.mcneel.com/c/serengeti/compute-rhino3d) -------------------------------------------------------------------------------- # Code-Driven File IO Source: https://developer.rhino3d.com/en/guides/rhinocommon/code-driven-file-io/ This guide gives an overview of using RhinoCommon to drive file format IO with code Prior to Rhino 8, code-driven exporting was done by using the active document and scripting the export command. This was both inefficient and painful to write. Rhino 8 introduces the abililty to read and write files of any format that Rhino supports entirely through code. This includes support for options that need to be applied to properly read or write different files. ## How does it work? Import and export plug-ins are provided an optional dictionary at read and write time. When the dictionary is present, instead of asking for user input, the importers and exporters pay attention to keys and values in the dictionary to make decisions on options for I/O. RhinoCommon provides classes for these options that can be converted into a dictionary that the import and export plug-ins can interpret. Look in the `Rhino.FileIO` namespace for format specific option classes. ## Example: Write an AutoCAD dwg from Rhino This example shows how to write the active `RhinoDoc` to an AutoCAD dwg with options: ```cs var doc = Rhino.RhinoDoc.ActiveDoc; if (doc != null) { // create/set options for exporting to DWG file var options = new Rhino.FileIO.FileDwgWriteOptions(); options.UseLWPolylines = true; options.Version = Rhino.FileIO.FileDwgWriteOptions.AutocadVersion.Acad2000; // convert options into a dictionary var optionsDictionary = options.ToDictionary(); var path = System.Environment.GetFolderPath(System.Environment.SpecialFolder.Desktop); path = System.IO.Path.Combine(path, "sample.dwg"); // export to a file with our options dictionary bool success = doc.Export(path, optionsDictionary); if (success) System.Console.WriteLine("Successfully exported sample.dwg"); else System.Console.WriteLine("Error while trying to export sample.dwg"); } ``` ```py import scriptcontext import Rhino import os if scriptcontext.doc is not None: options = Rhino.FileIO.FileDwgWriteOptions() options.UseLWPolylines = True options.Version = Rhino.FileIO.FileDwgWriteOptions.AutocadVersion.Acad2000 options_dictionary = options.ToDictionary() path = os.path.expanduser("~/Desktop/sample_py.dwg") success = scriptcontext.doc.Export(path, options_dictionary) if success: print("Successfully exported sample.dwg") else: print("Error while trying to export sample.dwg") ``` ## Headless Document Support Headless `RhinoDoc` instances can be used to further streamline the process. Headless documents are `RhinoDoc` instances that never affect the Rhino user interface. Their use can improve performance as none of the geometry in the document needs to be displayed in Rhino. You can either create a headless document from scratch and add geometry to it or import from a file using options. ## Example: Read a SketchUp file into Rhino This example shows how to read a SketchUp skp file and example the geometry using a headless RhinoDoc: ```cs // Create a headless doc and import SketchUp file into it var doc = Rhino.RhinoDoc.CreateHeadless(null); var options = new Rhino.FileIO.FileSkpReadOptions(); options.ImportCurves = false; options.EmbedTexturesInModel = false; var path = System.Environment.GetFolderPath(System.Environment.SpecialFolder.Desktop); path = System.IO.Path.Combine(path, "test.skp"); doc.Import(path, options.ToDictionary()); // Analyze what was read into the doc System.Console.WriteLine($"{doc.Objects.Count} objects"); foreach(var obj in doc.Objects) { var bbox = obj.Geometry.GetBoundingBox(true); System.Console.WriteLine($"Center is {bbox.Center}"); } // done with the doc, free up the memory it is using doc.Dispose(); ``` ```py import Rhino import os doc = Rhino.RhinoDoc.CreateHeadless(None) options = Rhino.FileIO.FileSkpReadOptions() options.ImportCurves = False options.EmbedTexturesInModel = False path = os.path.expanduser("~/Desktop/test.skp") doc.Import(path, options.ToDictionary()) print(f"{doc.Objects.Count} objects") for obj in doc.Objects: bbox = obj.Geometry.GetBoundingBox(True) print(f"Center is {bbox.Center}") doc.Dispose() ``` ## Using Grasshopper with Code Driven File.io When using Grasshopper to read or write files, there are some differences then the above examples. The main difference is Grasshopper is it own document. When reading in a file, objects are then processed thru Grasshopper independantly. And when writing out a file format, then a headless doc needs to be created in the same component that writes the file. See the examples below: Reading in a file format can be done in new Rhino 8 components using the Import File component: [ ](ExportTo3MFV3.gh) [Download Example Grasshopper Definition for Exporting a File](ExportTo3MFV3.gh) Here the file is imported and the objects can then be processed. To write out any of the File formats from Rhino, create headless Doc and then add objects to it. The example below writes a 3MF file using a Python 3 component. Set the script component `objects` input to `List Access` and `GeometryBase` Input Hint: ```py import System import Rhino doc = Rhino.RhinoDoc.CreateHeadless(None) if not objects is None: for o in objects: doc.Objects.Add(o) options = Rhino.FileIO.File3mfWriteOptions() options.Title = title options.Designer = Rhino.RhinoApp.LicenseUserName options.Metadata["Test"] = "Star" options.MoveOutputToPositiveXYZOctant = True; if not path is None: path = System.Environment.ExpandEnvironmentVariables(path) success = doc.Export(path, options.ToDictionary()) print(success) doc.Dispose(); ``` ## Other Use Cases - Batch processing: a directory of files could be read into headless documents and exported to another format. - Grasshopper: Using headless documents in Grasshopper with all Rhino file formats supports allows for processing geometry from different files without forcing the main Rhino active doc to change. -------------------------------------------------------------------------------- # Colors in Python Source: https://developer.rhino3d.com/en/guides/rhinopython/python-rhinoscriptsyntax-color/ This guide provides an overview of a RhinoScriptSyntax Color type in Python. ## Colors Colors in Rhino are represented as zero-based, one-dimensional arrays that contain four values. The first 3 values are the Red, Green and Blue channels. Each channel may contain a value from 0 to 255. The fourth value is the Alpha Channel. This control transparency of the color. 0 is completely transparent and the default value of 255 is completely opaque. ``` color contains (Red, Green, Blue, Alpha) ``` Use the `CreateColor()` function to create a new color structure: ```python import rhinoscriptsyntax as rs color1 = rs.CreateColor(128, 128, 128) # Creates a medium grey color. ``` The `CreateColor()` function assumes the alpha value is 255 by default. ```python import rhinoscriptsyntax as rs col = rs.CreateColor(43,45,56) print (col.R) print (col.G) print (col.B) ``` Unlike many other Rhino types, colors are immutable. This means you cannot set one channel by itself, but must always create a new color when trying to make a color. Setting one channel will not work, for instance `color1.B = 56` will throw an error. Here is a table of commonly used colors: | Color | | Red | | Green | | Blue | | ----------- | ---- | ---: | ---- | ----: | ---- | ---: | | Black | | 0 | | 0 | | 0 | | White | | 255 | | 255 | | 255 | | Medium Gray | | 128 | | 128 | | 128 | | Aqua | | 0 | | 128 | | 128 | | Navy Blue | | 0 | | 0 | | 128 | | Green | | 0 | | 255 | | 0 | | Orange | | 255 | | 165 | | 0 | | Yellow | | 255 | | 255 | | 0 | For more colors see an [Online RGB Color table](http://www.rapidtables.com/web/color/RGB_Color.htm#color-table). ## Related Topics - [What is Python and RhinoScript?](/guides/rhinopython/what-is-rhinopython) - [Python Points](/guides/rhinopython/python-rhinoscriptsyntax-points) - [Python Vectors](/guides/rhinopython/python-rhinoscriptsyntax-vectors) - [Python Lines](/guides/rhinopython/python-rhinoscriptsyntax-lines) - [Python Planes](/guides/rhinopython/python-rhinoscriptsyntax-planes) - [Python Objects](/guides/rhinopython/python-rhinoscriptsyntax-objects) -------------------------------------------------------------------------------- # Creating a Rhino-dependent C++ DLL Source: https://developer.rhino3d.com/en/guides/cpp/create-dependent-dll/ This guide outlines the process of creating a Rhino-dependent C++ DLL. ## Overview A Rhino-dependent C++ DLL is one that links with Rhino C++ SDK libraries, and that can be used to add functionality that can be shared between multiple C++ plug-ins, or that can be used to add Platform Invoke (PInvoke), interop functionality to RhinoCommon or Grasshopper plug-ins. ## Create the DLL To create a Rhino-dependent C++ DLL: 1. Download and install the [Rhino C++ SDK](https://developer.rhino3d.com/guides/cpp/installing-tools-windows/). 1. Launch Visual Studio. 1. Create a new [Rhino C/C++ plug-in project](https://developer.rhino3d.com/guides/cpp/your-first-plugin-windows/). 1. Using **Solution Explorer**, delete the `PlugIn` .cpp and .h files, and delete the `Command` .cpp file. 1. Using **Property Manager**, remove the `Rhino.Cpp.PlugIn` property page from both the `Debug|x64` and the `Release|x64` build configurations. 1. Again using **Property Manager**, add the `Rhino.Cpp.PlugInComponent.props` property page to both the `Debug|x64` and the `Release|x64` build configurations. The property page file is found in the `PropertySheets`` folder in the Rhino C++ SDK installation folder. 1. Build the project. Done! You now have a Rhino-dependent DLL project. Now you are ready to add your functionality; either by adding or linking in other libraries, or by exporting custom C++ functions. # More information [Wrapping Native Libraries](https://developer.rhino3d.com/guides/rhinocommon/wrapping-native-libraries/) -------------------------------------------------------------------------------- # Creating a Skin Source: https://developer.rhino3d.com/en/guides/cpp/creating-a-skin/ This guide outlines the tools for C/C++ developers to wrap their application around Rhino by creating custom Skin. ## Overview Rhino allows developers to customize most of Rhino's interface so that the application appears to be their own. We call this a custom *Skin*. With a custom Skin, you can change the application icon, splash screen, the application name etc. Creating a custom Skin for Rhino involves creating a custom skin assembly: 1. *skin name.rhs* This is a regular MFC DLL (*.dll*) that implements the skin's icon, splash screen, application name, etc. In this guide, we will refer this to the "Skin DLL." 1. *skin name.rhp* is a Rhino utility plugin that implements the menu handler, if necessary, and one or more custom commands. In this article, we refer to this as the "Skin Plugin." ## Create the Skin DLL To create the Skin DLL: 1. Launch *Visual Studio* and run the *Rhino Skin DLL wizard* installed by the Rhino C/C++ SDK. 1. The Rhino Skin DLL wizard creates three classes: 1. A `CWinApp`-derived class. This is the entry point of the DLL. 1. A `CRhinoSkinDLL`-derived class. This class lets you specify Rhino's icon, splash screen, and menu. For more information on this class, see *rhinoSdkSkinDLL.h*. 1. `CSplashWnd`. This is a basic implementation of a splash screen class. If you need something fancier, feel free to replace it with your own implementation. 1. Modify the project's icon and splash screen bitmap. If your Skin is going to override Rhino's main menu, then you need to create your menu resources as well. 1. Remember to fill out the developer information block found at the top of your *DLL's .cpp* file. This block is similar to that of Rhino plugins. ## Create the Skin Plugin To create a Skin Plugin: 1. Launch *Visual Studio* and run the *Rhino Plugin wizard* installed by the Rhino C/C++ SDK. When picking the wizard to run, make sure to add the new project to the open solution instead of creating a new solution. This way, your Skin project is organized into a single solution. 1. If the Skin DLL provides a custom menu, then copy the *UUID* generated by the plugin *AppWizard* and found in your plugin's `CRhinoPlugIn::PlugInID()` member to your Skin's `CRhinoSkinDLL::SkinPlugInID()` member. 1. These two methods must return the same *UUID*. This is a critical step as it identifies the main plugin that Rhino will load to manage its menus and extend the Rhino command set. 1. Add the following overrides to the header file of your `CRhinoPlugIn`-derived class: ```cpp // Skin DLL menu update handler void OnInitPlugInMenuPopups(WPARAM wparam, LPARAM lparam); // Skin DLL menu command handler BOOL OnPlugInMenuCommand(WPARAM wparam ); // Change to CRhinoPlugIn::load_plugin_at_startup plugin_load_time PlugInLoadTime(); ``` 1. Add the following definition to the *.cpp* file of your `CRhinoPlugIn`-derived class: ```cpp CRhinoPlugIn::plugin_load_time CSkinPlugInSamplePlugIn::PlugInLoadTime() { // Override to change load time to "at startup" return CRhinoPlugIn::load_plugin_at_startup; } ``` 1. If your Skin DLL is providing a custom menu, then add a source file named *MenuHandler.cpp* to the plugin project and put the definition of `CRhinoPlugIn::OnInitPlugInMenuPopups()` and `CRhinoPlugIn::OnPlugInMenuCommand()` in this file. 1. Include the Skin DLL's *Resource.h* file in *MenuHandler.cpp* to provide access to the Skin DLL's menu resource identifiers. For example: ```cpp #include "stdafx.h" #include "MySkinPlugIn.h" #include "../MySkinDLL/Resource.h" // Put these to overrides in a separate CPP file so they could // include the MySkinDLL/Resource.h file without conflicting // with this projects resource.h void CSkinPlugInSamplePlugIn::OnInitPlugInMenuPopups(WPARAM wParam, LPARAM lParam) { HMENU hMenu = (HMENU)wParam; if( NULL == hMenu ) return; switch( GetMenuItemID(hMenu, LOWORD(lParam)) ) { case IDM_SAMPLE_DISABLE: ::EnableMenuItem( hMenu, IDM_SAMPLE_DISABLE, MF_BYCOMMAND|MF_DISABLED|MF_GRAYED ); break; case IDM_SAMPLE_SUB_DISABLE: ::EnableMenuItem( hMenu, IDM_SAMPLE_SUB_DISABLE, MF_BYCOMMAND|MF_DISABLED|MF_GRAYED ); break; // TODO... } } BOOL CSkinPlugInSamplePlugIn::OnPlugInMenuCommand(WPARAM wParam) { ON_wString w; switch( (UINT)wParam ) { case IDM_SAMPLE_ONE: w = L"Test Item One"; break; case IDM_SAMPLE_TWO: w = L"Two"; break; case IDM_SAMPLE_DISABLE: w = L"Disabled"; break; case IDM_SAMPLE_SUB_A: w = L"Sub Menu A"; break; case IDM_SAMPLE_SUB_B: w = L"Sub Menu B"; break; case IDM_SAMPLE_SUB_DISABLE: w = L"Sub Menu Disabled"; break; default: return true; } ::RhinoMessageBox( w, L"OnMenu", MB_OK ); return true; } ``` 1. Compile the Skin Plugin. 1. Load the Skin Plugin using Rhino's *PluginManager* command so it has a chance to self-register. 1. Compile the Skin DLL. ## Installation

WARNING

Modifying the registry incorrectly can have negative consequences on your system's stability and even damage the system. To install your custom Skin, use **REGEDIT.EXE** to add a scheme key to your registry with a path to your Skin DLL. For example: | **Item** | | | **Value** | |:--------|:----:|:----:|:--------| | Subkey | | | HKEY_LOCAL_MACHINE\SOFTWARE\McNeel\Rhinoceros\MajorVersion.0\Scheme: MySkin || Entry name | | | SkinDLLPath | | Type | | | REG_SZ | | Data value | | | C:\Src\MySkin\Bin\Release\MySkin.rhs | Where `MajorVersion` is the major version of Rhino (e.g. 6, 7, 8). ## Testing You can now test your custom Skin by creating shortcut to your Rhino executable with `/scheme=""` as command line argument. For example: *C:\Program Files\Rhino 8\System\Rhino.exe" /scheme=MySkin* ## Additional Info If the user chooses not to run your skinned version of Rhino, you might want to prevent your Skin plugin loading. You can do this by checking to see if the name of the scheme that Rhino is using matches your Skin's scheme name. Do this by checking in your plugin's `CRhinoPlugIn::OnLoadPlugIn()` member: ```cpp BOOL CSkinPlugInSamplePlugIn::OnLoadPlugIn() { ON_wString scheme = RhinoApp().RegistrySchemeName(); if( scheme.CompareNoCase(L"Scheme: MySkin") != 0 ) return -1; // Fail silently... // TODO... return CRhinoUtilityPlugIn::OnLoadPlugIn(); } ``` -------------------------------------------------------------------------------- # Custom Component Options Source: https://developer.rhino3d.com/en/guides/grasshopper/custom-component-options/ This guide discusses how to add custom options to a component and have them included in *.gh/.ghx* (de)serialization. It skips over some portions of Component design which have already been handled in previous guides, so do not read this guide before familiarizing yourself with the [Simple Component](/guides/grasshopper/simple-component) guide. ## Overview The component we'll create in this article will sort a list of numbers and have the custom option to convert those numbers to absolute values prior to sorting. However, rather than providing this option as a boolean input parameter, we'll allow people to set it via the Component context menu. We'll need to do four special things to achieve this, to wit: - Declare a class level variable/property. - Provide access to the variable from within the Component menu. - Include the variable in (de)serialization. - Record undo events when changing the value. ## Example Component Before you start with this guide, create a new class that derives from [GH_Component](/api/grasshopper/html/T_Grasshopper_Kernel_GH_Component.htm), as outlined in the [Simple Component](/guides/grasshopper/simple-component) guide. This component will require one input parameter and one output parameter, both of type Number with list access: ```cs ... protected override void RegisterInputParams(Kernel.GH_Component.GH_InputParamManager pManager) { pManager.AddNumberParameter("Values", "V", "Values to sort", GH_ParamAccess.list); } protected override void RegisterOutputParams(Kernel.GH_Component.GH_OutputParamManager pManager) { pManager.AddNumberParameter("Values", "V", "Sorted values", GH_ParamAccess.list); } ... ``` ```vbnet ... Protected Overrides Sub RegisterInputParams(ByVal pManager As GH_Component.GH_InputParamManager) pManager.AddNumberParameter("Values", "V", "Values to sort", GH_ParamAccess.list) End Sub Protected Overrides Sub RegisterOutputParams(ByVal pManager As Kernel.GH_Component.GH_OutputParamManager) pManager.AddNumberParameter("Values", "V", "Sorted values", GH_ParamAccess.list) End Sub ... ``` Assuming for now we'll have a local property called `Absolute()` which gets a single boolean, we can also already write the `SolveInstance()` method: ```cs ... protected override void SolveInstance(Kernel.IGH_DataAccess DA) { List values = new List(); if ((!DA.GetDataList(0, values))) return; if ((values.Count == 0)) return; // Don't worry about where the Absolute property comes from, we'll get to it soon. if ((Absolute)) { for (Int32 i = 0; i < values.Count; i++) { values(i) = Math.Abs(values(i)); } } values.Sort(); DA.SetDataList(0, values); } ... ``` ```vbnet ... Protected Overrides Sub SolveInstance(ByVal DA As Kernel.IGH_DataAccess) Dim values As New List(Of Double) If (Not DA.GetDataList(0, values)) Then Return If (values.Count = 0) Then Return 'Don't worry about where the Absolute property comes from, we'll get to it soon. If (Absolute) Then For i As Int32 = 0 To values.Count - 1 values(i) = Math.Abs(values(i)) Next End If values.Sort() DA.SetDataList(0, values) End Sub ... ``` ## Class Level Variables The "Absolute" option for this component applies to the entire object, but not to other instances of this component. Since it needs to survive (i.e. retain its value) for as long as the component lives, it has to be declared as a class level variable: ```cs ... private bool m_absolute = false; public bool Absolute { get { return m_absolute; } set { m_absolute = value; if ((m_absolute)) { Message = "Absolute"; } else { Message = "Standard"; } } } ... ``` ```vbnet ... Private m_absolute As Boolean = False Public Property Absolute() As Boolean Get Return m_absolute End Get Set(ByVal value As Boolean) m_absolute = value If (m_absolute) Then Message = "Absolute" Else Message = "Standard" End If End Set End Property ... ``` The `m_absolute` field is a private field (only accessible from within this component) and it is exposed publicly via the `Absolute()` method, which allows both getting and setting. Furthermore, whenever the `m_absolute` field is set, the `Absolute()` method ensures that the correct message is assigned. The [Message](/api/grasshopper/html/P_Grasshopper_Kernel_GH_Component_Message.htm) field on `GH_Component` allows you to set a string which will be displayed underneath the component on the canvas. This is to signal to users that there's an option they can change which is not directly accessible via the input parameters. Note that the message is not set *until* the `Absolute()` property is accessed, so you should specifically place a call to `Absolute = False` (or `True`) in the constructor. It is of course possible to add any number of custom fields to a component, but you can only attach a single message, if you have more than one field you want to make the user aware of, you'll need to get creative. ## (De)serialization of Custom Data When you add options or states to your component which need to be "sticky," you'll also need to (de)serialize them correctly. (De)serialization is used when saving and opening files, when copying and pasting objects and during undo/redo actions. In this particular case, we only need to add a single boolean to the standard file archive. Serialization in Grasshopper happens using the *GH_IO.dll* methods and types, not via standard framework mechanisms such as the `SerializableAttribute`. Override the [Write](/api/grasshopper/html/M_Grasshopper_Kernel_GH_Component_Write.htm) and [Read](/api/grasshopper/html/M_Grasshopper_Kernel_GH_Component_Read.htm) methods on `GH_Component` and be sure to always call the base implementation. ```cs ... public override bool Write(GH_IO.Serialization.GH_IWriter writer) { // First add our own field. writer.SetBoolean("Absolute", Absolute); // Then call the base class implementation. return base.Write(writer); } public override bool Read(GH_IO.Serialization.GH_IReader reader) { // First read our own field. Absolute = reader.GetBoolean("Absolute"); // Then call the base class implementation. return base.Read(reader); } ... ``` ```vbnet ... Public Overrides Function Write(ByVal writer As GH_IO.Serialization.GH_IWriter) As Boolean 'First add our own field. writer.SetBoolean("Absolute", Absolute) 'Then call the base class implementation. Return MyBase.Write(writer) End Function Public Overrides Function Read(ByVal reader As GH_IO.Serialization.GH_IReader) As Boolean 'First read our own field. Absolute = reader.GetBoolean("Absolute") 'Then call the base class implementation. Return MyBase.Read(reader) End Function ... ``` ## Context Menu Changes We'll also need to add an additional menu item to the component context menu, then handle the click event for that item. Adding items to a context menu is best done via the [AppendAdditionalComponentMenuItems](/api/grasshopper/html/M_Grasshopper_Kernel_GH_Component_AppendAdditionalComponentMenuItems.htm) method. It allows you to insert any number of item in between the Bake and the Help items. The easiest way to add menu items is to use the Shared methods on [GH_DocumentObject](/api/grasshopper/html/T_Grasshopper_Kernel_GH_DocumentObject.htm) such as [Menu_AppendItem](/api/grasshopper/html/Overload_Grasshopper_Kernel_GH_DocumentObject_Menu_AppendItem.htm) or one of the overloads. In this case we also want to assign a tooltip text to the item which cannot be done from inside `Menu_AppendItem()`. ```cs ... protected override void AppendAdditionalComponentMenuItems(System.Windows.Forms.ToolStripDropDown menu) { // Append the item to the menu, making sure it's always enabled and checked if Absolute is True. ToolStripMenuItem item = Menu_AppendItem(menu, "Absolute", Menu_AbsoluteClicked, true, Absolute); // Specifically assign a tooltip text to the menu item. item.ToolTipText = "When checked, values are made absolute prior to sorting."; } ... ``` ```vbnet ... Protected Overrides Sub AppendAdditionalComponentMenuItems(ByVal menu As System.Windows.Forms.ToolStripDropDown) 'Append the item to the menu, making sure it's always enabled and checked if Absolute is True. Dim item As ToolStripMenuItem = Menu_AppendItem(menu, "Absolute", AddressOf Menu_AbsoluteClicked, True, Absolute) 'Specifically assign a tooltip text to the menu item. item.ToolTipText = "When checked, values are made absolute prior to sorting." End Sub ... ``` When this menu item is clicked, the delegate assigned inside the `Menu_AppendItem()` method will be invoked. It is here that we must handle a click event. There are usually three steps involved in handling clicks; Record the current state as an undo event, change the state, trigger a new solution: ```cs private void Menu_AbsoluteClicked(object sender, EventArgs e) { RecordUndoEvent("Absolute"); Absolute = !Absolute; ExpireSolution(true); } ... ``` ```vbnet ... Private Sub Menu_AbsoluteClicked(ByVal sender As Object, ByVal e As EventArgs) RecordUndoEvent("Absolute") Absolute = Not Absolute ExpireSolution(True) End Sub ... ``` Since our `Write()` and `Read()` methods handle the (de)serialization of the `Absolute` field, we can use the default [RecordUndoEvent](/api/grasshopper/html/Overload_Grasshopper_Kernel_GH_DocumentObject_RecordUndoEvent.htm) method. It is possible to define your own undo records, but that is a topic for another guide. ## Related Topics - [Simple Component](/guides/grasshopper/simple-component) -------------------------------------------------------------------------------- # Deployment to Production Servers Source: https://developer.rhino3d.com/en/guides/compute/deploy-to-iis/ How to deploy rhino compute for production on a machine running Internet Information Services (IIS). ## Overview This guide will walk you through how to setup an instance of Rhino.Compute on a virtual machine running Internet Information Services (IIS). [IIS](https://www.iis.net/), is a flexible, general-purpose web server developed by Microsoft which can be configured to serve requested HTML pages or files. We can setup IIS to process incoming requests (either from Hops or some other client) and forward that request to the Rhino.Compute instance. You may be asking yourself, "Why do I need IIS at all? Why can't I simply launch the Rhino.Compute server and send API requests directly to that instance?". Technically speaking, you don't need IIS. However, you would need to figure out how to relaunch the compute server should it crash or malfunction. You would also need to configure Rhino.Compute to launch whenever the virtual machine is launched. One of the main benefits of using IIS as a middleware is that it can automatically spin up an instance of the Rhino.Compute server whenever a request is recieved. With this configuration, you do not have to have compute running continuously. Instead, IIS can launch an instance of compute when it is needed which in turn will launch one or more child processes. These child processes are what perform the actual computation and also what require your license authorization. When Rhino.Compute.exe does not receive requests to solve over a period of seconds (this is called idlespans and the default is set to 1 hour), child compute.geometry.exe processes will shut down and stop incurring core hour billing. At some date in the future when a new request is received, IIS will make sure that Rhino.Compute.exe is running which will then relaunch the child processes. Note, there may be some small delay in the response while the child processes are launching.
Fig.1 - A flow diagram showing how IIS recieves an incoming request and launches Rhino.Compute.exe which in turn spins up child processes.
## Prerequisites Before running the bootstrap script on your server or virtual machine, you will need the following pieces of information. * **`EmailAddress`** - The script will use this email to download a copy of Rhino to install. This is similar to the Rhino download page behavior. * **`ApiKey`** - The API key is a string of text that is secret to your compute server and your applications that are using the compute API e.g. `b8f91f04-3782-4f1c-87ac-8682f865bf1b`. It is basically how the compute server ensures that the API calls are coming from your apps only. You can enter any string that is unique and secret to you and your compute apps. Make sure to keep this in a safe place. * **`RhinoToken`** – This is a long token that identifies your instance of Rhino Compute to the core-hour billing system. Go to the [Licenses Portal](https://www.rhino3d.com/licenses?_forceEmpty=true) to generate this unique id based on your license. See the ["Core-Hour Billing" guide](../core-hour-billing/) for more information. Running rhino compute locally uses your existing Rhino license and does not cost any additional money (other than your initial rhino license investment). Running compute locally is the best option for development and testing and to find out more, read Running and Debugging Compute Locally. However, when setting up a production environment you will need a server or virtual machine pre-installed with Windows Server 2019 or higher. Licensing works differently when running Rhino (ie. via Compute) in a production environment (ie. server-setting) and you will be charged at a rate of $0.10 per core per hour. Follow the "Core-Hour Billing" guide to get set up. This is important so do not skip. ## Create the VM The first step in the process of deploying **Rhino.Compute** for production is to either setup a physical machine to act as a server or create a virtual machine (VM). For this guide, we will be using a virtual machine. There are several popular services which can be configured to setup a wide array of virtual machines depending on your resource needs. Two of the most prominent providers include [Azure](https://azure.microsoft.com/en-us/free/virtual-machines/) and [AWS](https://aws.amazon.com/ec2/instance-types/). Depending on your preferences, we recommend starting with an Azure or AWS VM. Use the following guides to walk through that process. * [Create a Virtual Machine on Azure](../creating-an-Azure-VM). * [Create a Virtual Machine on AWS](../creating-an-aws-vm). ### Connect via RDP Now that we've configured the virtual machine, we need to be able to log onto it so that we can setup IIS and the Rhino.Compute instance. We'll do this by using a remote desktop protocol (RDP) which connects two computers over a network. To start, let's download the RDP file. We'll use the Azure VM Portal but a similar process is used for AWS. 1. First, make sure the virtual machine is running. Click on the **Overview** tab in the left-hand menu. If the *Status* says *Stopped (Deallocated)*, click on the **Start** button at the top to start the remote machine. After a few seconds, the status should say *Running*. 1. Click on the **Connect** menu item in the left-hand menu to pull out the connection settings blade. 1. Click on the **Download RDP File** button and save the file somewhere on your computer. 1. Click on the Windows **Start** menu and start typing *remote desktop connection* in the search bar. Click on the link to launch app. 1. Click on the button at the bottom of the app to **Show Options** 1. Under the *Connection Settings* area, select **Open** and navigate to the directory where you saved your RDP file. Choose that file and hit Open. 1. Select the checkbox to **Allow me to save credentials** 1. Click **Connect**. 1. A security pop-up will be opened. Click the checkbox for **Don't ask me again for connections to this computer** and then click **Connect**. 1. Enter the administrator credentials that you entered in step 7 of [Creating a Virtual Machine](../deploy-to-iis/#setting-up-a-virtual-machine). 1. You may see another security pop-up. Again, select **Don't ask me again for connections to this computer** and click **Yes**. Congratulations. You should now have access to the desktop of the remote machine running Windows Server 2019 or higher. ## Running the Bootstrap script Assuming that you are now logged into the virtual machine (using RDP), follow the steps below to install Rhino.Compute behind IIS. Note: These steps assume that this is an installation on a new virtual machine or server (meaning Rhino.Compute has not been previously installed on this machine). If you have already setup Rhino.Compute and IIS on this machine and simply need to update Rhino and/or Rhino.Compute on this VM, please proceed to the section on [Updating Rhino Compute](../deploy-to-iis/#updating-the-deployment). ### Installation 1. Click on the Windows Start menu and type in "Powershell". In the menu that appears, right-click on the **Windows Powershell app** and choose **Run As Administrator**. 1. **Copy and paste** the command below into the Powershell prompt and hit **Enter**. This command will download the latest bootstrap script and install Rhino and IIS onto your VM. Note: you will be prompted to enter your **Email Address**, **API Key**, and **Rhino Token**, so please have that information handy. ```powershell $F="https://developer.rhino3d.com/bootstrap_step-1.zip";$T="$($Env:temp)\tmp$([convert]::tostring((get-random 65535),16).padleft(4,'0')).tmp"; New-Item -ItemType Directory -Path $T; iwr -useb https://raw.githubusercontent.com/mcneel/compute.rhino3d/8.x/script/production/bootstrap_step-1.zip -outfile $T$F; Expand-Archive $T$F -DestinationPath $T; Remove-Item $T$F;& "$T\bootstrap_step-1\boostrap_step-1.ps1" ``` 1. At the end of the bootstrap script, you should see the message *Congratulations! All components have now been installed.* This will be followed by some instructions about how to install 3rd party plugins so that they will work properly with Rhino.Compute. Please take note of the new **Username** and **Password** which will be required when logging back in to install any additional plugin. The installtion process above is designed for Rhino 8. If you would like to install rhino.compute for Rhino 7, please [follow this guide](/guides/compute/deploy-to-iis-v7/). ## Testing the app At this point, IIS should be configured to launch the Rhino.Compute instance when an API request is made. Let's try getting Hops to send a definition to our virtual machine's URL. 1. Launch **Rhino** on your local machine. 1. If you have not already installed hops on this machine, please do so by searching for it in the Rhino **Package Manager**. 1. Start **Grasshopper** by typing the word *Grasshopper* into the command prompt and hitting enter. 1. Go to **File** and then **Preferences** to open the preferences dialog. 1. Click on the **Solver** tab in the left-hand menu. 1. In the **Hops - Compute server URLs** section, type in the web address of your virtual machine. Start by typing in `http://` followed by the IP address of the virtual machine followed by a `:` and the port number `80`. So, the full address would look something like this: http://52.168.38.105:80/ 1. In the **API Key** section, enter the API Key that you saved in the [Prerequisites](../deploy-to-iis/#prerequisites) section. 1. Add a **Hops** component to the **Grasshopper canvas** (Params/Util) 1. Right-click on the **Hops** component and set the **Path** to a valid Hops/Grasshopper definition. To learn more about setting up a Grasshopper definition that will work properly with Hops, [follow this guide](../hops-component/). Once the path is set, the Hops component will create the appropriate API request and sends it to the URL that we specified in step 6 (the Rhino.Compute server running on IIS). The compute server processes the request and sends a response back to Hops, which returns the result. Congratulations! You have successfully setup an instance of Rhino.Compute running behind IIS on a virtual machine. ## Modifying Compute Parameters After Deployment There are a number of command line arguments that can be used to modify how Rhino.Compute behaves. This section covers how to change those parameters after Rhino.Compute has been deployed. 1. Log into your VM (via RDP). See the section [Connect via RDP](#connect-via-rdp) for more details. 1. On the **Start** menu, click in the search area and type *Internet Information Services (IIS) Manager*. Click to launch the app. 1. In the IIS Manager, **click** on the web server node in the **Connections** panel on the left. Note, this web server name should be the same name you used when setting up the name of your virtual machine. 1. In the **Actions** pane on the right, click **Stop** to stop the web server. Now that the IIS web server has been stopped, we need to go and modify the **web.config* file for Rhino.Compute. The [web.config](https://docs.microsoft.com/en-us/aspnet/core/host-and-deploy/iis/web-config?view=aspnetcore-6.0) file is a file that is read by IIS and the ASP.NET Core module to configure an which is hosted by IIS. 1. Open a **File Explorer** window and navigate to *C:\inetpub\wwwroot\aspnet_client\system_web\4_0_30319\rhino.compute*. At the end of this directory, you should see a file labled **web.config**. Note, you may need to click on the **View** tab and click the checkbox to turn on **file name extensions**. Open the web.config file with **Notepad** or any other text editor you prefer. The full web.config file should look like this: ```xml ``` The line we are most interested in modifying is the one which starts with the **``** tag. You can see the second parameter on this line is called **`arguments`**. This is the section that you would edit to add other command line arguments to modify Rhino.Compute. Below is a description of each command line argument which can be modified. - **port** - This is the port number to run Rhino.Compute on. Port number 80 is typically reserved for HTTP communication, while port number 443 is used for the HTTPS protocol. Rhino.Compute is setup to be bound to port 80 in the bootstrap script, so do not modify this value unless you know what you are doing. --port 80 //bind the process to port 80 - **urls** - This is used specifically in ASP.NET Core applications to define the server URL(s) and port(s) that the web server (Kestrel) should bind to at startup. Adding this option will override any default settings. Multiple URLs can be separated by a semicolon. --urls "http://localhost:5000" //bind the process to the url http://localhost/ and port 5000 - **childcount** - This parameter controls the number of child compute.geometry processes to manage. The default value is `4` which means that Rhino.Compute will spawn four child processes each of which can handle incoming requests. --childcount 8 //launch eight child processes - **childof** - This is the process handle of parent process. Compute watches for the existence of this handle and will shut down when this process has exited. Since we are relying on IIS starting and stopping Rhino.Compute, you do not need this parameter when running in a production environment. - **spawn-on-startup** - This flag determines whether to launch a child compute.geometry process when Rhino.Compute gets started. The default value is `false`. This parameter is a flag which means that if it is not included in the argument list, the value will be false. If you include this parameter, then its value will be true. For production environments, this value should remain false. --spawn-on-startup //automatically start the child processes when Rhino.Compute is launched - **idlespan** - This is the number of seconds that a child compute.geometry processes should remain open between requests. The *default value is 1 hour*. When Rhino.Compute.exe does not receive requests to solve over a period of `idlespan` seconds, child compute.geometry.exe processes will shut down and stop incurring core hour billing. At some date in the future when a new request is received, the child processes will be relaunched which will cause a small delay on requests while the child processes are launching. --idlespan 1800 //shut down child processes after 30 mins of inactivity (30 mins x 60 secs/min = 1800) If you change the `idelspan` value in the command line arguments for Rhino.Compute, you will also need to make a modification to the IIS configuration. This is because IIS also contains a setting to shut down Rhino.Compute if no new requests are received after a period of time. When Rhino.Compute gets shutdown, it will in turn shutdown any child processes. For instance, lets say you change the `idlespan` value to 7,200 (2 hours). The bootstrap script sets the IIS `IdleTimeout` value to 65 minutes (slightly longer than the default `idlespan` value). After 65 minutes, IIS would shutdown Rhino.Compute, which would then shutdown all of its child processes much earlier than the expected 2 hour time limit. So, if you change the `idlespan` value, it is recommended that you also change the `IdleTimeout` value in the IIS settings. 1. To do this, go back to the IIS Manager and click on the **Application Pools** item in the **Connections** pane on the left. 1. In the center window click on the **RhinoComputeAppPool**. 1. In the **Actions** pane on the right, click **Advanced Settings**. 1. Find the **Idle Time-out** row under the **Process Model** section and change the time out value to be slightly longer than the `idlespan` value you set in the command line arguments. Note: the `idlespan` value is in seconds while the `Idle Timeout` value is in minutes. New command line arguments were added to Rhino.Compute on November 5, 2025. To use them, make sure you’re running a version released on or after that date. - **load-grasshopper** - This parameter determines whether the Grasshopper plugin is loaded when Rhino.Compute starts. By default, it’s set to `true` for backward compatibility—so if you don’t specify a value, Grasshopper will load automatically. Setting it to `false` can make startup faster, but you won't be able to solve Grasshopper definitions using Rhino.Compute. You’ll still be able to use standard SDK functions and any custom endpoints you’ve added through Rhino plugins (click [here](../custom-endpoints/) to learn more about extending Rhino.Compute with custom endpoints). --load-grasshopper false //skip loading the Grasshopper plugin during startup - **max-request-size** | **\** - This parameter sets the maximum allowable payload for any incoming request. The default value is `52428800 bytes` which is 50mb. --max-request-size 104857600 //would allow requests up to 100mb (100mb = 104857600 bytes) - **apikey** - This parameter allows you to set an API key to be used for authentication. Normally, this is set as an environment variable, but this parameter will override that setting for a given session. --apikey "MyCustomAPIKey" //defines an API key to be used for authentication - **timeout** - This parameter lets you specify a timespan (in seconds) to wait before a request times out. The default value is `100 seconds`. --timeout 300 //wait 5 mins before a request times out (5 mins * 60 sec/min = 300 seconds) - **create-headless-doc** - This parameter defines whether or not you want to create a new headless Rhino document upon receiving a new request. Each time a request is received, it checks whether this value is `true`. If it is, a new headless document is instantiated with tolerances and units determined by the input object. This feature is useful for third party plugins which may make calls to the Rhino Document to retrieve certain properties. --create-headless-doc true //create a new headless Rhino document on each request Once you have modified web.config file, **save** it and close the file. You can then go back to the IIS Manager and **start** the web server by clicking on the web server node in the **Connections** panel on the left and clicking **start** in the **Actions** pane on the right. ## Updating The Deployment If you have already Rhino.Compute set up on this machine, you may need to periodically update Rhino and/or the Rhino.Compute build files to the latest version. Follow the steps below to update these applications. ### Update Rhino 1. Click on the Windows Start menu and type in "Powershell". In the menu that appears, right-click on the **Windows Powershell app** and choose **Run As Administrator**. 1. **Copy and paste** the command below into the Powershell prompt and hit **Enter**. This command will download the latest version of Rhino for Windows. Note: you will be prompted to enter your **Email Address** so please have that information available. ```powershell iwr -useb https://raw.githubusercontent.com/mcneel/compute.rhino3d/8.x/script/production/module_update_rhino.ps1 -outfile update_rhino.ps1; .\update_rhino.ps1 ``` ### Update Compute 1. Click on the Windows Start menu and type in "Powershell". In the menu that appears, right-click on the **Windows Powershell app** and choose **Run As Administrator**. 1. **Copy and paste** the command below into the Powershell prompt and hit **Enter**. This command will download the latest version of Rhino.Compute. ```powershell iwr -useb https://raw.githubusercontent.com/mcneel/compute.rhino3d/8.x/script/production/module_update_compute.ps1 -outfile update_compute.ps1; .\update_compute.ps1 ``` ## Quick Links - [What is Hops](../what-is-hops) - [How Hops Works](../how-hops-works) - [The Hops Component](../hops-component) -------------------------------------------------------------------------------- # Event Watchers Source: https://developer.rhino3d.com/en/guides/rhinocommon/event-watchers/ This guide covers how to synchronize a control's appearance with what is going on in Rhino using event watchers. ## UI Thread Applications can not update any user interface controls from any thread other than the main UI thread. Changing a property on a windows control typically causes the control to immediately update its display. But if the call is made from another thread then the UI thread windows will crash the application. If you are updating a control from inside your event handler, you should check to see if `CheckAccess()` on the Dispatcher associated with your control evaluates to `True`. If this is the case, you must invoke a delegate that handles the UI updating and make sure that this delegate is called from the UI thread. This is actually easier than it sounds. Here is a C# sample of `RhinoDoc.AddRhinoObject` event handler that handles the thrown exception when called from a thread other than the UI thread... ```cs private void rhinoObjectAdded(Object sender, Rhino.DocObjects.RhinoObjectEventArgs e) { var msg = String.Format("thread id = {0}, obj id = {1}", Thread.CurrentThread.ManagedThreadId, e.ObjectId.ToString()); RhinoApp.WriteLine(msg); try { // when a sphere is added from a secondary thread this line will // throw an exception because UI controls can only be accessed from // the main UI thread _label.Content = msg; } catch (InvalidOperationException ioe) {RhinoApp.WriteLine(ioe.Message);} } ``` ## Other Threads But... I really do need to update my control's state, even when in the wrong thread! This is where `Dispatcher.Invoke` or `Dispatcher.BeginInvoke` comes into play. You can force the code that modifies the control to run from the UI thread. Here is a C# sample of how to do this... ```cs private void rhinoObjectAddedSafe(Object sender, Rhino.DocObjects.RhinoObjectEventArgs e) { var msg = String.Format("thread id = {0}, obj id = {1}", Thread.CurrentThread.ManagedThreadId, e.ObjectId.ToString()); RhinoApp.WriteLine(msg); // checks if the calling thread is the thread the dispatcher is associated with. // In other words, checks if the calling thread is the UI thread if (_label.Dispatcher.CheckAccess()) // if we're on the UI thread then just update the component _label.Content = msg; else { // invoke the setLabelTextDelegate on the thread the dispatcher is associated with, i.e., the UI thread var setLabelTextDelegate = new Action(txt => _label.Content = txt); _label.Dispatcher.Invoke(setLabelTextDelegate, new String[] { msg }); } } ``` The above function could be rewritten to just always call the delegate... ```cs private void rhinoObjectAddedSafe(Object sender, Rhino.DocObjects.RhinoObjectEventArgs e) { var msg = String.Format("thread id = {0}, obj id = {1}", Thread.CurrentThread.ManagedThreadId, e.ObjectId.ToString()); RhinoApp.WriteLine(msg); // invoke the setLabelTextDelegate on the thread the dispatcher is associated with, i.e., the UI thread var setLabelTextDelegate = new Action(txt => _label.Content = txt); _label.Dispatcher.Invoke(setLabelTextDelegate, new String[] { msg }); } ``` **NOTE**: If Windows Forms (on Windows) is used instead of WPF then `_label.Dispatcher.CheckAccess()` becomes `_label.InvokeRequired` and `_label.Dispatcher.Invoke(...)` becomes `_label.Invoke(...)`. Here is the complete example: ```cs using System; using Rhino; using Rhino.Commands; using Rhino.DocObjects; using Rhino.Geometry; using System.Threading; using System.Windows; using System.Windows.Controls; namespace examples_cs { [System.Runtime.InteropServices.Guid("E4A93905-6E61-43BB-9FF0-4D5F6AF76704")] public class ChangeUiFromDifferentThreadCommand : Command { public override string EnglishName { get { return "csChangeUIFromDifferentThread"; } } private RhinoDoc _doc; private Label _label; private Window _window; protected override Result RunCommand(RhinoDoc doc, RunMode mode) { _doc = doc; _window = new Window {Title = "Object ID and Thread ID", Width = 500, Height = 75}; _label = new Label(); _window.Content = _label; new System.Windows.Interop.WindowInteropHelper(_window).Owner = Rhino.RhinoApp.MainWindowHandle(); _window.Show(); // register the rhinoObjectAdded method with the AddRhinoObject event RhinoDoc.AddRhinoObject += RhinoObjectAdded; // add a sphere from the main UI thread. All is good AddSphere(new Point3d(0,0,0)); // add a sphere from a secondary thread. Not good: the rhinoObjectAdded method // doesn't work well when called from another thread var addSphereDelegate = new Action(AddSphere); addSphereDelegate.BeginInvoke(new Point3d(0, 10, 0), null, null); // handle the AddRhinoObject event with rhinoObjectAddedSafe which is // desgined to work no matter which thread the call is comming from. RhinoDoc.AddRhinoObject -= RhinoObjectAdded; RhinoDoc.AddRhinoObject += RhinoObjectAddedSafe; // try again adding a sphere from a secondary thread. All is good! addSphereDelegate.BeginInvoke(new Point3d(0, 20, 0), null, null); doc.Views.Redraw(); return Result.Success; } private void AddSphere(Point3d center) { _doc.Objects.AddSphere(new Sphere(center, 3)); } private void RhinoObjectAdded(Object sender, RhinoObjectEventArgs e) { var message = String.Format("thread id = {0}, obj id = {1}", Thread.CurrentThread.ManagedThreadId, e.ObjectId.ToString()); RhinoApp.WriteLine(message); try { // when a sphere is added from a secondary thread this line will // throw an exception because UI controls can only be accessed from // the main UI thread _label.Content = message; } catch (InvalidOperationException ioe) {RhinoApp.WriteLine(ioe.Message);} } private void RhinoObjectAddedSafe(Object sender, RhinoObjectEventArgs e) { var message = String.Format("thread id = {0}, obj id = {1}", Thread.CurrentThread.ManagedThreadId, e.ObjectId.ToString()); RhinoApp.WriteLine(message); // checks if the calling thread is the thread the dispatcher is associated with. // In other words, checks if the calling thread is the UI thread if (_label.Dispatcher.CheckAccess()) // if we're on the UI thread then just update the component _label.Content = message; else { // invoke the setLabelTextDelegate on the thread the dispatcher is associated with, i.e., the UI thread var setLabelTextDelegate = new Action(txt => _label.Content = txt); _label.Dispatcher.BeginInvoke(setLabelTextDelegate, new String[] { message }); } } } } ``` -------------------------------------------------------------------------------- # Frequently Asked Questions Source: https://developer.rhino3d.com/en/guides/general/frequently-asked-questions/ This guide is a list of Frequently Asked Questions (FAQ). **Which SDK is right for me?** It all depends on what you want to do. If you are looking to automate repetitive tasks in Rhino, writing a [Python](/guides/#rhinopython) script is the way to go. If you are looking to write a full-fledged plugin or Grasshopper component, we strongly suggest the [RhinoCommon SDK](/guides/rhinocommon/what-is-rhinocommon/). If you are very proficient with C/C++, you should consider the native C/C++ SDK (only supported on Rhino for Windows). **Can I write plugins that run on both Windows and Mac?** Yes...even using [the same code](/guides/rhinocommon/what-is-rhinocommon/). **What are Macros?** Macros are string of Rhino commands and command options that allow you to create an automated sequence of operations. This macro (sequence) can then be repeated at the push of a toolbar button or by typing an alias. **What are Scripts?** For more complex tasks, macros are insufficient. They lack the ability to make complex calculations, store and retrieve data, analyze that data and make conditional decisions, or reach deep into the inner workings of Rhino. For this, one needs a real programming tool. The simplest and most accessible of these is Python, which also includes its version of RhinoScript syntax. When we talk about scripts we are usually referring to functions written with RhinoScript or Python. **What are Plugins?** Plugins are even more sophisticated tools: these are compiled computer programs that can be integrated into Rhino. These can range from simple script-like functions to complex, full blown programs for doing rendering, animation, machining, etc. **What is your release schedule?** Weekly! (if all goes well) but - for most users - the answer is monthly. See our [Release Schedule](/guides/general/developing-software-in-public/#publish) for more information. -------------------------------------------------------------------------------- # Implement HTTPS Callbacks Source: https://developer.rhino3d.com/en/guides/rhinocommon/cloudzoo/cloudzoo-implement-http-callbacks/ This guide explains all the HTTPS callbacks that need to be implemented by an issuer of Cloud Zoo. As an issuer, you must implement four HTTPS endpoints to answer basic questions Cloud Zoo requires to function. We provide a [ready-to-deploy solution in our Github repository](https://github.com/mcneel/cloudzoo-issuer) you can use as a template to implement these callbacks, or if you're experienced with HTTP rest APIs, you can roll your own from scratch. Regardless of the path you choose, the issuer must answer the following questions: - Can a license be added to an entity? The issuer is asked this question when a user tries to add a license to their account or a team. You may request further information from the user, accept the request, or deny the request. - Can a license be removed from an entity? The issuer is asked this question when a user tries to remove a license from their account or a team. - What are the details of a specific license key? This is used to display the license information to the user. For each of these questions, you MUST implement the following 3 callbacks: 1. GET `/get_license` 2. POST `/add_license` 3. POST `/remove_license` Note that all the callbacks listed above follow the conventions listed below. ## Callback Conventions Unless noted, the following conventions apply to *all* callbacks implemented by an issuer. ### Callback URL Cloud Zoo will perform a request to the base URL provided when creating an issuer plus the path (i.e. `/add_license`) and method (i.e. `GET` or `POST`). For example, if your issuer's base URL is `https://mydomain.com/cloudzoo`, then the full callback URL for `/add_license` would be `POST https://mydomain.com/cloudzoo/add_license` ### JSON All payload to and from callbacks happens in [JSON format](https://www.json.org). To make this explicit, every request to a callback will have the header `Content-Type: application/json` present in the HTTPS request. ### Localization In some circumstances, such as when additional input is required from a user when adding a license to an account, a localized response SHOULD be returned by the issuer in the user's locale. To make this possible, every request to a callback will have an `Accept-Language` header present like so: ``` Accept-Language: fr-CH; fr;q=0.9, en;q=0.8, *;q=0.5 ``` The header encodes the languages preferred by the user currently interacting with Cloud Zoo. If none of the languages specified in the header are available, English SHOULD be used. ### Authentication All endpoints in Cloud Zoo or on the issuer use [Basic Authentication](https://en.wikipedia.org/wiki/Basic_access_authentication) using the issuer’s id and the corresponding secret shared between Cloud Zoo and the respective issuer. You MUST make sure that the secret matches verbatim before processing any callback request on any endpoint to prevent a security breach. For example, on the issuer's end: ```python from flask import request, Response ISSUER_ID = "issuer_1" ISSUER_SECRET = "my_secret" def check_auth(username, password): """This function is called to check if a username password combination is valid.""" return username == ISSUER_ID and password == ISSUER_SECRET @app.route("https://developer.rhino3d.com/add_license", methods=["POST"]) def add_license() """See if a license can be added to an entity or whether we should ask more information, or whether we should deny the request.""" auth = request.authorization if not auth or not check_auth(auth.username, auth.password): return Response( 'Could not verify your access level for that URL.\n', 'You have to authenticate with proper credentials', 401, {'WWW-Authenticate': 'Basic realm="Credentials Required"'} ) else: pass #Continue processing the request HERE ``` ### Non-successful responses If returning a response with an HTTP status code greater or equal to `400`, the issuer SHOULD include the following JSON in the payload as well: ``` { "description": "The license cannot be removed from Cloud Zoo because [Marley](https://example.com) says so.", "details": "request id 239231203123212" } ``` - The `description` field is user facing, and SHOULD be localized. In addition, the description string may have markdown style links as in the example above. The links can be used to help the user navigate and solve the issue. - The `details` field is not user-facing, but may be logged for troubleshooting purposes. You MUST NOT include sensitive data in the details string. ## Callbacks ### GET /get_license Return information about a specific license so users can see details about the license. #### Example Request ``` GET /get_license?aud=PRODUCT_ID&key=A_LICENSE_KEY ``` - `aud`: The product id of the license whose information is to be returned. - `key`: The license key of the license whose information is to be returned. #### Required Response The response will vary depending on the issuer’s decision as described below: A successful response: - HTTP Status Code: `200 (OK)` - Response Payload: A [License object](/guides/rhinocommon/cloudzoo/cloudzoo-license). [See an actual example in our GitHub example](https://github.com/mcneel/cloudzoo-issuer/blob/7afc34a458589e17217e7e857b5b9695c46fe8ca/app.py#L264) A non-successful (error) response: - HTTP Status Code: A code greater or equal to `400 (Bad Request)` - Response Payload: [A non-successful response](#non-successful-responses) ### POST /add_license See if a license can be added to an entity or whether an issuer should ask more information, or whether we should deny the request. This callback is invoked whenever a license is about to be added to an entity. #### Example Request ``` POST /add_license { "entityId": "9304194021213-|-Group", "entityType": "Group", "license": { "key": "RH50-ABCD-EFGZ-HIJK-LMNO", "aud": "PRODUCT-ID-HERE" }, "userInfo": { "sub": "43190412048124", "email": "marley_the_dog@mcneel.com", "com.rhino3d.accounts.emails": [ "marley_the_dog@mcneel.com", "marleyz121@gmail.com" ], "com.rhino3d.accounts.member_groups": [ { "id": "9304194021213", "name": "Marley’s Friends LLC" } ], "com.rhino3d.accounts.admin_groups": [], "com.rhino3d.accounts.owner_groups": [], "name": "Marley", "locale": "en-gb", "picture": "http://marley.the.dog.com/images/coolpic.png" }, "precondition": "RH40-ABCD-EFGZ-HIJK-LMNO" } ``` - `entityId`: The id of the entity whom the license will be added to. Entities can be individual users or groups as defined in Rhino Accounts. - `userInfo`: The decoded JWT representing the user’s OpenID Token who wants to add the license to the specified entity. See [Rhino Accounts OpenID Tokens](https://docs.google.com/document/d/1-U0FYt6iQAM3UA6Rio4z0sDVXBSdc0kQk5e4zumnKig/edit#heading=h.qctsih4c0ctd) for details. The user is always a privileged member of the entity or is the entity itself in case `entityType` is `User`. Additional fields could be present and MAY be used but SHOULD NOT be assumed to be present. - `license`: The license that is to be added. This field is a simplified [License object](/guides/rhinocommon/cloudzoo/cloudzoo-license) with the license key and product id of the license. - `precondition`: This field is optional. If provided, it is a string string containing a precondition that was requested by the issuer (see response details below for more details on this field). #### Required Response The response will vary depending on the issuer’s decision as described below: If the license *can* be added to an entity: - HTTP Status Code: `200 (OK)` - Response Payload: A [License Cluster object](/guides/rhinocommon/cloudzoo/cloudzoo-licensecluster) representing the license(s) to be added. See an actual example in our GitHub example [See an actual example in our GitHub example](https://github.com/mcneel/cloudzoo-issuer/blob/7afc34a458589e17217e7e857b5b9695c46fe8ca/app.py#L138) If more information (`precondition`) is required: - HTTP Status Code: `428 (Precondition Required)` - Response Payload: [A non-successful response](#non-successful-responses). The `description` field will be used to describe what input is required from the user, such as a previous license key or a secret code. [See an actual example]((https://github.com/mcneel/cloudzoo-issuer/blob/7afc34a458589e17217e7e857b5b9695c46fe8ca/app.py#L138). If the additional information (`precondition`) is incorrect: - HTTP Status Code: `412 (Precondition Failed)` - Response Payload: [A non-successful response](#non-successful-responses). [See an actual example]((https://github.com/mcneel/cloudzoo-issuer/blob/7afc34a458589e17217e7e857b5b9695c46fe8ca/app.py#L138). If the license *cannot* be added to an entity: - HTTP Status Code: `409 (Conflict)` - Response Payload: [A non-successful response](#non-successful-responses). [See an actual example]((https://github.com/mcneel/cloudzoo-issuer/blob/7afc34a458589e17217e7e857b5b9695c46fe8ca/app.py#L138). #### Remarks This callback MUST be idempotent, meaning that it MUST consistently return the same result given a set of arguments. This is because the callback may be called more than one time with the same arguments by the system as part of a transaction that could be retried. ### POST /remove_license See if a license can be removed from an entity. This callback is invoked right before a license is removed from an entity. Issuers SHOULD make sure to note that a license is no longer in the entity. #### Example Request ``` POST /remove_license { "entityId": "9304194021213-|-Group", "entityType": "Group", "userInfo": { "sub": "43190412048124", "email": "marley_the_dog@mcneel.com", "name": "Marley", "locale": "en-gb", "picture":"http://marley.the.dog.com/images/coolpic.png" } "licenseCluster": { "licenses":[ ONE OR MORE LICENSE OBJECTS ... ] } } ``` - `entityId`: The id of the entity whom the license will be removed from. Entities can be individual users or groups as defined in [Rhino Accounts](https://accounts.rhino3d.com/help). - `entityType`: Entities can be individual users (`User`) or groups (`Group`) as defined in [Rhino Accounts](https://accounts.rhino3d.com/help). - `userInfo`: The decoded JWT representing the user’s OpenID Token who wants to add the license to the specified entity. See [Rhino Accounts OpenID Tokens](https://docs.google.com/document/d/1-U0FYt6iQAM3UA6Rio4z0sDVXBSdc0kQk5e4zumnKig/edit#heading=h.qctsih4c0ctd) for details. The user is always a privileged member of the entity or is the entity itself in case `entityType` is `User`. Additional fields could be present and MAY be used but SHOULD NOT be assumed to be present. - `licenseCluster`: The [License Cluster object](/guides/rhinocommon/cloudzoo/cloudzoo-licensecluster) to be removed. #### Required Response The response will vary depending on the issuer’s decision as described below: A successful response (The license can be removed): - HTTP Status Code: `200 (OK)` - Response Payload: Empty. [See an actual example in our GitHub example](https://github.com/mcneel/cloudzoo-issuer/blob/7afc34a458589e17217e7e857b5b9695c46fe8ca/app.py#L227) A non-successful (error) response (The license cannot be removed): - HTTP Status Code: A code greater or equal to `400 (Bad Request)` - Response Payload: [A non-successful response](#non-successful-responses) #### Remarks This callback MUST be idempotent, meaning that it MUST consistently return the same result given a set of arguments. This is because the callback may be called more than one time with the same arguments by the system as part of a transaction that could be retried. -------------------------------------------------------------------------------- # Light Attenuation Source: https://developer.rhino3d.com/en/guides/cpp/light-attenuation/ This brief guide discusses light attenuation in Rhino. ## Problem So you are interested in taking advantage `ON_Light::Attenuation` in your render plugin, and you need to clarify how you can use it. Is the input vector supposed to represent the distance over which you want the light to attenuate to zero? ## Solution Light attenuation determines how fast the light intensity decreases with distance from objects. The three coefficients to light attenuation are: 1. Constant attenuation ($$C$$) 1. Linear attenuation ($$L$$) 1. Quadratic attenuation ($$Q$$) Thus, you could create the input vector as follows: ```cpp ON_3dVector attenuation( C, L, Q ); ``` **NOTE**: Rhino's user interface only uses constant attenuation so that adding a light reveals everything, no matter how far away the light source is from any given piece of geometry. ## Types of Attenuation ### Constant If you want constant attenuation, or: $$1 \over C$$ then both $$L$$ and $$Q$$ must be $$0$$ and $$C > 0$$ (usually $$= 1.0$$). ### Linear If you want linear attenuation: $$1 \over C + dL$$ where $$d$$ = distance to light, then $$C$$ and $$L$$ vary and $$Q$$ must be $$0$$. ### Quadratic If you want quadratic attenuation: $$1 \over C + dL + d^{2Q}$$ then all 3 coefficients can and should vary. -------------------------------------------------------------------------------- # Lines in Python Source: https://developer.rhino3d.com/en/guides/rhinopython/python-rhinoscriptsyntax-line/ This guide provides an overview of a RhinoScriptSytntax Line Geometry in Python. ## Lines 3-D lines, or chords, are represented as zero-based, one-dimensional arrays that contain two elements: the starting 3-D point and the ending 3-D point. A 3-D line is represented by a start pint and an ednpoint. line1 = ((1.0, 2.0, 3.0), (4.0, 5.0, 6.0)) To add the line to the current Rhino file, and see it drawn on screen, you can use the `AddLine` method in RhinoscriptSytnax: ```python import rhinoscriptsyntax as rs startPoint = (1.0, 2.0, 3.0) endPoint = (4.0, 5.0, 6.0) lineID = rs.AddLine(startPoint,endPoint) print(lineID) ``` When adding geometry to Rhino, rhinoscriptsyntax will return an 'Object ID' for the added object. The Rhino file has the object added as geometry in the file. Just like an object added using a Rhino command, it has an ID that is a unique reference to this object. This makes it possible and easy to get access to the specific object later in the script. Saving this ID in a variable or a list is what allows it to be used later to select and manipulate the object. RhinoScript contains a number of methods to manipulate lines. See [Lines and Planes](/guides/rhinopython/python-rhinoscriptsyntax-line-plane-methods) for details. ## Related Topics - [What is Python and RhinoScript?](/guides/rhinopython/what-is-rhinopython) - [Python Points](/guides/rhinopython/python-rhinoscriptsyntax-points) - [Python Vectors](/guides/rhinopython/python-rhinoscriptsyntax-vectors) - [Python Lines](/guides/rhinopython/python-rhinoscriptsyntax-lines) - [Python Planes](/guides/rhinopython/python-rhinoscriptsyntax-planes) - [Python Objects](/guides/rhinopython/python-rhinoscriptsyntax-objects) -------------------------------------------------------------------------------- # Migrate your plug-in project to Rhino 8 Source: https://developer.rhino3d.com/en/guides/cpp/migrate-your-plugin-windows-8/ This guide walks you through migrating your Rhino 7 plug-in project to Rhino 8. ## Prerequisites It is presumed you already have the necessary tools installed and are ready to go. If you are not there yet, see [Installing Tools (Windows)](/guides/cpp/installing-tools-windows) ## Migrate the project To migrate your Rhino 7 C++ plugin project to Rhino 8: 1. Launch *Visual Studio* and click *File* > *Open* > *Project/Solution...*. 2. Navigate to your project's folder and open either your plugin project *(.vcxproj)* or solution *(.sln)* 3. When your plugin project opens, Visual Studio may display the *Retarget Projects* dialog box. Just click **Cancel**. ![*Retarget Projects*](https://developer.rhino3d.com/images/migrate-plugin-windows-8-cpp-01.png) ## Replace property sheets The Rhino C/C++ SDK includes Visual Studio Property Sheets that provide a convenient way to synchronize or share common settings among other plugin projects. You will need to remove references to Rhino 7 C/C++ SDK property sheets and replace them with references to Rhino 8 C/C++ SDK property sheets. 1. From *Visual Studio*, click *View* > *Property Manager*. ![Property Manager](https://developer.rhino3d.com/images/migrate-plugin-windows-8-cpp-02.png) 2. Right-click on the *Rhino.Cpp.PlugIn* property sheets in both *Debug | x64* and *Release | x64* configurations and click *Remove*. 3. Right-click on the *Debug | x64* configuration and click *Add Existing Property Sheet*. 4. Navigate to the following location: *C:\Program Files\Rhino 8.0 SDK\PropertySheets* 5. Select *Rhino.Cpp.PlugIn.props* and click *OK*. 6. Repeat the above steps for the the *Release | x64* configuration. 7. Save the changes to your solution. Your plugin project should now be ready to build with the Rhino 8 C/C++ SDK. ## Related Topics - [What's New?](/guides/cpp/whats-new) -------------------------------------------------------------------------------- # Render Engine Integration - Interactive Viewport Source: https://developer.rhino3d.com/en/guides/rhinocommon/render-engine-integration-interactive-viewport/ This guide, the fourth of a series, covers integrating render engines in Rhino's viewport. ## Overview This is part four in the series on render engine integration in Rhinoceros 3D using RhinoCommon. 1. [Setting up the plug-in](/guides/rhinocommon/render-engine-integration-introduction/) 1. [Modal Rendering](/guides/rhinocommon/render-engine-integration-modal/) 1. [ChangeQueue](/guides/rhinocommon/render-engine-integration-changequeue/) 1. Interactive render - viewport integration (this guide) 1. Preview render *(forthcoming)* If you have not already read the first three parts, please do so before proceeding. ## Realtime Display For this plug-in we are going to do things in a slightly different way. Not because it is a must, but because it gives an interesting possibility for plug-in developers who want to integrate their own render engines, but without exposing it to the `_Render` command. We do that with a generic utility plug-in. There won't be an API to implement for the `_Render` command, instead we'll implement two new classes. One derived from `Rhino.Render.RealtimeDisplayMode` and one derived from `Rhino.Render.RealtimeDisplayModeClassInfo`. Together these will effectively create and register a conduit that is used during the drawing process of a viewport to display the result of the render engine. For this example a `ChangeQueue` implementation is used, but as said in earlier articles it is possible to do your data conversion directly from the `RhinoDoc`. If the render engine to be integrated is one using mesh data for geometry I advise strongly to use the `ChangeQueue`. ### Utility plug-in ```cs public class MockingViewportPlugIn : Rhino.PlugIns.PlugIn { public MockingViewportPlugIn() { if (Instance == null) Instance = this; } public static MockingViewportPlugIn Instance { get; private set; } protected override LoadReturnCode OnLoad(ref string errorMessage) { // RealtimeDisplayMode.RegisterDisplayModes(this); // call to RegisterDisplayModes no longer necessary, it is automatically called. return LoadReturnCode.Success; } } ``` The plug-in code is very lean, only `LoadRetunCode OnLoad()` needs to be overridden. With a proper RealtimeDisplayModeClassInfo and RealtimeDisplayMode implementation the new viewport mode will be registered with Rhino. It'll show up in the viewport mode dropdown list. ### Registering with Rhino ```cs public class MockingRealtimeDisplayModeInfo : RealtimeDisplayModeClassInfo { public override string Name => "MockingRealtimeMode"; public override Guid GUID => new Guid("F14A3A24-C2FB-4216-9D2A-9636EF3869FA"); public override Type RealtimeDisplayModeType => typeof (MockingRealtimeDisplayMode); } ``` Implement a class derived from `Rhino.Render.RealtimeDisplayModeClassInfo`. When the plug-in is loaded the automatic registration procedure of the plug-in ensures that this information is used to identify the `RealtimeDisplayMode` implementation of the plug-in. All of the properties of the class are important, but pay especially close attention to `Guid GUID`. This has to be unique from other plug-ins, so don't ever copy-paste Guids from sample code. The `Type RealtimeDisplayModeType`property should return the type of your `RealtimeDisplayMode` implementation. After the plug-in is loaded the viewport mode can be found from the mode drop-down list. ![viewports modes droplist](https://developer.rhino3d.com/images/mockingbird//mockingbird_viewport_001_viewport_modes_droplist.png) ### The viewport implementation The actual viewport integration is done with a class deriving from `RealtimeDisplayMode`. When deriving from that class, which we do with `MockingRealtimeDisplayMode`, Visual Studio will tell that several abstract methods need to be implemented. These methods are the minimum required functions to ensure proper functioning of the integration. The entire class can be found in the Git repository here. Lets step through the process what happens when the user selects the display mode for the viewport. First of all an instance of our class will be created. If there is the need for initialisation a public default constructor can be implemented where such initialisation can be done. For `MockingRealtimeDisplayMode` we don't need that. During the start up phase of the mode switch the underlying system will be querying whether our engine is started, and whether there are results available. Because this can happen already before we've actually managed to start our engine and get some results we'll be using a boolean flag `_started` to communicate our state through the functions `IsRendererStarted()` and `IsFrameBufferAvailable()`. Our real entry into the rendering process happens with `StartRenderer()`. ```cs public override bool StartRenderer(int w, int h, RhinoDoc doc, ViewInfo view, ViewportInfo viewportInfo, bool forCapture, RenderWindow renderWindow) { _started = true; // prepare render, get a changequeue _width = (int)w; _height = (int)h; reng = new MockingRender(Rhino.PlugIns.PlugIn.IdFromName("MockingBirdViewport"), doc.RuntimeSerialNumber, view, renderWindow) { MaxPasses = 50 }; reng.MaxPassesCompleted += Reng_MaxPassesCompleted; reng.PassRendered += Reng_PassRendered; reng.RenderReset += Reng_RenderReset; reng.RenderStarted += Reng_RenderStarted; MaxPassesChanged += MockingRealtimeDisplayMode_MaxPassesChanged; // start rendering _startTime = DateTime.Now; _isCompleted = false; _theThread = new Thread(reng.ColorPixels) {Name = "MockingBirdViewport thread"}; _theThread.Start(); return true; } ``` For this example I opted to implement a very simple 'render engine' to show how working with threads in this environment can be done. So I start by creating an instance of that engine `MockingRender`. This engine uses a `ChangeQueue` internally, so I give the plug-in ID, Rhino document runtime serial number, the `ViewInfo` instance and the `RenderWindow` instance to it. To make communication between the `MockingRealtimeDisplayMode` and `MockingRender` easy I have added several events to `MockingRender`. I register necessary handlers for those. Once the render engine has completed a pass it fires the `PassRendered` event. In the handler the underlying system gets notified about that fact by calling the `SignalDraw()` function provided by the base class `RealtimeDisplayMode`. ```cs private void Reng_PassRendered(object sender, PassRenderedEventArgs e) { _currentPass = e.Pass; SignalRedraw(); } ``` The actual rendering The `MockingRender` class is responsible for generating the pixel data for the viewport. Its entry function is ColorPixels(). ```cs public void ColorPixels() { RenderStarting?.Invoke(this, EventArgs.Empty); Passes = 0; RenderStarted?.Invoke(this, EventArgs.Empty); while (true) { while (Passes < MaxPasses) { ColorPass(Passes); Thread.Sleep(10); if (_shutdown) break; Passes += 1; } if (!_completionTriggered && Passes >= MaxPasses) { _completionTriggered = true; MaxPassesCompleted?.Invoke(this, EventArgs.Empty); } Thread.Sleep(10); if (_shutdown) break; } } ``` It essentially goes into an eternal loop that runs until the `_shutdown` flag is set. The render process goes through each pass (and simulates some extra workload by sleeping a whopping 10 milliseconds), then essentially waits for `Passes` to get reset or `MaxPasses` change such that `Passes < MaxPasses`. `ColorPass()` then fills the pixel buffer, presented to the render engine by means of the `RenderWindow`. Increasing `Passes` will also fire the `PassRendered` event. ```cs private void ColorPass(int pass) { var fac = pass/(float)MaxPasses; var c = Color4f.FromArgb(1.0f, _color.R*fac, _color.G*fac, _color.B*fac); using (var channel = rw.OpenChannel(RenderWindow.StandardChannels.RGBA)) { var size = rw.Size(); for (var x = 0; x < size.Width; x++) { for (var y = 0; y < size.Height; y++) { channel.SetValue(x, y, c); } if (_shutdown) break; } } } ``` In `ColorPass()` above the most important part to look at is line 5. With the `using` idiom the necessary channel from the `RenderWindow` is opened (`RGBA` in general, there are other channels too, though, but not in the scope of this article series), then filled with color data per pixel. The using idiom ensures the opened channel is properly disposed of. Note that the simplest possible pixel buffer filling code would be to have the channel `SetValue` loop directly in `StartRenderer()` and leave out the entire `MockingRender` and `Thread` construct. ## Next Steps *Congratulations!* These are the steps necessary to integrate a new render engine into Rhino viewport for interactive, real-time rendering using the RhinoCommon SDK. *Now what?* This is part four in the series on render engine integration in Rhinoceros using RhinoCommon. The next guide (forthcoming) will demonstrate implementing a preview render. ## Related Topics - [Render Engine Integration - Introduction](/guides/rhinocommon/render-engine-integration-introduction/) - [Render Engine Integration - Modal](/guides/rhinocommon/render-engine-integration-modal/) - [Render Engine Integration - ChangeQueue](/guides/rhinocommon/render-engine-integration-changequeue/) - Preview render *(forthcoming)* -------------------------------------------------------------------------------- # Script Editor Options Source: https://developer.rhino3d.com/en/guides/scripting/editor-configs/ Provides information on editing and language support options in Script Editor **Editor Options** dialog (*Tools > Options* menu) provides access to editor and language settings that are persistent. Hovering over help icons provides more information about each option: ![](https://developer.rhino3d.com/en/guides/scripting/editor-configs/editor-options-tipdot.png) ## General Options General options are under **General** tab of *Editor Options* dialog and are self explanatory: ![](https://developer.rhino3d.com/en/guides/scripting/editor-configs/editor-options-general.png) Grasshopper Script Editor has a few layout options under *General* tab. *Window* menu also shows toggles for these options: ![](https://developer.rhino3d.com/en/guides/scripting/editor-configs/editor-options-general-embedded.png) *Window > Toggle \** menu items provide toggles for Editor UI elements. Editor remembers the last UI layout before it is closed. See [Layout Options: Python](/guides/scripting/scripting-gh-python/#layout-options) or [C#](/guides/scripting/scripting-gh-csharp/#layout-options) for more information on these options. ## Editing Options Editing options are language-specific. Each language tab has its own editing options: ![](https://developer.rhino3d.com/en/guides/scripting/editor-configs/editor-options-editing.png) *Edit > Toggle \** menu items provide toggles for some of the options. These changes are in-session only and do not get saved to settings file. See [Editing Features](/guides/scripting/editor-editing/) for more information on these options. ## Language Support Options Language Support options are language-specific. Each language tab has its own language options: ![](https://developer.rhino3d.com/en/guides/scripting/editor-configs/editor-options-langserver.png) *Edit > Toggle \** menu items provide toggles for some of the options. These changes are in-session only and do not get saved to settings file. See [Editing Features](/guides/scripting/editor-editing/) for more information on these options. ## Python Paths ### Scripts Path By default, Rhino adds these paths to Python 2 and 3 search paths (`sys.path`): On Windows: - **Shared** `%PROGRAMDATA%\McNeel\Rhinoceros\.0\scripts` (if exists) - **User** `%APPDATA%\McNeel\Rhinoceros\.0\scripts` On macOS: - **Shared** `/Users/Shared/McNeel/Rhinoceros/.0/scripts` (if exists) - **User** `~/Library/Application Support/McNeel/Rhinoceros/.0/scripts` Note that the first path on either platform, is the shared path and takes precedence over the user path. So if python module `test` is available in both paths, the one under shared path will be imported. Shared `scripts` path are not created by default so there is not a conflict unless you, your system admin, or other third-party plugin intentionally places python modules or scripts in this path. ### Editor Paths You can add a list of other search paths for each Python version in Editor options: ![](https://developer.rhino3d.com/en/guides/scripting/editor-configs/editor-options-python-paths.png) Note that the order of these paths is important. First path on the list would be the first path to be search for a module. Editor stores these paths in two `.rhinocode/python-3.pth` and `.rhinocode/python-2.pth` files. See [Path Files]() for more information. -------------------------------------------------------------------------------- # Simple Data Types Source: https://developer.rhino3d.com/en/guides/grasshopper/simple-data-types/ This guide discusses how Grasshopper deals with data items and types. ## Overview It's a rather complicated topic as data is an integral part of the Grasshopper process and GUI. Grasshopper needs to be able to (de)serialize data, display data in tooltips, convert data to other types of data, prompt the user for persistent data, draw geometry preview data in viewports and bake geometric data. In this topic I'll only talk about non-geometric data, we'll get to previews and baking in a later topic. Practically all native data types in Grasshopper are based either on a .NET Framework type or a RhinoCommon type. For example `System.Boolean`, `System.String`, `Rhino.Geometry.Point3d` and `Rhino.Geometry.Brep` to name but a few. However, the parameters in Grasshopper don't directly store Booleans, String, Points and Breps as these types can't handle themselves in the cauldron that is Grasshopper. ## Prerequisites We will not be dealing with any of the basics of component development in this guide. Please start with the [Your First Component](/guides/grasshopper/your-first-component-windows) guide and the [Simple Component](/guides/grasshopper/simple-component) guide before starting this one. Before you start, create a new class that derives from `Grasshopper.Kernel.GH_Component`, as outlined in the [Simple Component](/guides/grasshopper/simple-component) guide. ## The IGH_Goo interface In this section I'll briefly discuss all the methods and properties that are defined in IGH_Goo. What they're for, who uses them at what time, etc, etc. All data used in Grasshopper must implement the [IGH_Goo](/api/grasshopper/html/T_Grasshopper_Kernel_Types_IGH_Goo.htm) interface. `IGH_Goo` defines the bare minimum of methods and properties for any kind of data before it is allowed to play ball. ### IsValid ```cs bool IsValid { get{} } ``` ```vbnet ReadOnly Property IsValid() As Boolean ``` Not all data types are valid all the time, and this property allows you to test the current instance of your data. When data is invalid it will often be ignored by components. ### TypeName ```cs string TypeName { get{} } ``` ```vbnet ReadOnly Property TypeName() As String ``` This property must return a human-readable name for your data type. ### TypeDescription ```cs string TypeDescription { get{} } ``` ```vbnet ReadOnly Property TypeDescription() As String ``` This property must return a human-readable description of your data type. ### Duplicate ```cs IGH_Goo Duplicate() ``` ```vbnet Function Duplicate() As IGH_Goo ``` This function must return an exact duplicate of the data item. Data is typically shared amongst multiple Grasshopper parameters, so before data is changed, it first needs to copy itself. When data only contains ValueTypes and Primitives, making a copy of an instance is usually quite easy. ### ToString ```cs string ToString() ``` ```vbnet Function ToString() As String ``` This function is called whenever Grasshopper needs a human-readable version of your data. It is this function that populates the data panel in parameter tooltips. You don't need to create a String that is parsable, it only needs to be somewhat informative. ### EmitProxy ```cs IGH_GooProxy EmitProxy() ``` ```vbnet Function EmitProxy() As IGH_GooProxy ``` Data proxies are used in the Data Collection Manager. You can ignore this function (i.e. Return Nothing) without crippling your data type. ### CastTo ```cs bool CastTo(out T target) ``` ```vbnet Function CastTo(Of T)(ByRef target As T) As Boolean ``` Data Casting is a core feature of Grasshopper data. It basically allows data types defined in Grasshopper add-ons to become an integral part of Grasshopper. Lets assume that we have a Component that operates on data type [A]. But instead of playing nice, we provide data of type [B]. Two conversion (casting) attempts will be made in order to change [B] into [A]. If [B] implements IGH_Goo, then it is asked if it knows how to convert itself into an instance of [A]. Failing that, if [A] implements `IGH_Goo`, it is asked whether or not it knows how to construct itself from an instance of [B]. The `CastTo()` function is responsible for step 1. The `CastTo()` method is a generic method, meaning the types on which it operates are not defined until the method is actually called. This allows the function to operate "intelligently" on data types. It also unfortunately means you have to be "intelligent" when implementing this function. See the [Grasshopper Data Types](/guides/grasshopper/grasshopper-data-types) guide for more information on conversion. ### CastFrom ```cs bool CastFrom(object source) ``` ```vbnet Function CastFrom(ByVal source As Object) As Boolean ``` The `CastFrom()` function is responsible for step 2 of data casting. Some kind of data is provided as a source object and if the local Type knows how to "read" the source data it can perform the conversion. See the [Grasshopper Data Types](/guides/grasshopper/grasshopper-data-types) guide for more information on conversion. ### ScriptVariable ```cs object ScriptVariable() ``` ```vbnet Function ScriptVariable() As Object ``` When data is fed into a VB or C# script component, it is usually stripped of `IGH_Goo` specific data and methods. The `ScriptVariable()` method allows a data type to provide a stripped down version of itself for use in a Script component. ## The GH_Goo abstract class Although all data in Grasshopper must implement the `IGH_Goo` interface, it is not necessary to actually write a type from scratch. It is good practice to inherit from the abstract class `GH_Goo`, as it takes care of some of the basic functionality. `GH_Goo` is a generic type (that's what the "``" bit means), where `T` is the actual type you're wrapping. `GH_Goo` has several abstract methods and properties which *must* be implemented, but a lot of the other methods are already implemented with basic (though usually useless) functionality. ## An Example Type We'll now create a very simple custom type. This will introduce the basic concept of custom type development, without dealing with any of the baking and previewing logic yet. Our custom type will be a TriState flag, similar to boolean values but with an extra state called "Unknown". We'll represent these different states using integers: - Negative One `-1` = Unknown - Zero `0` = False - One `1` = True We'll start with the general class layout, then drill down into each individual function. Create a new public class called `TriStateType` and inherit from `GH_Goo`. Be sure to import the Grasshopper `Kernel` and `Kernel.Types` namespaces as we'll need them both: ```cs using Grasshopper.Kernel; using Grasshopper.Kernel.Types; namespace MyTypes { public class TriStateType : GH_Goo { } } ``` ```vbnet Imports Grasshopper.Kernel Imports Grasshopper.Kernel.Types Namespace MyTypes Public Class TriStateType Inherits GH_Goo(Of Integer) End Class End Namespace ``` ### Constructors Unless a constructor is defined, .NET classes always have a default constructor which initializes all the fields of the class to their default values. This constructor does not require any inputs and when you develop custom types it is a good idea to always provide a default constructor. If there is no default constructor, then class instances cannot be created automatically which thwarts certain algorithms in Grasshopper. In addition to a default constructor I also find it useful to supply so called copy constructors which create a new instance of the type class with a preset value. ```cs // Default Constructor, sets the state to Unknown. public TriStateType() { this.Value = -1; } // Constructor with initial value public TriStateType(int tristateValue) { this.Value = tristateValue; } // Copy Constructor public TriStateType(TriStateType tristateSource) { this.Value = tristateSource.Value; } // Duplication method (technically not a constructor) public override IGH_Goo Duplicate() { return new TriStateType(this); } ``` ```vbnet '' Default Constructor, sets the state to Unknown. Public Sub New() Me.Value = -1 End Sub '' Constructor with initial value Public Sub New(ByVal tristateValue As Integer) Me.Value = tristateValue End Sub '' Copy Constructor Public Sub New(ByVal tristateSource As TriStateType) Value = tristateSource.Value End Sub '' Duplication method (technically not a constructor) Public Overrides Function Duplicate() As Kernel.Types.IGH_Goo Return New TriStateType(Me) End Function ``` Incidentally, the Value property which we are using to assign integers to our local instance is provided by the `GH_Goo` base class. `GH_Goo` defines a protected field of type `T` called `m_value` and also a public accessor property called `Value` which gets or sets the `m_value` field. In this particular case, it actually makes sense to override the default `Value` property implementation, as the number of sensible values we can assign (-1, 0 and +1) is a subset of the total number values available through the Integer data type. It makes no sense to assign -62 for example. We *could* of course agree that all negative values indicate an "Unknown" state, but we should try to restrict ourselves to only three integer values: ```cs // Override the Value property to strip non-sensical states. public override int Value { get { return base.Value; } set { if (value < -1) { value = -1; } if (value > +1) { value = +1; } base.Value = value; } } ``` ```vbnet '' Override the Value property to strip non-sensical states. Public Overrides Property Value() As Integer Get Return MyBase.Value End Get Set(ByVal value As Integer) If (value < -1) Then value = -1 If (value > +1) Then value = +1 MyBase.Value = value End Set End Property ``` ### Formatters Formatting data is primarily a User Interface task. Both the data type and the data state need to be presented in human-readable form every now and again. This mostly involves readonly properties as looking at data does not change its state: ```cs // TriState instances are always valid public override bool IsValid { get { return true; } } // Return a string with the name of this Type. public override string TypeName { get { return "TriState"; } } // Return a string describing what this Type is about. public override string TypeDescription { get { return "A TriState Value (True, False or Unknown)"; } } // Return a string representation of the state (value) of this instance. public override string ToString() { if (this.Value == 0) { return "False"; } if (this.Value > 0) { return "True"; } return "Unknown"; } ``` ```vbnet '' TriState instances are always valid Public Overrides ReadOnly Property IsValid() As Boolean Get Return True End Get End Property '' Return a string with the name of this Type. Public Overrides ReadOnly Property TypeName() As String Get Return "TriState" End Get End Property '' Return a string describing what this Type is about. Public Overrides ReadOnly Property TypeDescription() As String Get Return "A TriState Value (True, False or Unknown)" End Get End Property '' Return a string representation of the state (value) of this instance. Public Overrides Function ToString() As String If (Value = 0) Then Return "False" If (Value > 0) Then Return "True" Return "Unknown" End Function ``` ### Serialization Some data types can be stored as *persistent data*. Persistent data must be able to serialize and deserialize itself from a Grasshopper file. Most simple types support this feature (Booleans, Integers, Strings, Colours, Circles, Planes etc.), most complex geometry types cannot be stored as persistent data (Curves, Breps, Meshes). If possible, you should aim to provide robust (de)serialization for your data: ```cs // Serialize this instance to a Grasshopper writer object. public override bool Write(GH_IO.Serialization.GH_IWriter writer) { writer.SetInt32("tri", this.Value); return true; } // Deserialize this instance from a Grasshopper reader object. public override bool Read(GH_IO.Serialization.GH_IReader reader) { this.Value = reader.GetInt32("tri"); return true; } ``` ```vbnet '' Serialize this instance to a Grasshopper writer object. Public Overrides Function Write(ByVal writer As GH_IO.Serialization.GH_IWriter) As Boolean writer.SetInt32("tri", Value) Return True End Function '' Deserialize this instance from a Grasshopper reader object. Public Overrides Function Read(ByVal reader As GH_IO.Serialization.GH_IReader) As Boolean Value = reader.GetInt32("tri") Return True End Function ``` ### Casting There are three casting methods on `IGH_Goo`; the `CastFrom()` and `CastTo()` methods that facilitate conversions between different types of data and the `ScriptVariable()` method which creates a safe instance of this data to be used inside untrusted code (such as VB or C# Script components). ```cs // Return the Integer we use to represent the TriState flag. public override object ScriptVariable() { return this.Value; } // This function is called when Grasshopper needs to convert this // instance of TriStateType into some other type Q. public override bool CastTo(ref Q target) { //First, see if Q is similar to the Integer primitive. if (typeof(Q).IsAssignableFrom(typeof(int))) { object ptr = this.Value; target = (Q)ptr; return true; } //Then, see if Q is similar to the GH_Integer type. if (typeof(Q).IsAssignableFrom(typeof(GH_Integer))) { object ptr = new GH_Integer(this.Value); target = (Q)ptr; return true; } //We could choose to also handle casts to Boolean, GH_Boolean, //Double and GH_Number, but this is left as an exercise for the reader. return false; } // This function is called when Grasshopper needs to convert other data // into TriStateType. public override bool CastFrom(object source) { //Abort immediately on bogus data. if (source == null) { return false; } //Use the Grasshopper Integer converter. By specifying GH_Conversion.Both //we will get both exact and fuzzy results. You should always try to use the //methods available through GH_Convert as they are extensive and consistent. int val; if (GH_Convert.ToInt32(source, out val, GH_Conversion.Both)) { this.Value = val; return true; } //If the integer conversion failed, we can still try to parse Strings. //If possible, you should ensure that your data type can 'deserialize' itself //from the output of the ToString() method. string str = null; if (GH_Convert.ToString(source, out str, GH_Conversion.Both)) { switch (str.ToUpperInvariant()) { case "FALSE": case "F": case "NO": case "N": this.Value = 0; return true; case "TRUE": case "T": case "YES": case "Y": this.Value = +1; return true; case "UNKNOWN": case "UNSET": case "MAYBE": case "DUNNO": case "?": this.Value = -1; return true; } } //We've exhausted the possible conversions, it seems that source //cannot be converted into a TriStateType after all. return false; } ``` ```vbnet '' Return the Integer we use to represent the TriState flag. Public Overrides Function ScriptVariable() As Object Return Value End Function '' This function is called when Grasshopper needs to convert this '' instance of TriStateType into some other type Q. Public Overrides Function CastTo(Of Q)(ByRef target As Q) As Boolean 'First, see if Q is similar to the Integer primitive. If (GetType(Q).IsAssignableFrom(GetType(Integer))) Then Dim ptr As Object = Value target = DirectCast(ptr, Q) Return True End If 'Then, see if Q is similar to the GH_Integer type. If (GetType(Q).IsAssignableFrom(GetType(GH_Integer))) Then Dim int As Object = New GH_Integer(Value) target = DirectCast(int, Q) Return True End If 'We could choose to also handle casts to Boolean, GH_Boolean, 'Double and GH_Number, but this is left as an exercise for the reader. Return False End Function '' This function is called when Grasshopper needs to convert other data '' into TriStateType. Public Overrides Function CastFrom(ByVal source As Object) As Boolean 'Abort immediately on bogus data. If (source Is Nothing) Then Return False 'Use the Grasshopper Integer converter. By specifying GH_Conversion.Both 'we will get both exact and fuzzy results. You should always try to use the 'methods available through GH_Convert as they are extensive and consistent. Dim int As Integer If (GH_Convert.ToInt32(source, int, GH_Conversion.Both)) Then Value = int Return True End If 'If the integer conversion failed, we can still try to parse Strings. 'If possible, you should ensure that your data type can 'deserialize' itself 'from the output of the ToString() method. Dim str As String = Nothing If (GH_Convert.ToString(source, str, GH_Conversion.Both)) Then Select Case str.ToUpperInvariant() Case "FALSE", "F", "NO", "N" Value = 0 Return True Case "TRUE", "T", "YES", "Y" Value = +1 Return True Case "UNKNOWN", "UNSET", "MAYBE", "DUNNO", "?" Value = -1 Return True End Select End If 'We've exhausted the possible conversions, it seems that source 'cannot be converted into a TriStateType after all. Return False End Function ``` ## Related Topics - [Grasshopper Data Types](/guides/grasshopper/grasshopper-data-types) - [Simple Component](/guides/grasshopper/simple-component) - [Simple Parameters](/guides/grasshopper/simple-parameters) -------------------------------------------------------------------------------- # VBScript Data Types Source: https://developer.rhino3d.com/en/guides/rhinoscript/vbscript-datatypes/ This guide is an overview of VBScript Data Types. ## Overview VBScript has only one data type called a Variant. A Variant is a special kind of data type that can contain different kinds of information, depending on how it is used. Because Variant is the only data type in VBScript, it is also the data type returned by all functions in VBScript. At its simplest, a Variant can contain either numeric or string information. A Variant behaves as a number when you use it in a numeric context and as a string when you use it in a string context. That is, if you are working with data that looks like numbers, VBScript assumes that it is numbers and does what is most appropriate for numbers. Similarly, if you're working with data that can only be string data, VBScript treats it as string data. You can always make numbers behave as strings by enclosing them in quotation marks (`" "`). ## Variant Subtypes Beyond the simple numeric or string classifications, a Variant can make further distinctions about the specific nature of numeric information. For example, you can have numeric information that represents a date or a time. When used with other date or time data, the result is always expressed as a date or a time. You can also have a rich variety of numeric information ranging in size from Boolean values to huge floating-point numbers. These different categories of information that can be contained in a Variant are called subtypes. Most of the time, you can just put the kind of data you want in a Variant, and the Variant behaves in a way that is most appropriate for the data it contains. The following table shows subtypes of data that a Variant can contain: | Subtype | | | | Description | |:--------|:-:|:-:|:-:|:--------| | Empty | | | | Variant is uninitialized. Value is 0 for numeric variables or a zero-length string () for string variables. | | Null | | | | Variant intentionally contains no valid data. | | Boolean | | | | Contains either True or False. | | Byte | | | | Contains integer in the range 0 to 255. | | Integer | | | | Contains integer in the range -32,768 to 32,767. | | Currency | | | | -922,337,203,685,477.5808 to 922,337,203,685,477.5807. | | Long | | | | Contains integer in the range -2,147,483,648 to 2,147,483,647. | | Single | | | | Contains a single-precision, floating-point number in the range -3.402823E38 to -1.401298E-45 for negative values; 1.401298E-45 to 3.402823E38 for positive values. | | Double | | | | Contains a double-precision, floating-point number in the range -1.79769313486232E308 to -4.94065645841247E-324 for negative values; 4.94065645841247E-324 to 1.79769313486232E308 for positive values. | | Date (Time) | | | | Contains a number that represents a date between January 1, 100 to December 31, 9999. | | String | | | | Contains a variable-length string that can be up to approximately 2 billion characters in length. | | Object | | | | Contains an object. | | Error | | | | Contains an error number. | You can use VBScript conversion functions to convert data from one subtype to another. In addition, the `VarType` function returns information about how your data is stored within a Variant. ## Related topics - [What are VBScript and RhinoScript?](/guides/rhinoscript/what-are-vbscript-rhinoscript) - [VBScript Variables](/guides/rhinoscript/vbscript-variables/) -------------------------------------------------------------------------------- # Vectors in Python Source: https://developer.rhino3d.com/en/guides/rhinopython/python-rhinoscriptsyntax-vectors/ This guide provides an overview of RhinoScriptSyntax Vector Geometry in Python. ## Vectors Similar to 3D points, 3D vectors are stored as [Vector3d](https://developer.rhino3d.com/api/RhinoCommon/html/T_Rhino_Geometry_Vector3d.htm) structures. They can be thought as a zero-based, one-dimensional list that contain three numbers. These three number represent to the X, Y and Z coordinate direction of the vector. ```python vector3d contains (1.0, 2.0, 3.0) ``` Here is an easy way to construct a vector: ```python import rhinoscriptsyntax as rs vec = rs.CreateVector(1.0, 2.0, 3.0) ``` A Vector3d's coordinates can be accessed as a list, one element at a time: ```python import rhinoscriptsyntax as rs vec = rs.CreateVector(1.0, 2.0, 3.0) print(vec[0]) #Prints the X coordinate of the Vector3d print(vec[1]) #Print the Y coordinate of the Vector3d print(vec[2]) #Print the Z coordinate of the Vector3d ``` The coordinates of a Vector3d may also be accessed through its `.X`, `.Y` and `.Z` properties: ```python import rhinoscriptsyntax as rs vec = rs.CreateVector(1.0, 2.0, 3.0) print(vec.X) # Prints the X coordinate of the Vector3d print(vec.Y) # Print the Y coordinate of the Vector3d print(vec.Z) # Print the Z coordinate of the Vector3d ``` To change the individual coordinate of a Vector3d, simply assign a new value to the coordinate through the index location or coordinate property: ```python import rhinoscriptsyntax as rs point1 = (1,2,3) point2 = (4,6,7) vec = rs.CreateVector(1.0, 2.0, 3.0) vec[0] = 5.0 # Sets the X coordinate to 5.0 vec.Y = 45.0 # Sets the Y coordinate to 45.0 print(vec) #Print the new coordinates ``` To find the vector between two points, use vector subtraction: ```python point1 = rs.CreatePoint(1,2,3) point2 = rs.CreatePoint(4,5,6) vector = point1 - point2 print(vector) #prints the new coordinates. ``` In the above example, the vector goes from point1 to point2. Reversing this direction is a common mistake. It is important to be sure that the starting point is subtracted from the ending point. Vectors can also be added to points to create new point locations. Here is an example of moving a point location by a vector: ```python # A base point point1 = rs.CreatePoint(1,1,1) # A vector with a direction of 2 units in the X direction vector1 = rs.CreateVector(2.0, 0.0, 0.0) # Setting the coordinate of point1 to to units more in the X direction. vector = point1 + vector1 # point1 + vector1 = [1+2, 1+0, 1+0] = [3,1,1] print(vector) ``` RhinoScriptSyntax contains a number of methods to manipulate vectors. See [RhinoScript Points and Vectors Methods](/guides/rhinopython/python-rhinoscriptsyntax-point-vector-methods) for details. ## Related Topics - [Python Points](/guides/rhinopython/python-rhinoscriptsyntax-points) - [Python Vectors](/guides/rhinopython/python-rhinoscriptsyntax-vectors) - [Python Lines](/guides/rhinopython/python-rhinoscriptsyntax-line) - [Python Planes](/guides/rhinopython/python-rhinoscriptsyntax-plane) - [Python Objects](/guides/rhinopython/python-rhinoscriptsyntax-objects) -------------------------------------------------------------------------------- # Where to find help... Source: https://developer.rhino3d.com/en/guides/rhinopython/python-where-to-find-help/ ## Forums: The RhinoPython community is very active and offers a wonderful resource for posting questions/answers and finding help on just about anything!: [http://python.rhino3d.com/forums/](http://python.rhino3d.com/forums/) ## General References for Python: Python's main website offers a plethora of information about the syntax, building-in functionality, libraries etc! This is the main resource for anything Python! [http://docs.python.org/](http://docs.python.org/) The Python Documentation also has a great introduction into the basics of Python: [http://docs.python.org/tutorial/introduction.html](http://docs.python.org/) [http://docs.python.org/tutorial/](http://docs.python.org/tutorial/) A very useful Python style guide: [http://www.python.org/dev/peps/pep-0008/](http://www.python.org/dev/peps/pep-0008/) Another very thorough resource for Python is from MIT, called "How to Think Like a Computer Scientist": [http://www.greenteapress.com/thinkpython/thinkCSpy/thinkCSpy.pdf](http://www.greenteapress.com/thinkpython/thinkCSpy/thinkCSpy.pdf) ## Common Exceptions/Errors: For a list of common errors, exceptions and pitfalls that you are likely to run into when coding see: [http://docs.python.org/release/3.1.3/library/exceptions.html#bltin-exceptions](http://docs.python.org/release/3.1.3/library/exceptions.html#bltin-exceptions) [http://secant.cs.purdue.edu/_media/proghints.pdf](http://secant.cs.purdue.edu/_media/proghints.pdf) ## Syntax & Programming Reminders: - Python is Case Sensitive ("A" and "a" are NOT the same thing!) - Python is Indent Sensitive (Use indentation to delineate the scope of loops, conditionals, functions and classes) Remember an extra space or the absence of a space can make a world of a difference! - You do NOT need to declare variables or variables types! Just simply use them (x=3)! - The " # " sign is used for comments, the computer will skip over them. - Print and Return are NOT the same thing - print writes something to the screen, return actually passes a value! - Remember Variable Scope - where you define a variable is important! Variables defined within functions & classes can only be used within those functions/classes unless passed as input or through the return statement! - "return" only works inside a function. - Develop code incrementally, testing, debugging and printing as you finish smaller sections. Writing hundreds of lines and hitting run will most likely not work and will make it far more difficult to spot errors! ***If this makes no sense to you yet - no fear! Keep reading (and come back to it later)!... ## Related Topics - [Where to find help - Next Topic >>](/guides/rhinopython/primer-101/1-2-where-to-find-help/) - [Rhino.Python Primer 101](/guides/rhinopython/primer-101/rhinopython101) - [Running Scripts](/guides/rhinopython/python-running-scripts) - [Canceling Scripts](/guides/rhinopython/python-canceling-scripts) - [Editing Scripts](/guides/rhinopython/python-editing-scripts) - [Scripting Options](/guides/rhinopython/python-scripting-options) - [Reinitializing Python](/guides/rhinopython/python-scripting-reinitialize) -------------------------------------------------------------------------------- # Your First Plugin (Mac) Source: https://developer.rhino3d.com/en/guides/rhinocommon/your-first-plugin-mac/ This guide walks you through your first plugin for Rhino for Mac using RhinoCommon and Visual Studio Code. It is presumed you already have the necessary tools installed and are ready to go. If you are not there yet, see [Installing Tools (Mac)](/guides/rhinocommon/installing-tools-mac). ## HelloRhinoCommon We will use Visual Studio Code to create a new, basic, command plugin called HelloRhinoCommon. We are presuming you are new to Visual Studio Code, so we'll go through this one step at a time. ### Download the required template 1. Launch Visual Studio Code. 1. Open _Visual Studio Code's Terminal_ via _Terminal (menu entry)_ > _New Terminal_, or using the command palette _(⌘ ⇧ P)_ and search for "Terminal". 1. Inside Terminal, run: ```pwsh dotnet new install Rhino.Templates ``` ### Starting the Project 1. Create a folder on your mac where you would like your project to live. Name the folder `HelloRhinoCommon`. 1. If you have not done so already, _launch Visual Studio Code_. 1. Now we can open our new folder, navigate to _File_ > _Open Folder_ and choose the folder we just created. 1. Open Terminal via _Terminal_ > _New Terminal_, or using the command palette _(⌘ ⇧ P)_ and search for "Terminal". 1. Enter the following command into the Terminal: ```pwsh dotnet new rhino --version 8 -sample ``` 1. In our Folder explorer, we should see the project appear as Visual Studio Code discovers the files. 1. Expand the Solution Explorer, this is the best way to interact with C# projects on Mac in Visual Studio Code. ### Boilerplate Build 1. Before we do anything, let's _Run and Debug_ HelloRhinoCommon to make sure everything is working as expected. We'll just build the boilerplate Plugin template. Click the _Run and Debug_ button on the left hand side of Visual Studio Code and then the green play button in the newly opened panel. ![New Project](https://developer.rhino3d.com/images/your-first-plugin-mac-03.png) 1. _Rhinoceros_ launches. Click _New Model_. 1. Type `Hello` into the Rhino Commandline. Notice that the command autocompletes. ![Command Autocompletes](https://developer.rhino3d.com/images/your-first-plugin-mac-04.png) 4. The _HelloRhinoCommonCommand_ command lets us draw a line, and then prints out a message 1. Press Stop Debugging _(⇧ F5)_, in Visual Studio Code, signified by the Red Square in the debug toolbar. This stops the debugging session. Go back to _Visual Studio Code_. Let's take a look at the Plugin Anatomy. ### Plugin Anatomy 1. Use the _Solution Explorer_ to expand the project so that it looks like below. ![Solution Anatomy](https://developer.rhino3d.com/images/your-first-plugin-mac-06.png) 1. The _HelloRhinoCommon_ solution (_.sln_) contians all of our projects. This was created for us by the `dotnet` command we ran earlier. 1. The _HelloRhinoCommon_ project (_.csproj_) has the same name as its parent solution. This is the project that was created for us by `dotnet` command we ran earlier. 1. _Dependencies_: Just as with most projects, you will be referencing other libraries. The _RhinoCommon Plugin_ template added the necessary references to create a basic RhinoCommon plugin. 1. _EmbeddedResources_: This is where you would place any image assets you want to ship with your plugin. The _RhinoCommon Plugin_ template added an icon file with a default boilerplate icon. 1. _Properties_ contains the _AssemblyInfo.cs_ source file. This file contains the meta-data (author, version, etc), including the very-important `Guid`, which identifies the plugin. 1. _HelloRhinoCommonCommand.cs_ is where the action is. Let's take a look at this file in the next section below... 1. _HelloRhinoCommonPlugin.cs_ is where this template plugin derives from _Rhino.Plugins.Plugin_ and returns a static Instance of itself. ### Make Changes 1. Open _HelloRhinoCommonCommand.cs_ in Visual Studio Code's Source Editor (if it isn't already). 2. Notice that `HelloRhinoCommonCommand` inherits from `Rhino.Commands.Command` ```c# public class HelloRhinoCommonCommand : Rhino.Commands.Command ``` 3. And that it overrides one inherited property called `EnglishName` ```c# public override string EnglishName => "HelloRhinoCommonCommand"; ``` 4. All Rhino commands must have an `EnglishName` property. This command name will become inaccurate soon, as we're going to spice up our quite pointless command. Let's rename the command to _HelloDrawLine_: ```c# public override string EnglishName => "HelloDrawLine"; ``` 5. Further down, notice that `HelloRhinoCommandCommand` overrides the `RunCommand` method: ```c# protected override Result RunCommand (Rhino.RhinoDoc doc, RunMode mode) ``` 6. And then type in the following by hand on line 62 to get a feel for the editor. ```c# RhinoApp.WriteLine("I'm writing my first Rhino Plugin!"); ``` 7. Notice that - as you type - Visual Studio Code uses IntelliSense, just like Visual Studio for Windows (and many other editors). ### Debugging 1. Set a breakpoint on line 59 of _HelloRhinoCommonCommand.cs_. You set breakpoints in Visual Studio Code by clicking in the gutter to the left of the line numbers. ![Set a breakpoint](https://developer.rhino3d.com/images/your-first-plugin-mac-07.png) 1. _Run and Debug_. our project. The breakpoint will become an empty circle, this is because our code has not been loaded yet. Once we hit the breakpoint once and continue, the code will be loaded until we end our Debug session. ![Set a breakpoint](https://developer.rhino3d.com/images/your-first-plugin-mac-08.png) 1. Click New Model. And then run our _HelloDrawLine_ command. Create the two points and as soon as you do, you should hit your breakpoint and rhino will pause ![Hit a breakpoint](https://developer.rhino3d.com/images/your-first-plugin-mac-09.png) 1. With Rhino paused, in _Visual Studio Code_ we will see _Locals_ under _Variables_. In the list, find the `pt1` object we authored. Click the dropdown _arrow_ to expand the list of members on `pt1`. Our `pt1` is a `Rhino.Geometry.Point3d` this class has an `X`, `Y`, `Z` property just as we'll find documented in the [RhinoCommon API](https://developer.rhino3d.com/api/rhinocommon/rhino.geometry.point3d). ![Locals panel](https://developer.rhino3d.com/images/your-first-plugin-mac-10.png) 1. Let's Continue Execution in Rhino by pressing the Green _Play_ button in the Debug Bar 1. Control is passed back to _Rhino_ and your command finishes. _Quit_ Rhino or _Stop_ the debugging session. _Congratulations!_ You have just built your first RhinoCommon plugin for Rhino for Mac. _Now what?_ ## Next Steps If you'd like to push your exciting new plugin to Yak so that everyone can use it, check out the [Creating a Yak Package](/guides/yak/creating-a-rhino-plugin-package/) guide. If you'd like to collaborate with colleagues and friends commit your plugin to a git repo. [Getting started with GitHub](https://github.blog/developer-skills/github/beginners-guide-to-github-repositories-how-to-create-your-first-repo/). `dotnet new gitignore` will add a gitignore file so unnecessary files will not be committed. Try debugging your new plugin on [Windows](/guides/rhinocommon/your-first-plugin-windows/), all plugins using the new templates are now cross-platform by default. ## Related topics - [Installing Tools (Mac)](/guides/rhinocommon/installing-tools-mac) - [Plugin Installers (Mac)](/guides/rhinocommon/plugin-installers-mac) -------------------------------------------------------------------------------- # Calling Compute with .NET Source: https://developer.rhino3d.com/en/guides/compute/compute-net-getting-started/ This guide covers all the necessary tools required to get started with the Rhino Compute Service in Csharp In this guide, we will detail how you can leverage [Rhino Compute™](https://www.rhino3d.com/compute) from your .NET development environment in Windows to perform geometric operations in your application. To be able to make HTTP requests, it is necessary to have Rhino.Compute running on a server, either locally or remotely. If you don't have it set up yet, please visit one of the following guides: - [Running Rhino.Compute locally](https://developer.rhino3d.com/guides/compute/development/) - [Deployment to production servers](https://developer.rhino3d.com/guides/compute/deploy-to-iis/) ## Prerequisites Before continuing, please ensure you have the right tools. This guide presumes you have an: - Windows Operating System. Rhino.Compute only runs in Windows 7 or later. - Development Environment. You will need one of the IDEs from Microsoft Visual Studio - [Visual Studio 2022](https://visualstudio.microsoft.com/downloads/). The most comprehensive IDE for .NET developers on Windows. - [Visual Studio Code](https://code.visualstudio.com/). A standalone cross-platform IDE. It is free and open source. It is recommended that you install the typical installation. ## Setting up a Compute Project in Visual Studio Once you have your Rhino.Compute server up and running (either local or remote), there are a few tools which are essential to communicate with it. Here are the detailed descriptions of these tools: - [Rhino3dm](https://www.nuget.org/packages/Rhino3dm) - The Dotnet wrapper for [OpenNurbs](https://developer.rhino3d.com/guides/opennurbs/) which contains the functions to read and write Rhino Geometry Objects. As it is available as a NuGet package, you can download directly using the NuGet Package Manager. - [NewtonSoft.JSON](https://www.nuget.org/packages/Newtonsoft.Json/) - The very popular JSON library. This package is called directly by RhinoCompute.cs as the information is defined in JSON format embedded in the body of a REST POST. As rhino3dm, it is also available as a NuGet Package. - [RhinoCompute.cs](https://github.com/mcneel/compute.rhino3d/blob/8.x/src/compute.geometry/RhinoCompute.cs) - This is a work in progress package which is meant to add classes available in RhinoCommon, but not available through Rhino3dm. RhinoCompute makes calls into Rhino for these functions. Here are step by step instructions to setting up a basic project to use Rhino.Compute: ### File New 1. If you have not done so already, **launch Visual Studio**. For the purposes of this guide, we are using Visual Studio 2022 Community Edition and C#. 1. On the right-hand menu, click on **Create a new project**... 1. The New Project wizard should appear. In the right column you will find a list with all the possible project templates you can choose from. To filter among them enter the word *console*. 1. Select the C# version of the **Console App** template and click on **Next**. 1. For the purposes of this guide, we will name our demo plugin *TestCompute*. Enter your desired name in the space reserved for it under the **Project name** field. 1. Below the name, you can select a **Location** for this project on you device. This is optional depending on how you want to structure your project. 1. Click the **Next** button. **Note**: You can place the solution and the project in the same directory to simplify the root path. 1. Select the target framework for your project according to your specifications. In this guide we will choose **.NET 8.0 (Long Term Support)**. 1. Once you click on **Create** a new solution called *TestCompute* should open... ### Steps to install the NuGet packages 1. Right-click your project name in Solution Explorer and select **Manage NuGet Packages ...**. 1. On the left side of the dialog click on **Browse**. 1. In the search box enter **rhino3dm** and wait fo the results to appear. 1. Click on the first one. You will find other packages starting by *Rhino3dmIO*, but all of them have been discontinued. 1. Click on the **Install** button. 1. Continue by searching for **Newtonsoft.JSON**. Click on a *NewtonSoft.JSON* option and click on the **Install** button. 1. Close the Manage NuGet Packages dialog. The Nuget packages are installed and ready to use. Changes that were made: - The *Rhino3dm* and *NewtonSoft.JSON* NuGet packages were installed in your project. - Your project now references the *Rhino3dm* and *Newtonsoft.JSON* assemblies. ### Include RhinoCompute.cs in the project RhinoCompute.cs is a package which adds the methods to call into the Rhino.Compute server. It is organized similarly to RhinoCommon calls. 1. Download the [RhinoCompute.cs](https://github.com/mcneel/compute.rhino3d/blob/8.x/src/compute.geometry/RhinoCompute.cs) source file from the [compute.rhino3d github repo](https://github.com/mcneel/compute.rhino3d). Click on the **Download raw file** icon and store it wherever you prefer on your device. 2. In Visual Studio, using the **Project** pulldown, select **Add Existing Item...** 3. Select the file you have just downloaded and add it to the project. Changes that were made: - The [RhinoCompute.cs](https://github.com/mcneel/compute.rhino3d/blob/8.x/src/compute.geometry/RhinoCompute.cs) source file is now installed and its functions are available in your project. ## The first use of Rhino.Compute As a place to start, let's create a simple console app that shows the standard workflow for calling methods using Rhino.Compute. The guide below shows a simple example of using *Rhino3dm* locally to read, write and create Rhino geometry. Then we will use Rhino.Compute to handle a function that does not exist in *Rhino3dm*. To simplify this example, Rhino.Compute is running locally using the default web address: http://localhost:6500, No API Key is required in this instance. If you are sending a request to a remote VM which is configured to require an API Key, then you will need to provide that information in the HTTP request header. ```C# using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using Rhino.Compute; namespace TestCompute { class Program { static void Main(string[] args) { // Uses standard Rhino3dm methods locally to create a sphere. var sphere = new Rhino.Geometry.Sphere(Rhino.Geometry.Point3d.Origin, 12); var sphereAsBrep = sphere.ToBrep(); // the following function calls rhino.compute to get access to something not // available in Rhino3dm. In this case send a Brep to Compute and get a Mesh back. var meshes = MeshCompute.CreateFromBrep(sphereAsBrep); // Use regular Rhino3dm local calls to count the vertices in the mesh. Console.WriteLine($"Got {meshes.Length} meshes"); for (int i = 0; i < meshes.Length; i++) { Console.WriteLine($" {i + 1} mesh has {meshes[i].Vertices.Count} vertices"); } Console.WriteLine("press any key to exit"); Console.ReadKey(); } } } ``` The example above first creates a sphere using *Rhino3dm*. Then, we make a call to Rhino.Compute to create a mesh from that Brep object. Rhino.Compute then returns the mesh. Finally, we use *Rhino3dm* to walk through the mesh and count the number of vertices.
Line Description
5 Include the Rhino.Compute Assembly from the RhinoCompute.cs Package.
19 Here the OpenNurbs Brep sphere is sent to Compute to convert to a mesh. The Mesh is returned as a OpenNurbs Mesh.
## Seeing the results of Compute Now let's look at another example. Many times the intersection of objects may need to be calculated. Intersections are not in OpenNurbs, but they are part of Rhino.Compute. Here is a tutorial to create two circles, find their intersection, then convert the circles to an SVG image to display. ```C# using System.Collections.Generic; using System.Text; using Rhino.Geometry; namespace CircleIntersection { class Program { static void Main(string[] args) { // create a couple Circles using a local copy of Rhino3dmIo var c1 = new Circle(new Point3d(0, 0, 0), 100); var c2 = new Circle(new Point3d(30, 30, 0), 70); // call compute to perform a curve boolean operation var intersectionCurves = Rhino.Compute.CurveCompute.CreateBooleanIntersection(c1.ToNurbsCurve(), c2.ToNurbsCurve()); Mesh[] intersectionMeshes = null; if (intersectionCurves != null) { // use local Rhino3dmIo to create a Brep from the curves var brep = Brep.CreateTrimmedPlane(c1.Plane, intersectionCurves); // call compute to mesh the Brep intersectionMeshes = Rhino.Compute.MeshCompute.CreateFromBrep(brep, MeshingParameters.FastRenderMesh); } // just some helper routines to create an SVG file of the results so we can see what was generated string path = "circle_intersection.svg"; WriteSvgFile(path, c1, c2, intersectionMeshes); System.Diagnostics.Process.Start(path); } /// /// Very basic SVG file creation routine to make an SVG that displays two circles and a mesh. /// Kept simple just for this sample; there are much better SVG toolkits available that could /// be used for general purpose routines. /// /// /// /// /// static void WriteSvgFile(string path, Circle circle1, Circle circle2, Mesh[] intersectionMeshes) { var doc = new System.Xml.XmlDocument(); doc.AppendChild(doc.CreateXmlDeclaration("1.0", "UTF-8", "no")); doc.XmlResolver = null; doc.AppendChild(doc.CreateDocumentType("svg", "-//W3C//DTD SVG 1.1//EN", "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd", null)); var svgroot = doc.CreateElement("svg"); var bbox = circle1.BoundingBox; bbox.Union(circle2.BoundingBox); var length = (bbox.Max - bbox.Min).Length; bbox.Inflate(length * 0.05); AppendAttribute(doc, svgroot, "viewBox", $"{bbox.Min.X} {bbox.Min.Y} {bbox.Max.X - bbox.Min.X} {bbox.Max.Y - bbox.Min.Y}"); AppendAttribute(doc, svgroot, "version", "1.1"); AppendAttribute(doc, svgroot, "xmlns", "http://www.w3.org/2000/svg"); doc.AppendChild(svgroot); if (intersectionMeshes != null) { foreach (var mesh in intersectionMeshes) { foreach (var face in mesh.Faces) { var elem = doc.CreateElement("polygon"); var attrib = doc.CreateAttribute("fill"); attrib.Value = "#008000"; // green elem.Attributes.Append(attrib); attrib = doc.CreateAttribute("stroke"); attrib.Value = "#008000"; // green elem.Attributes.Append(attrib); attrib = doc.CreateAttribute("points"); StringBuilder sb = new StringBuilder(); var points = new List(); points.Add(mesh.Vertices[face.A]); points.Add(mesh.Vertices[face.B]); points.Add(mesh.Vertices[face.C]); if (face.IsQuad) points.Add(mesh.Vertices[face.D]); for (int i = 0; i < points.Count; i++) { if (i > 0) sb.Append(", "); sb.Append($"{points[i].X}, {points[i].Y}"); } attrib.Value = sb.ToString().Trim(); elem.Attributes.Append(attrib); svgroot.AppendChild(elem); } } } svgroot.AppendChild(SvgCircle(circle1, doc)); svgroot.AppendChild(SvgCircle(circle2, doc)); doc.Save(path); } static void AppendAttribute(System.Xml.XmlDocument doc, System.Xml.XmlElement element, string name, string value) { var attr = doc.CreateAttribute(name); attr.Value = value; element.Attributes.Append(attr); } static System.Xml.XmlElement SvgCircle(Rhino.Geometry.Circle c, System.Xml.XmlDocument doc) { var elem = doc.CreateElement("circle"); var attrib = doc.CreateAttribute("cx"); attrib.Value = $"{c.Center.X}"; elem.Attributes.Append(attrib); attrib = doc.CreateAttribute("cy"); attrib.Value = $"{c.Center.Y}"; elem.Attributes.Append(attrib); attrib = doc.CreateAttribute("r"); attrib.Value = $"{c.Radius}"; elem.Attributes.Append(attrib); attrib = doc.CreateAttribute("fill-opacity"); attrib.Value = "0"; elem.Attributes.Append(attrib); attrib = doc.CreateAttribute("stroke"); attrib.Value = "#000000"; elem.Attributes.Append(attrib); return elem; } } } ``` ## Next Steps **Congratulations!** You have the tools to use the [Rhino.Compute server](https://www.rhino3d.com/compute). *Now what?* 1. Download the [Compute Samples repo from GitHub](https://github.com/mcneel/rhino-developer-samples/tree/8/compute/cs). 1. The libraries are still very new and changing rapidly. Give them a try or get involved. Ask any questions or share what you are working on the [Compute Discussion Forum](https://discourse.mcneel.com/c/serengeti/compute-rhino3d) -------------------------------------------------------------------------------- # Components with a variable number of parameters Source: https://developer.rhino3d.com/en/guides/grasshopper/components-with-variable-number-of-parameters/ This guide has yet to be authored or ported. This guide has yet to be ported to this site. Please check back soon for updates. -------------------------------------------------------------------------------- # Configure Compute to use HTTPS Source: https://developer.rhino3d.com/en/guides/compute/configure-compute-for-https/ ## Overview In this guide, we will walk through the process of creating a valid SSL certificate so that Rhino.Compute can communicate with clients using the HTTPS protocol. ## Prerequisites The following must be completed: 1. You must have an active virtual machine (VM) instance. Use the following guides to walk through setting up a VM. * [Create a Virtual Machine on Azure](../creating-an-Azure-VM). * [Create a Virtual Machine on AWS](../creating-an-aws-vm). 1. The VM must be accessible to the web (open port 80, and 443). 1. A static public IPv4 address must be associated with your virtual machine. To learn more about configuring static IP address, use the following links: * [Configure IP addresses for an Azure network interface](https://learn.microsoft.com/en-us/azure/virtual-network/ip-services/virtual-network-network-interface-addresses?tabs=nic-address-portal#add-ip-addresses) * [Associate an elastic IP address with an EC2 instance](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/elastic-ip-addresses-eip.html) 1. You must have an existing domain and have access to its DNS settings. An **A record** in your DNS settings must point to the public IPv4 address of your virtual machine. For this guide, I have assoicated an elastic IP address with my virtual machine instance. I have also setup an A record in my DNS settings to point *rhino.compute.rhino3d.com* at the IP address of my virtual machine. ## Modify the Host Name Before we step through the process of generating an SSL certificate, we need to make one modification to our existing IIS configuration for Rhino.Compute. 1. If you have not already done so, log into your VM (via RDP). See the section [Connect via RDP](../deploy-to-iis/#connect-via-rdp) for more details. 1. On the **Start** menu, click in the search area and type *Internet Information Services (IIS) Manager*. Click to launch the app. 1. In the IIS Manager, **click** on the web server node in the **Connections** panel on the left to expand the menu tree. Then **click** on the **Sites** node to expand the sub-menu. Lastly, select the **Rhino.Compute** node from the menu tree to adjust its settings. 1. In the **Actions** pane on the right, click **Bindings**. 1. In the **Site Bindings** pane, select the **Add** button. 1. In the **Host name** text field, type in the subdomain name that you created when setting up the A-Record. Click **OK** when done. 1. At this point, you should have two site bindings listed. Click **Close** when done. ## Generate the Certificate The next step in the process is to create and install an SSL certificate for the local IIS server. An SSL certificate is a digital certificate that authenticates a website's identity and enables an encrypted connection. It is required in order to use the HTTPS protocol. To generate the certificate, we recommend using [Win-ACME](https://www.win-acme.com/). Win-ACME is a simple interactive client which can create and install the certificate as well as handle renewing the certificate when needed. 1. [Download the Win-ACME Client](https://github.com/win-acme/win-acme/releases/download/v2.2.2.1449/win-acme.v2.2.2.1449.x64.pluggable.zip) on the virtual machine. Note: Win-ACME is distributed as .zip file. 1. Right-click on the download .zip file and select **Extract All**. It doesn't really matter what directory you choose to extract the files to as we will manually move/rename them in the next step. Click **Extract**. 1. Select the newly extracted directory and type **Ctrl+X** to **Cut** and then **Ctrl+V** to **Paste** this folder into the root C:\\ drive. 1. Now, right-click on the directory that you just copied to the C:\\ drive and select **Rename** from the menu. Shorten the folder name to just *"win-acme"*. 1. Click on the Windows Start menu and type in "Powershell". In the menu that appears, right-click on the **Windows Powershell app** and choose **Run As Administrator**. 1. Type in the following command and hit **Enter** to launch the Win-ACME application. ```powershell C:\win-acme\wacs.exe ``` 1. You should see an interactive menu appear with a set of instructions which can be run by typing in a specific letter. 1. Type the letter **N** and hit **Enter** to create a certificate with the default settings. You should see a list of available IIS sites that are available. If you do not see an entry for **Rhino.Compute (1 binding)** then it is likely that you have not set the host name correctly in the previous step. See the section on [modifying the host name](#modify-the-host-name). 1. Type the number associated with the row for **Rhino.Compute (1 binding)** and hit **Enter**. 1. Hit **Enter** again to accept the default **Pick all bindings**. 1. When prompted to *Continue with this selection?* hit **Enter** or type **Y** for yes. 1. You should see some information printed to the console as it generates the SSL certificate. Congratulations. If successful, the application will then run a series of authorization and validation tests to confirm host is secure. Win-ACME will then generate the SSL certificate and install it with IIS and add a new binding **(*:443)** to the Rhino.Compute site. The SSL certificate will be valid for 90 days. However, the Win-ACME application will create a task scheduler which will try to renew the certificate after 60 days. You should now be able to send an HTTPS request to your Rhino.Compute server and get a valid response back. -------------------------------------------------------------------------------- # Copy Objects to Layer Source: https://developer.rhino3d.com/en/samples/rhinopython/copy-objects-to-layer/ Demonstrates how to copy objects to layer based on Python. ```python import rhinoscriptsyntax as rs def CopyObjectsToLayer(): "Copy selected objects to a different layer" # Get the objects to copy objectIds = rs.GetObjects("Select objects") # Get all layer names layerNames = rs.LayerNames() if (objectIds==None or layerNames==None): return # Make sure select objects are unselected rs.UnselectObjects( objectIds ) layerNames.sort() # Get the destination layer layer = rs.ComboListBox(layerNames, "Destination Layer <" + rs.CurrentLayer() + ">") if layer: # Add the new layer if necessary if( not rs.IsLayer(layer) ): rs.AddLayer(layer) # Copy the objects newObjectIds = rs.CopyObjects(objectIds) # Set the layer of the copied objects [rs.ObjectLayer(id, layer) for id in newObjectIds] # Select the newly copied objects rs.SelectObjects( newObjectIds ) ########################################################################## # Check to see if this file is being executed as the "main" python # script instead of being used as a module by some other python script # This allows us to use the module which ever way we want. if( __name__ == "__main__" ): #call function defined above CopyObjectsToLayer() ``` -------------------------------------------------------------------------------- # Creating Global Sticky Variables Source: https://developer.rhino3d.com/en/guides/rhinopython/ghpython-global-sticky/ Create a variable that sticks around for other components. Sometimes there is a need to share information between Grasshopper and Rhino.Python. This can be done through a global [sticky variable](http://developer.rhino3d.com/samples/rhinopython/sticky-values/) creating a definition wide global sticky variable. Both Rhino.Python and gh.python use the same Python instance, so the Python sticky is shared between the two. With a global sticky variable all the components in a definition can access the information. As an example, here is a definition that creates a simple sticky variable and passes it to another component that is not connected: ```python import scriptcontext as rs rs.sticky["someName"] = x #"x" is the input for the component ``` The scriptcontext.sticky creates a global variable names "someName". This can be referenced in another component using this code: ```python import scriptcontext as rs a = rs.sticky["someName"] #output the value of the sticky ``` Within Rhino.Python, the same value can be accessed the same way through [Rhino.Python sticky](http://developer.rhino3d.com/samples/rhinopython/sticky-values/). ## Related Topics - [Your first script with Python in Grasshopper](/guides/rhinopython/what-is-rhinopython) - [What is Python and RhinoScript?](/guides/rhinopython/what-is-rhinopython) - [Editing Python in Grasshopper](/guides/rhinopython/python-running-scripts) - [Python Guide for Rhino](/guides/rhinopython/) -------------------------------------------------------------------------------- # Digitally Signing LAN Zoo Plugins Source: https://developer.rhino3d.com/en/guides/rhinocommon/digitally-signing-plugins-for-zoo/ This guide discusses how to digitally sign LAN Zoo and Rhino plugins. ## Overview To add plugins to the LAN Zoo, and to call the license functions from within your Rhino plugin, you must digitally sign your plugins using a certificate signed by the *Robert McNeel & Associates Code Signing Authority*. ## Generate Private Key & Certificate Signing Request Follow these steps to generate the necessary info to forward to *Robert McNeel & Associates Code Signing Authority*... 1. Download and install the latest [OpenSSL](https://slproweb.com/products/Win32OpenSSL.html). Note, downloading and installing the "light" version (smaller download) is sufficient. 2. After installation, use Windows Explorer to navigate to the OpenSSL installation folder and double-click on `start.bat` found in the `Bin` folder. 3. From the Windows command prompt that opens, navigate to your plug-in's project folder. 4. Save the contents of [ mcneelcodesigning.zip](https://files.mcneel.com/zoo/mcneelcodesigning.zip) to your plug-in's project folder. 5. From the command prompt, run `CreateRequest.bat `, where *filename* is the name (without an extension) that will be used to save your private key (*.key*), certificate signing request (*.csr*), and final signed digital certificate (*.crt*). 6. You will be prompted to answer some questions. Be sure to answer them correctly... ```cmd C:\Dev\Zoo> CreateRequest.bat TestZooPluginKey Loading 'screen' into random state - done Generating RSA private key, 4096 bit long modulus ................................................................................ e is 65537 (0x10001) Loading 'screen' into random state - done You are about to be asked to enter information that will be incorporated into your certificate request. What you are about to enter is what is called a Distinguished Name or a DN. There are quite a few fields but you can leave some blank For some fields there will be a default value, If you enter '.', the field will be left blank. ----- Country Name (2 letter code) []: State or Province Name (full name) []: Locality Name (eg, city) []: Organization Name (eg, company) []: Organizational Unit Name (eg, section) []: Common Name (eg, your websites domain name) []: Please enter the following 'extra' attributes to be sent with your certificate request A challenge password []: Saved private key: 'TestZooPluginKey.key' Saved CSR: 'TestZooPluginKey.csr' ``` ## Requesting a Signed Digital Certificate 1. Email the certificate signing request (*.csr*) created above to [Brian Gillespie](mailto:brian@mcneel.com) along with a certificate request. 2. We will process your request and, if it is approved, will send you a signed digital certificate (*.crt*). ## Creating a Personal Information Exchange To digitally sign your LAN Zoo or Rhino plugin, convert the signed digital certificate (*.crt*), emailed to you upon approval, into a personal information exchange (*.pfx*) file... 1. Copy the signed digital certificate (*.crt*) you receive into the same folder as your private key (*.key*) and certificate signing request (*.csr*). 2. Use Windows Explorer to navigate to the OpenSSL installation folder and double-click on `start.bat` found in the `Bin` folder. 3. From the Windows command prompt that opens, navigate to the above folder. 4. From the command prompt, run `MakePfxFile.bat `, where *filename* is the name (without an extension)... ```cmd C:\Dev\Zoo\TestZooPlugin>MakePfxFile.bat TestZooPluginKey Loading 'screen' into random state - done Enter Export Password: Verifying - Enter Export Password: Created 'TestZooPluginKey.pfx'. Use this to sign your executable code. ``` ## Sign Your Plugins Now that you have a personal information exchange (*.pfx*), you can use it to sign LAN Zoo and Rhino plugins. 1. Open a *Visual Studio Command Prompt*. 2. Use *Signtool.exe*, with the following syntax, to digitally sign your plugins... ```cmd signtool.exe sign /f .pfx /fd sha256 /tr http://timestamp.digicert.com /td sha256 /v ``` **Note:** If you set a password for your PFX file, above, you'll need to add ```/p ``` to your signing script. Be careful with your password! For example: ```cmd C:\Dev\Zoo\TestZooPlugin> signtool sign /f TestZooPluginKey.pfx /fd sha256 /tr http://timestamp.digicert.com /td sha256 /v TestZooPlugin.dll The following certificate was selected: Issued to: MCNEEL.COM Issued by: McNeel Software Development Expires: SHA1 hash: Done Adding Additional Store Successfully signed and timestamped: TestZooPlugin.dll Number of files successfully Signed: 1 Number of warnings: 0 Number of errors: 0 ``` -------------------------------------------------------------------------------- # Migrate your plug-in project to Rhino 7 Source: https://developer.rhino3d.com/en/guides/cpp/migrate-your-plugin-windows/ This guide walks you through migrating your Rhino 6 plug-in project to Rhino 7. ## Prerequisites It is presumed you already have the necessary tools installed and are ready to go. If you are not there yet, see [Installing Tools (Windows)](/guides/cpp/installing-tools-windows) ## Migrate the project To migrate your Rhino 6 C++ plugin project to Rhino 7: 1. Launch *Visual Studio 2019* and click *File* > *Open* > *Project/Solution...*. 2. Navigate to your project's folder and open either your plugin project *(.vcxproj)* or solution *(.sln)* 3. When your plugin project opens, Visual Studio will display the *Retarget Projects* dialog box. Specify the following actions and then click *OK*. ![*Retarget Projects*](https://developer.rhino3d.com/images/migrate-plugin-windows-cpp-02.png) ## Replace property sheets The Rhino C/C++ SDK includes Visual Studio Property Sheets that provide a convenient way to synchronize or share common settings among other plugin projects. You will need to remove references to Rhino 6 C/C++ SDK property sheets and replace them with references to Rhino 7 C/C++ SDK property sheets. 1. From *Visual Studio 2019*, click *View* > *Property Manager*. ![Property Manager](https://developer.rhino3d.com/images/migrate-plugin-windows-cpp-01.png) 2. Right-click on the *Rhino.Cpp.PlugIn* property sheets in both *Debug | x64* and *Release | x64* configurations and click *Remove*. 3. Right-click on the *Debug | x64* configuration and click *Add Existing Property Sheet*. 4. Navigate to the following location: *C:\Program Files\Rhino 7.0 SDK\PropertySheets* 5. Select *Rhino.Cpp.PlugIn.props* and click *OK*. 6. Repeat the above steps for the the *Release | x64* configuration. 7. Save the changes to your solution. Your plugin project should now be ready to build with the Rhino 7 C/C++ SDK. ## Related Topics - [What's New?](/guides/cpp/whats-new) -------------------------------------------------------------------------------- # Modify Plug-In licensing code to support Cloud Zoo Source: https://developer.rhino3d.com/en/guides/rhinocommon/cloudzoo/cloudzoo-modify-plugin-licensing-code/ This guide discusses all the steps needed to have a Plug-In support Cloud Zoo. ## Important Information Supporting Cloud Zoo within your plugin's code is easy, but it is important to understand that Cloud Zoo licensing behaves differently from other licensing methods in Rhino. With Cloud Zoo, your plugin (and Rhino itself) will _not_ receive a license key entered by the user or a local Zoo server, but rather a cryptographically verified token called a _License Lease_. The License Lease is authored by Cloud Zoo and handed to Rhino when a license for your plugin is first requested, and at regular intervals thereafter. A License Lease expires every few weeks, so Rhino will automatically negotiate a new lease with Cloud Zoo and hand it to your plugin so that the lease is always a few weeks from expiring. In addition, note that expiration is not the only way a license lease can voided. Rhino can inform your plugin that a lease has been voided for a variety of reasons (For example, the license was removed from Cloud Zoo by the user or the user starting Rhino on a different computer). ## Required Steps To support Cloud Zoo in your plugin, the following must be done in your plugin's code: 1. Implement `OnLeaseChangedDelegate` method. 2. Call `GetLicense` within your plugin. 3. [Digitally sign your plugin](/guides/rhinocommon/digitally-signing-plugins-for-zoo). ## Implement OnLeaseChangedDelegate This method is called by Rhino after a license lease changes. This can happen in the following situations: - A license lease is successfully negotiated after you call `GetLicense`. - A new license lease is negotiated automatically by Rhino to keep it from expiring. - The current license lease expires (The lease received is null). - The current license lease is voided by Cloud Zoo (The lease received is null). When `LicenseLeaseChangedEventArgs.Lease` is null, you should assume the user does not have a valid license and disable functionality accordingly. ### Example: ```c# /// /// Called by Rhino to signal that a lease from Cloud Zoo has changed. /// If LicenseLeaseChangedEventArgs.Lease is null, then the server has signaled /// that this product is no longer licensed. Your plug-in must change behavior /// to behave appropriately. /// /// Data passed by Rhino when the lease changes /// Icon to be displayed in Tools > Options > Licenses for this lease. private static void OnLeaseChanged(LicenseLeaseChangedEventArgs args, out System.Drawing.Icon icon) { icon = ProductIcon; // This sample does not support Rhino accounts. if (null == args.Lease) { // Lease has been voided; this product should behave as if it has no // license. It is up to the plug-in to determine what that looks like. } // Verify that args.Lease.ProductId is correct // Verify that args.Lease.ProductEdition is correct // Verify that args.Lease.ProductVersion is correct // Verify thatargs.Lease.IsExpired() is false } ``` [See an example in GitHub](https://github.com/mcneel/rhino-developer-samples/blob/e24bb79e8e3954952826111185db4ce7f96ecd65/rhinocommon/cs/SampleCsWithLicense/SampleCsWithLicensePlugIn.cs#L166) ## Call GetLicense Like other licensing methods, you should call `GetLicense` when your plugin is loaded. The only difference is that you must pass the `OnLeaseChanged` delegate you implemented in the previous step and add `SupportsRhinoAccounts` to the `LicenseCapabilities` argument so that Rhino can know that you support Cloud Zoo and so that Rhino can know which delegate to call when a license lease change occurs. If Rhino shows the "The resource cannot be found." error message when trying to get a lease for your plug-in from Cloud Zoo, make sure you've registered your product with Cloud Zoo and that the product's ID matches the plug-in's GUID. When calling `GetLicense`, the `validatedProductKeyDelegate` argument cannot be null. If you only support Cloud Zoo licensing (`LicenseCapabilities.SupportsRhinoAccounts`), then use a dummy method, like below.

private static ValidateResult OnValidateProductKey(string licenseKey, out LicenseData licenseData)
{
  // will not be called but must nonetheless be a valid delegate method
  licenseData = null;
  return ValidateResult.ErrorShowMessage;
}



--------------------------------------------------------------------------------

# Node in Code from Python.
Source: https://developer.rhino3d.com/en/guides/rhinopython/ghpython-call-components/

It is possible to call a Grasshopper component from inside a Python script.


**Node-in-Code™**: almost every Grasshopper component is now callable as a function in other places in Rhino.  Grasshopper components may be called in GhPython (on the Gh canvas) and from rhino.Python (off the canvas). This adds few thousand new functions accessible to GhPython. Functions are also available through 3rd party components.  Along with the new functionality that this provides, the technique can be used to simplify existing gh definition files by simply lumping together a bunch of related components into a single 'scott_davidson' script.

There is a module in `ghpythonlib.components` which attempts to make every component available in Python in the form of an easy to call function.

## Components As functions from GhPython

In this example we can make a new ghPhython component that combines the standard Grasshopper Voronoi component and the Grasshopper Area components.  Within one GhPython component the voronoi curves are created and the cell centroid points are output.

Use a GhPython component with these inputs:

Node in Code sample file

Before entering code in the GhPython component it to review the input properties to make sure the correct Type hint is used.  In this case the points input has a Point3D hint and a List Access level set.  Do this by right clicking on the GhPython component input.

Enter this code into the GhPython component:

```python
import ghpythonlib.components as ghcomp
curves = ghcomp.Voronoi(points) # call the Grasshopper Voronoi component
centroids = ghcomp.Area(curves).centroid # call Grasshopper Area component with curves from Voronoi
```

Of course you can mix in other Python to perform calculations on the results of the component function calls. I tweaked the above example to find the curve generated from Voronoi that has the largest area.

```python
import ghpythonlib.components as ghcomp

curves = ghcomp.Voronoi(points)
areas = ghcomp.Area(curves).area
#find the biggest curve in the set
max_area = -1
max_curve = None
for i, curve in enumerate(curves):
    if areas[i] > max_area:
        max_area = areas[i]
        max_curve = curve
```

Remember, this can be done for almost every component in Grasshopper (including every installed add-on component.) I use the term almost because there are cases where the function call doesn’t make sense. These cases are for things like Kangaroo or timers where the state of the component is important between iterations. Fortunately this is pretty rare.

### Configuring inputs:

There are a few techniques that can make working with `ghpythonlib.components` easier.
It is quite helpful and simplifies the code greatly if the proper [Type Hint](/guides/rhinopython/ghpython-component/#advanced-input-properties) is set on the Input of the Python component. Setting the Type Hint to a specific data type will give the script direct access to the objects.   Without the Type Hint a lot more data checking and conversion may be necessary. For instance in a case where `points` are expected as input, set Type Hint > Point3D.  This assures the GhPython components passes in actual point objects to Python. This is especially important for any inputs expecting geometry objects.

Inputs also should be set properly for [Object Access, List Access, or Tree Access](/guides/rhinopython/ghpython-component/#advanced-input-properties).  Setting the Object vs List level access is important for make sure the GhPython code gets objects one at a time, or as a whole list of objects all at once.

Working with multiple inputs and outputs from component methods is like working with any other Python functions.  Inputs can be passed to the function through a list of arguments.  For instance the Divide Curve component has multiple inputs (Curve, Number, Kinks)

To pass these arguments, use a sequential list:

```python
results = ghcomp.DivideCurve(C, N, K)
```

Inputs may also are still optional and can be left off the end. Arguments left out will use their built in defaults:

```python
results  = ghcomp.DivideCurve(C, N)
```

Inputs may also be passed as Keyword arguments `(**kwargs)`.  In this way options do not need to be assigned sequentially and certain optional inputs can be skipped:

```python
results  = ghcomp.DivideCurve(Curves = C, Kinks = K)
```

In the case above the missing input Number of divisions the default value of (10) is used.  Be careful on relying on default values.  It is possible default values can change in the future.

Help for details of specific Keyword Arguments names can be found in the help tab at the bottom the the GhPython editor while editing the arguments:

At present, there is no way to switch components that have special settings to any alternative. For example the Bounding Box component has a special setting, Union Box:

### Managing Outputs

Components with multiple outputs will pass back a list or results corresponding to the sequential order of outputs for the component. Outputs come out of  ghpythonlib.components as sequential lists of lists.  In the case of (Points, Tangent Vectors, Parameters(t))

```Python
results  = ghcomp.DivideCurve(C, N, K)
results[0] # List of points from Divide
results[1] # List of Tangent Vectors
Results[2] # List of Pameters on the curve
```

A nice Python trick can be used to separate the output into separate lists for each output return values:

```python
P, T, t  = ghcomp.DivideCurve(C, N, K)
```
Now each variable contains an already separate list of values that can be used in subsequent functions in the script.

## Node in code from rhino.Python

In addition to [calling Grasshopper components as functions from the GhPython code](#components-as-functions-from-ghpython), the Grasshopper components are also outside of the Grasshopper canvas through rhino.python.

While the Grasshopper canvas will not be visible during this operation, it is worth noting that Grasshopper will load up the first time you call into the `ghpythonlib` assembly.  This can take a couple seconds the first time you run the script in Rhino.

As an example her is a script that calls into Grasshopper to use the Voronoi component to create voronoi cells around a few selected points.

```python
import rhinoscriptsyntax as rs
import ghpythonlib.components as ghcomp
import scriptcontext

points = rs.GetPoints(True, True)
if points:
    curves = ghcomp.Voronoi(points)
    for curve in curves:
        scriptcontext.doc.Objects.AddCurve(curve)
    for point in points:
        scriptcontext.doc.Objects.AddPoint(point)
    scriptcontext.doc.Views.Redraw()
```
The key is the ghpythonlib modules are available in the standard Python editor in Rhino. Behind the scenes things are running through Grasshopper code, but you don’t have to use a canvas to do your work.

This also lets you work in a slightly different way where you can get points in Rhino using rhinoscriptsyntax “get input” type functions and pass those points (or curves or breps) into the Grasshopper component code.

This opens up many more methods which are available to rhino.Python. Remember, this can be done for almost every component in Grasshopper (including every installed add-on component.) I use the term almost because there are cases where the function call doesn’t make sense. These cases are for things like Kangaroo or timers where the state of the component is important between iterations. Fortunately this is pretty rare.

Help for specific component arguments and return values can be found in the help tab at the bottom the the rhino.Python editor while editing the arguments.

## Related Topics

- [Your first script with Python in Grasshopper](/guides/rhinopython/what-is-rhinopython)
- [What is Python and RhinoScript?](/guides/rhinopython/what-is-rhinopython)
- [Editing Python in Grasshopper](/guides/rhinopython/python-running-scripts)
- [Python Guide for Rhino](/guides/rhinopython/)


--------------------------------------------------------------------------------

# Planes in Python
Source: https://developer.rhino3d.com/en/guides/rhinopython/python-rhinoscriptsyntax-plane/

This guide provides an overview of RhinoScriptSyntax Plane Geometry in Python.


## Planes

Planes are represented by a [Plane](/api/RhinoCommon/html/T_Rhino_Geometry_Plane.htm) structure.  Planes  can be thought of as a zero-based, one-dimensional list containing four elements: the plane's origin ([point3D](/guides/rhinopython/python-rhinoscriptsyntax-points)), the plane's X axis direction ([vector3d](/guides/rhinopython/python-rhinoscriptsyntax-vectors)), the plane's Y axis direction ([vector3d](/guides/rhinopython/python-rhinoscriptsyntax-vectors)), and the plane's Z axis direction ([vector3d](/guides/rhinopython/python-rhinoscriptsyntax-vectors)).

```
plane contains (pointOrigin, vectorX, vectorY, vectorZ)
```

It is easy to forget that there is a specific geometric relationship between the axes.  With Planes, the Y axis is automatically oriented 90-degrees to the X axis.  The X axis is the only axis that can be easily defined.  The Y axis is made orthogonal to the X vector, and the direction of the Z axis is computed from the cross-product of the other two vectors.

Planes can be constructed in a number of ways. One common function is `PlaneFromPoints`:

```python
import rhinoscriptsyntax as rs

plane = rs.PlaneFromPoints((-2,-5,0),(1,2,0),(-3,3,0))

print(plane.Origin) # origin point
print(plane.XAxis) # x-axis vector
print(plane.YAxis) # y-axis vector
```

Plane also have a number of properties that can be used to get or set the individual values in the Point object.  In the example below the `.Origin`, `.XAxis`, `.Yaxis`, `.Zaxis` are used.

Planes can also be created using the `CreatePlane()`, [PlaneFromFrame](/api/RhinoScriptSyntax/win/#collapse-PlaneFromFrame),  [PlaneFromNormal](/api/RhinoScriptSyntax/win/#collapse-PlaneFromNormal), and [PlaneFromPoints](/api/RhinoScriptSyntax/win/#collapse-PlaneFromPoints) functions.

To change origin of a Plane, simply assign a new value to the `.Origin` property.

```python
import rhinoscriptsyntax as rs

plane = rs.PlaneFromPoints((-2,-5,0),(1,2,0),(-3,3,0))

print(plane.Origin) # origin point
print(plane.XAxis) # x-axis vector
print(plane.YAxis) # y-axis vector

plane.Origin = rs.CreatePoint(3,4,5) # Changes the origin of the plane.

print(plane.Origin)
print(plane.XAxis) # x-axis vector
print(plane.YAxis) # y-axis vector
```

RhinoScriptSyntax contains a number of functions to manipulate planes.  See [Lines and Planes](/guides/rhinopython/python-rhinoscriptsyntax-line-plane-methods) for details.

Also, please see the Python primer [Section 8.5 Planes](/guides/rhinopython/primer-101/8-geometry/#85-planes).

## Related Topics

- [What is Python and RhinoScript?](/guides/rhinopython/what-is-rhinopython)
- [Python Points](/guides/rhinopython/python-rhinoscriptsyntax-points)
- [Python Vectors](/guides/rhinopython/python-rhinoscriptsyntax-vectors)
- [Python Lines](/guides/rhinopython/python-rhinoscriptsyntax-lines)
- [Python Planes](/guides/rhinopython/python-rhinoscriptsyntax-planes)
- [Python Objects](/guides/rhinopython/python-rhinoscriptsyntax-objects)


--------------------------------------------------------------------------------

# Python Conditionals
Source: https://developer.rhino3d.com/en/guides/rhinopython/python-conditionals/

This guide is an survey of Python conditional statements.



## Overview

You can control the flow of your script with conditional statements and looping statements.  Using conditional statements, you can write Python code that makes decisions and repeats actions.  The following conditional statements are available in Python:

* `if` statement
* `if`...`else` statement
* `if`...`elif`...`elif`...`else` nested statement

The `if`... statement is used to evaluate whether a condition is True or False and, depending on the result, to specify one or more statements to run.  Usually the condition is an expression that uses a comparison operator to compare one value or variable with another.  For information about comparison operators, see the [Python Operators](/guides/rhinopython/python-operators) guide. `if`... and `if...elif` statements can be nested to as many levels as you need.

Python programming language assumes any non-zero and non-null values as TRUE, and if it is either zero or null, then it is assumed as FALSE value.

### if

To run only one statement when a condition is True, use the single-line syntax for the `if`... statement.  The following example shows the single-line syntax.

```python
var1 = 350
if var1 == 350 : print ("The value of the variable is 350")
```

To run more than one line of code, you must use the multiple-line (or block) syntax. The first line ends in a colon (:). As with all Python block syntax, the whitespaces to the left of the lines must be the same throughout the block. As an example:

```python
var1 = 350
if var1 == 350 :
    print ("The value of the variable is 350")
    var2 = 450
    print ("The value of variable 2 is 450")
```

### if..else

You can use an `if`...`else` statement to define two blocks of executable statements: one block to run if the condition is True, the other block to run if the condition is False...

```python
var1 = 350
if var1 == 0 :
    MyLayerColor = 'vbRed'
    MyObjectColor = 'vbBlue'
else :
    MyLayerColor = 'vbGreen'
    MyObjectColor = 'vbBlack'
print (MyLayerColor)
```

### if..elif..elif..else

A variation on the `if`...`else` statement allows you to choose from several alternatives.  Adding `elif` clauses expands the functionality of the `if`...`else` statement so you can control program flow based on multiple different possibilities. For example:

```python
var1 = 0
if var1 == 0 :
    print ("This is the first " + str(var1))
elif var1 == 1 :
    print ("This is the second " + str(var1))
elif var1 == 2 :
    print ("This is the third " + str(var1))
else :
    print ("Value out of range!")
```

You can add as many `elif` clauses as you need to provide alternative choices. Thie `elif` statement takes the place of the `Select Case` statement in other languages.

For more information on nesting if..elif..else statements see [Python nested if statements on TutorialsPoint](https://www.tutorialspoint.com/python/nested_if_statements_in_python.htm)

## Related Topics

- [What are Python and RhinoScriptSyntax?](/guides/rhinopython/what-is-rhinopython)
- [Python Operators](/guides/rhinopython/python-operators)


--------------------------------------------------------------------------------

# RDK Render Content Editors
Source: https://developer.rhino3d.com/en/guides/cpp/rdk-render-content-editors/

This guide describes the RDK Material, Environment, and Texture Editors

The _RDK Render Content Editors_ display objects called _Render Contents_ and allow the user to edit them. These editors are all based on a similar interface with only small functional differences between them. Render Contents are the foundation of the RDK core and one of the most important objects it provides. Please see [Render Content](/guides/cpp/rdk-render-content) for more information. The Texture Editor is known to users as the Texture _Palette_ but programmatically it is an editor just like the other two.

1. Navigation controls similar to those found on a web browser.
2. Resizeable Floating Previews.
3. Configurable thumbnails with multiple sizes and styles.
4. Resizeable preview pane.
5. User interface for editing render content parameters (AKA _fields_).
6. Breadcrumb navigation control similar to those found on file explorers.
7. [Task](/guides/cpp/rdk-task-classes/) menu for performing actions on render contents and setting editor options.

These editors are integrated with Rhino's tabbed pane system. You can access them through the Rhino Render menu, the Rendering tool bar, or the editor commands.

Lists of materials, environments, and textures are stored in the Rhino document. Each editor displays the relevant render content type as preview thumbnails.

Contents display an interface below the preview thumbnails in an area reserved for collapsible UI panels. An addition to the basic UI panels, several additional collapsible panels are provided by Rhino within the same area as the content UI. These include the Name, Notes, Texture Summary, Local Mapping, Graph and Adjustment panels.

Each material, environment or texture can have child nodes (AKA _child slots_ or _sub-nodes_). The children can be of any content type, but specific child slots will only support specific types. The most common child type is a texture. For example, the _Color_ child slot for a Custom Material will only support textures, as will the _Background image_ child slot on a Basic Environment.


--------------------------------------------------------------------------------

# Simple Parameters
Source: https://developer.rhino3d.com/en/guides/grasshopper/simple-parameters/

This guide covers parameters; what they are, what they're for, what they can and cannot do.


 
## Overview

Parameters are a vital part of Grasshopper (the other being components).  However, unlike components, it is far less likely that you'll need to make your own parameters.  Most components require only the native data types available inside Grasshopper.  In those odd cases where you need to work with custom data, you'll also need to create custom parameters that store that data.  In this topic, we'll create a parameter which can handle the TriStateType we discussed in the [Simple Data Types](/guides/grasshopper/simple-data-types) guide.

Parameters are responsible for storing and distributing data.  Components use them to collect existing data and output new data.  Parameters can also be used to convert data from one type into another, even though on the atomic level the conversion is actually performed by the [CastTo(T)](/api/grasshopper/html/M_Grasshopper_Kernel_Types_IGH_Goo_CastTo__1.htm) and [CastFrom](/api/grasshopper/html/M_Grasshopper_Kernel_Types_IGH_Goo_CastFrom.htm) methods on [IGH_Goo](/api/grasshopper/html/T_Grasshopper_Kernel_Types_IGH_Goo.htm).  Most objects in the Special subcategory on the Grasshopper toolbar are basically parameters with extended GUIs.  Parameters can also be used by themselves to store constant data or to redirect data into multiple streams.

## Prerequisites

We will not be dealing with any of the basics of component development in this guide.  Please start with the [Your First Component](/guides/grasshopper/your-first-component-windows) guide and the [Simple Component](/guides/grasshopper/simple-component) guide before starting this one.

Before you start, create a new class that derives from `Grasshopper.Kernel.GH_Component`, as outlined in the [Simple Component](/guides/grasshopper/simple-component) guide.

## Grasshopper.Kernel.IGH_Param

All parameters in Grasshopper must implement the [IGH_Param](/api/grasshopper/html/T_Grasshopper_Kernel_IGH_Param.htm) interface.  `IGH_Param` defines the bare minimum of what a parameter must be able to do.  There are some interfaces that extend on `IGH_Param`, and also some abstract classes that partially implement the interface.  Note that `IGH_Param` already inherits from `IGH_ActiveObject`, so it comes with a lot of baggage.

`IGH_Param` is quite an extensive interface, it defines nearly thirty properties and methods, some of which are quite tricky to implement.  It is highly recommended that you do not directly implement IGH_Param, but derive from the abstract [GH_Param(T)](/api/grasshopper/html/T_Grasshopper_Kernel_GH_Param_1.htm) class instead.  `GH_Param(Of T)` provides a basic implementation of `IGH_Param` and takes care of quite a lot of the nasty bits.

Here's a list of things `GH_Param` will happily do for you that you would otherwise have to do yourself:

- It will create default Attributes in case the parameter is floating.
- It will manage and synch the source and recipient lists.
- It can parse network topology to figure out dependency relationships.
- It will create, restore and clean proxy sources used during IO operations.
- It knows how to disassociate itself from adjacent objects in a network.
- It will provide getters and settings for typical parameter properties.
- It knows how to apply DataMapping algorithms to local data.
- It can handsomely format local data for display in tooltips.
- It will maintain a `GH_Structure(Of T)` for local data storage.
- It provides methods to cast data into the local type.
- It will accurately measure the time it takes to collect and process data.
- It knows how to collect and interweave data from multiple sources.
- It will correctly populate parameter pop-up menus.
- It will provide the correct StateTags for DataMapping settings.
- It will (de)serialize all local properties.

In other words, *do not implement* `IGH_Param` *but derive from* `GH_Param`*!*

## Types of Parameters

As mentioned before, parameters can be encountered as component inputs or outputs, but also as free-floating objects.  These are not different classes, but rather the same class behaving in different ways.  Every instance of `IGH_Param` has a [Kind](/api/grasshopper/html/P_Grasshopper_Kernel_IGH_Param_Kind.htm) readonly property which describes the context of that instance:

![Types of Parameters](https://developer.rhino3d.com/images/simple-parameters-01.png)

In the image above, we see all three valid parameter Kinds (`Input`, `Output` and `Floating`).  The "bool" object is a floating parameter of type `Param_Boolean`.  The "P" on the left side of the "Cull" component is an input parameter of that very same type.

The Text Panel is also a floating parameter which derives from `GH_Param(Of GH_String)`.  In a way it is very similar to the String parameter (not shown), except it overrides the display and GUI of the default `GH_Param` class.  The "L" on the right of the "Cull" component is an output parameter of the `Param_GenericObject` type (just like the "L" input parameter).

From this image we can see how versatile parameters can be.  Parameters can have wildly different front-ends, they can expose and hide input and output grips at will, they can be part of a larger component or stand by themselves.  They can even override so much of the default functionality that they no longer appear to be parameters at all (like the cluster output attached to "L").

## Data Inside Parameters

All parameters have the capacity to store data, this is, after all, their primary function.  When you derive from [GH_Param(T)](/api/grasshopper/html/T_Grasshopper_Kernel_GH_Param_1.htm) there will be a protected member available within the class called `m_data` of type [GH_Structure(T)](/api/grasshopper/html/T_Grasshopper_Kernel_Data_GH_Structure_1.htm).  Since `GH_Structure` only accepts types of `T` that implement `IGH_Goo`, `GH_Param` also only accepts types for `T` that implement `IGH_Goo`.  This is why you cannot have a `GH_Param` but must instead have `GH_Param`.

Data stored inside the `m_data` field is destroyed whenever the parameter expires.  Parameters can be expired via several different mechanisms, but the most common ones are:

- One or more of the sources of a parameter expires.
- The component that owns the parameter expires.
- The user forces a complete solution recalculation (F5).

Because the data gets destroyed so readily, we call it *Volatile Data*.  A lot of parameters in Grasshopper are also capable of storing *Persistent Data*, which is not destroyed whenever a solution runs.  This allows parameters to define a collection of "constant" values.  The "Set One XXXX", "Set Multiple XXXX" and "Manage XXXX Collection" menu items in the parameter pop-up menu refer to persistent data.

When a parameter is not connected to any source parameters, the persistent data will be copied into the volatile data.  If there is no persistent data, the parameter will remain empty and a runtime warning will be included to this effect.

`GH_Param` however does not support persistent data.  You can add your own mechanism for this (like Text Panel does) or you can choose to derive from more advanced classes like `GH_PersistentParam(T)`.  In this example, we'll derive from [GH_PersistentParam](/api/grasshopper/html/T_Grasshopper_Kernel_GH_PersistentParam_1.htm) as we want to give users the ability to specify local data.  `GH_PersistentParam` requires we implement two additional methods that allow users to specify persistent data.  These methods are called from the default popup menu when the "Set One XXXX" and "Set Multiple XXXX" items are clicked.

So let's start with deriving from `GH_PersistentParam` and see where that takes us:

  
  

```cs
public class TriStateParameter : GH_PersistentParam
{

  // We need to supply a constructor without arguments that calls the base class constructor.
  public TriStateParameter() :
    base("TriState", "Tri", "Represents a collection of TriState values", "Params", "Primitive") { }

  public override System.Guid ComponentGuid
  {
    // Always generate a new Guid, but never change it once
    // you've released this parameter to the public.
    get { return new Guid("{2FEEF1A2-A764-431d-8C78-9BF92C78BAE1}"); }
  }

  protected override Kernel.GH_GetterResult Prompt_Singular(ref TriStateType value)
  {
    // Todo, impement this function.
  }
  protected override Kernel.GH_GetterResult Prompt_Plural(ref List values)
    // Todo, impement this function.
  }
}

```

```vbnet
Public Class TriStateParameter
  Inherits GH_PersistentParam(Of TriStateType)

  'We need to supply a constructor without arguments that calls the base class constructor.
  Public Sub New()
    'This is where we specify the name, description, tab and panel of this parameter.
    MyBase.New("TriState", "Tri", "Represents a collection of TriState values", "Params", "Primitive")
  End Sub

  Public Overrides ReadOnly Property ComponentGuid() As System.Guid
    Get
      'Always generate a new Guid, but never change it once you've released this parameter to the public.
      Return New Guid("{2FEEF1A2-A764-431d-8C78-9BF92C78BAE1}")
    End Get
  End Property

  Protected Overrides Function Prompt_Singular(ByRef value As TriStateType) As Kernel.GH_GetterResult
    'Todo, impement this function.
  End Function
  Protected Overrides Function Prompt_Plural(ByRef values As System.Collections.Generic.List(Of TriStateType)) As Kernel.GH_GetterResult
    'Todo, impement this function.
  End Function
End Class
```

That is essentially all there's to it.  Though you should also provide an [Icon](/api/grasshopper/html/P_Grasshopper_Kernel_GH_DocumentObject_Icon.htm) and an [Exposure](/api/grasshopper/html/P_Grasshopper_Kernel_IGH_DocumentObject_Exposure.htm) for this object.  The `Prompt_Singular` and `Prompt_Plural` methods need to be implemented.  In this case we'll use standard Rhino Command Line interface to prompt for persistent values:

  
  

```cs
protected override Kernel.GH_GetterResult Prompt_Singular(ref TriStateType value)
{
  Rhino.Input.Custom.GetOption() go = New Rhino.Input.Custom.GetOption();
  go.SetCommandPrompt("TriState value");
  go.AcceptNothing(true);
  go.AddOption("True");
  go.AddOption("False");
  go.AddOption("Unknown");

  switch (go.Get())
  {
    case Rhino.Input.GetResult.Option:
      if (go.Option().EnglishName == "True") { value = new TriStateType(1); }
      if (go.Option().EnglishName == "False") { value = new TriStateType(0); }
      if (go.Option().EnglishName == "Unknown") { value = new TriStateType(-1); }
      return GH_GetterResult.success;

    case Rhino.Input.GetResult.Nothing:
      return GH_GetterResult.accept;

    default:
      return GH_GetterResult.cancel;
  }
}

protected overrides Kernel.GH_GetterResult Prompt_Plural(ref List values)
{
  values = new List

  while (true)
  {
    TriStateType val = null;
    switch (Prompt_Singular(val))
      case GH_GetterResult.success:
        values.Add(val);
        break;

      case GH_GetterResult.accept:
        return GH_GetterResult.success;

      case GH_GetterResult.cancel:
        values = null;
        return GH_GetterResult.cancel;
    }
  }
}

```

```vbnet
Protected Overrides Function Prompt_Singular(ByRef value As TriStateType) As Kernel.GH_GetterResult
  Dim go As New Rhino.Input.Custom.GetOption()
  go.SetCommandPrompt("TriState value")
  go.AcceptNothing(True)
  go.AddOption("True")
  go.AddOption("False")
  go.AddOption("Unknown")

  Select Case go.Get()
    Case Rhino.Input.GetResult.Option
      If (go.Option().EnglishName = "True") Then value = New TriStateType(1)
      If (go.Option().EnglishName = "False") Then value = New TriStateType(0)
      If (go.Option().EnglishName = "Unknown") Then value = New TriStateType(-1)
      Return GH_GetterResult.success

    Case Rhino.Input.GetResult.Nothing
      Return GH_GetterResult.accept

    Case Else
      Return GH_GetterResult.cancel
  End Select
End Function

Protected Overrides Function Prompt_Plural(ByRef values As List(Of TriStateType)) As Kernel.GH_GetterResult
  values = New List(Of TriStateType)

  Do
    Dim val As TriStateType = Nothing
    Select Case Prompt_Singular(val)
      Case GH_GetterResult.success
        values.Add(val)

      Case GH_GetterResult.accept
        Return GH_GetterResult.success

      Case GH_GetterResult.cancel
        values = Nothing
        Return GH_GetterResult.cancel
    End Select
  Loop
End Function
```

## Related Topics

- [Simple Component](/guides/grasshopper/simple-component)
- [Simple Data Types](/guides/grasshopper/simple-data-types)


--------------------------------------------------------------------------------

# VBScript Conditionals
Source: https://developer.rhino3d.com/en/guides/rhinoscript/vbscript-conditionals/

This guide is an survey of VBScript conditional statements.


 
## Overview

You can control the flow of your script with conditional statements and looping statements.  Using conditional statements, you can write VBScript code that makes decisions and repeats actions.  The following conditional statements are available in VBScript:

- `If`...`Then`...`Else` statement
- `Select Case` statement

## If Then Else

The `If`...`Then`...`Else` statement is used to evaluate whether a condition is True or False and, depending on the result, to specify one or more statements to run.  Usually the condition is an expression that uses a comparison operator to compare one value or variable with another.  For information about comparison operators, see the [VBScript Operators](/guides/rhinoscript/vbscript-operators) guide.  `If`...`Then`...`Else` statements can be nested to as many levels as you need.

### If True

To run only one statement when a condition is True, use the single-line syntax for the `If`...`Then`...`Else` statement.  The following example shows the single-line syntax.  Notice that this example omits the `Else` keyword...

```vbnet
Sub FixDate()
  Dim myDate
  myDate = #11/17/2008#
  If myDate < Now Then myDate = Now
End Sub
```

To run more than one line of code, you must use the multiple-line (or block) syntax.  This syntax includes the `End If` statement, as shown in the following example:

```vbnet
Sub AlertUser(value)
  If value = 0 Then
    MyLayerColor = vbRed
    MyObjectColor = vbBlue
  End If
End Sub
```

### Some True Some False

You can use an `If`...`Then`...`Else` statement to define two blocks of executable statements: one block to run if the condition is True, the other block to run if the condition is False...

```vbnet
Sub AlertUser(value)
  If value = 0 Then
    MyLayerColor = vbRed
    MyObjectColor = vbBlue
  Else
    MyLayerColor = vbGreen
    MyObjectColor = vbBlack
  End If
End Sub
```

### Several Alternatives

A variation on the `If`...`Then`...`Else` statement allows you to choose from several alternatives.  Adding `ElseIf` clauses expands the functionality of the `If`...`Then`...`Else` statement so you can control program flow based on different possibilities.  For example:

```vbnet
Sub ReportValue(value)
  If value = 0 Then
    MsgBox value
  ElseIf value = 1 Then
    MsgBox value
  ElseIf value = 2 then
    Msgbox value
  Else
    Msgbox "Value out of range!"
  End If
End Sub
```

You can add as many `ElseIf` clauses as you need to provide alternative choices.  Extensive use of the `ElseIf` clauses often becomes cumbersome.  A better way to choose between several alternatives is the `Select Case` statement.

## Select Case

The `Select Case` structure provides an alternative to `If`...`Then`...`Else` for selectively executing one block of statements from among multiple blocks of statements.  A `Select Case` statement provides capability similar to the `If`...`Then`...`Else` statement, but it makes code more efficient and readable.

A `Select Case` structure works with a single test expression that is evaluated once, at the top of the structure. The result of the expression is then compared with the values for each `Case` in the structure.  If there is a match, the block of statements associated with that `Case` is executed, as in the following example...

```vbnet
Select Case MyVar
  Case "red"     MyColor = vbRed
  Case "green"   MyColor = vbGreen
  Case "blue"    MyColor = vbBlue
  Case Else      MsgBox "Pick another color."
End Select
```

Notice that the `Select Case` structure evaluates an expression once at the top of the structure.  In contrast, the `If`...`Then`...`Else` structure can evaluate a different expression for each `ElseIf` statement.  You can replace an `If`...`Then`...`Else` structure with a `Select Case` structure only if each `ElseIf` statement evaluates the same expression.

## Related Topics

- [What are VBScript and RhinoScript?](/guides/rhinoscript/what-are-vbscript-rhinoscript)
- [VBScript Operators](/guides/rhinoscript/vbscript-operators)


--------------------------------------------------------------------------------

# Wrapping Native Libraries
Source: https://developer.rhino3d.com/en/guides/rhinocommon/wrapping-native-libraries/

This guide demonstrates how to wrap a C/C++ library in order to call into it from .NET.



## Overview

We present a sample solution that uses Platform Invoke (PInvoke), which allows .NET code to call functions that are implemented in a C/C++ DLL (Windows) or dylib (macOS).  It is important to understand that the main utility of this advanced approach is in the ability to call the same C/C++ code on both platforms from .NET.  

First, we will build a simple C/C++ library that adds two numbers together.  After that, we will examine the wrapping .NET code required to call into our library from a RhinoCommon plugin on both Windows and Mac.

### Prerequisites

This guide does not presume you are a C/C++ or .NET expert, but assumes you have a functional working knowledge of both.  This is an advanced guide; that said, the intent of this guide is to illustrate basic considerations of wrapping a C/C++ library and the logistical issues calling it from a RhinoCommon plugin on both Windows and Mac.

We will be analyzing a sample solution called *[SampleNativeLibrary](https://github.com/dalefugier/SampleNativeLibrary)*.  Please clone or download this repository.  *SampleNativeLibrary* builds against the RhinoWIP (on Windows) and Rhino 5 for Mac (on macOS).  (On Windows, it is possible to use Rhino 6, but you will have to change the RhinoCommon references).

It is presumed you already have *all* the necessary tools installed and are ready to go.  If you are not there yet, see both [Installing Tools (Windows)](/guides/rhinocommon/installing-tools-windows) and [Installing Tools (Mac)](/guides/rhinocommon/installing-tools-mac).  It is also helpful to have read and understood [Your First Plugin (Windows)](/guides/rhinocommon/your-first-plugin-windows) and [Your First Plugin (Mac)](/guides/rhinocommon/your-first-plugin-mac).

  

WARNING

Other methods to create a .NET binding to a C# library exist. A notorious one is based on the compilation of the C++ library with the C++/CLI compiler. To keep things compatible with the Apple macOS, and because the IJW (it just works) technology sometimes does not, we suggest the use of PInvoke. ## SampleLibrary Let's begin by taking a look at an absurdly simple C/C++ "library" - SampleLibrary - that does one thing: add two numbers together. We'll start on Windows, but it really doesn't matter if you start on a macOS, nearly everything that follows applies on each platform... ### Windows 1. Open *SampleNativeLibrary.sln* in *Visual Studio*. 1. In the Solution Explorer, you will notice there are three projects...two C# (.csproj) projects and one C++ project. Expand the *SampleLibrary* C++ project... ![SampleNativeLibrary](https://developer.rhino3d.com/images/wrapping-native-libraries-02.png) 1. The *SampleLibrary* (.vcxproj) is just a boilerplate C++ project (created using the regular Shared MFC C++ project wizard) that was created by Visual Studio. *There is nothing fancy going on here at all; much of the code is not even relevant to this guide*. 1. Open the *SampleLibrary.cpp* file and take a look. Nearly all of the code in this file is auto-generated boilerplate. The only important section to pay attention to is: SAMPLELIBRARY_C_FUNCTION double Add(int a, double b) { return a + b; } ...even if you are not a C/C++ programmer, this C function should be clear to you. `Add` takes a native `int` and a native `double`, and returns the sum of the inputs as a native `double`. Take note of the `SAMPLELIBRARY_C_FUNCTION` decoration above the implementation...we will talk about that in a moment. 1. In the *Header Files* filter, find and open the *SampleLibraryInclude.h* header file. The first thing to note is that there are two well `#defined` sections to this header: one that relates to Windows (`#if defined (_WIN32)`) and one that relates to Mac (`#if defined(__APPLE__)`). The code in these #defines are basically telling the linker to export functions in a specific way. The reason they are different on each platform is that Dynamic Link Libraries (DLLs) are implemented differently in Windows and macOS - each platform has a unique way of telling the linker what to do with the library. How this works is not really important at this juncture, just know that these MACROs tell the linker to export the functions. More importantly... 1. Take a look at the function declaration at the bottom of the file: SAMPLELIBRARY_C_FUNCTION double Add(int a, double b); ...is decorated with the same `SAMPLELIBRARY_C_FUNCTION`. 1. This is the decoration that tells the linker to make the function available to outside callers. By decorating with this macro, the linker adds information to the DLL that makes these functions public. Without this decoration, these functions would not be available outside the the assembly (.dll or .dylib). By providing this decoration, we are doing the .NET equivalent of making them "public". 1. Do a "sanity check" and *Build* SampleLibrary to make sure that all your tools are working as expected. SampleLibrary should build without errors as the native *SampleLibrary.dll* in the project */bin* folder. Ok, now that we know roughly what is in the native SampleLibrary on Windows, let's take a look at it on macOS... ### Mac 1. Launch *Xcode* and open *SampleLibrary.xcodeproj*. (Unlike Visual Studio on Windows, on macOS, we cannot do all of our development in a single IDE, but we have to build our native C/C++ *SampleLibrary* using the native Apple Tools - Xcode and xcodebuild). 1. In the *Project Navigator*, notice that this project is referencing the *exact same source code* as its Windows counterpart... ![SampleLibrary on macOS](https://developer.rhino3d.com/images/wrapping-native-libraries-03.png) 1. Instead of building a .dll, on macOS we are building *libSampleLibrary.dylib* which - despite the extension - amounts to exactly the same thing as a dll. For all intents and purposes, SampleLibrary does exactly the same thing on macOS that it does on Windows and the source is the same. 1. *Build* SampleLibrary using Xcode to make sure that it builds successfully. 1. *Quit* Xcode. On macOS, we are actually going to use command line `xcodebuild` from Visual Studio for Mac to build our native library, but we'll talk about that below. Now that we have examined the simple native SampleLibrary, let's turn our attention to the .NET portion of our wrapping code... ## SampleNativeLibrary SampleNativeLibrary is the .NET project that calls into the SampleLibrary. On each platform, we are using the exact same wrapping source code, just using cloned .csproj projects on each platform. Let's take a look at SampleNativeLibrary on Windows using Visual Studio first... ### Windows 1. If you have not done so already, open the *SampleNativeLibrary.sln* in *Visual Studio*. 1. If you have not done so already, build *SampleLibrary*. Verify that *SampleLibrary.dll* is present in your */bin* folder. 1. Now, let's turn our attention to the *SampleRhino.Win* project. Make sure that *SampleRhino.Win* is set as the Startup Project and expand it so you can see the source files... ![SampleRhino on Windows](https://developer.rhino3d.com/images/wrapping-native-libraries-04.png) 1. Before we do anything, let's build and test the plugin. Build *SampleRhino.Win*. 1. Load the *SampleRhino.rhp* in Rhino and run the *SampleRhinoCommand*. You will be prompted to enter two numbers. Once you have done that you should see the result in a dialog box... ![SampleRhino plugin test](https://developer.rhino3d.com/images/wrapping-native-libraries-05.png) 1. The "additional" calculation for this command was all performed in the native C/C++ SampleLibrary.dll. Return to *Visual Studio*. Let's take a look at how the `Add` function is being called. 1. Open *SampleRhinoCommand.cs* and find the `RunCommand` method. After prompting the user to enter two numbers, we see the following code var result = RhinoMath.UnsetValue; try { result = UnsafeNativeMethods.Add(first, second); } catch (Exception ex) { RhinoApp.WriteLine(ex.Message); return Result.Failure; } 1. The `Add(first, second)` method is being called on the `UnsafeNativeMethods` class. Open *UnsafeNativeMethods.cs*. Let's go through this class line-by-line. 1. The necessary functionality to use PInvoke is contained in the `System.Runtime.InteropServices` namespace, specifically the `DllImport` function. 1. *UnsafeNativeMethods.cs* contains the class: internal static class Import { public const string lib = "SampleLibrary.dll"; } ...which declares a `lib` string member. On Windows, it is necessary to explicitly state the name of the native dll being called (on macOS, a *.dll.config* file is used to point to a .dylib - see below). 1. The `UnsafeNativeMethods` class itself contains a single function... internal static extern double Add(int a, double b); ...does this look familiar? 1. The `Add` function is decorated with an [Attribute](https://msdn.microsoft.com/en-us/library/z0w1kczw.aspx): [DllImport(Import.lib, CallingConvention = CallingConvention.Cdecl)] ...this important bit of metadata tells the runtime to look in the native library and call the associated function with a specific language (C) calling convention when the `Add` method is called from .NET. *This is the point at which the link between the managed .NET code and the unmanaged C/C++ code is established.* 1. Since we are bridging the world of managed and unmanaged code, *we need to be very careful about the types of variables we are using*. To illustrate this point, let's change the code and see what happens... 1. In *UnsafeNativeMethods* change the function declaration of `Add` to accept `double`s (instead of `int`s) as the first argument... internal static extern double Add(double a, double b); 1. *Build* the plugin. No errors or warnings, right? Now run the plugin and test the *SampleRhinoCommand* with this change. Try adding 2 + 2. What happens? *2 + 2 no longer equals 4*. That's bad (hopefully, Rhino did not crash). The compiler did not detect the error we introduced. Change the type of argument `a` back to an `int`, rather than a `double` and save the file. 1. *You must ensure that the function in .NET exactly matches the function declaration in the header file made in the unmanaged code* (in this case, in *SampleLibraryInclude.h*). Managing these correspondences is challenging with all but the most trivial libraries. In [Using methodgen](/guides/rhinocommon/using-methodgen), we will discuss a way of generating these function signatures using a utility program we wrote to maintain RhinoCommon. Before we do that, let's turn our attention to... ### Mac 1. If you have not done so already, open the *SampleNativeLibrary.sln* in *Visual Studio for Mac*. 1. The native *SampleLibrary* project cannot be built from Visual Studio for Mac. As mentioned above, Visual Studio for Mac is a .NET only IDE; it cannot build C/C++ projects like Visual Studio. (We will work around this limitation in a moment). 1. First, let's turn our attention to the *SampleRhino.Mac* project. Make sure that *SampleRhino.Mac* is set as the Startup Project and expand it so you can see the source files... ![SampleRhino on Mac](https://developer.rhino3d.com/images/wrapping-native-libraries-06.png) 1. Just as with *SampleLibrary*, *SampleRhino* is exactly the same source on both platform: the platform-specific *.csproj* files are referencing exactly the same *.cs* files. 1. *Build*, run, and test *SampleRhinoCommand* in Rhino for Mac. Everything should work as expected. No surprises here...all the code is the same (if you skipped the [Windows section above](#windows-1), read it now; all of it applies on macOS). 1. There is one critical difference between how the Windows and macOS wrapping works and it has little to do with code. As we saw above, *UnsafeNativeMethods.cs* contains the class: internal static class Import { public const string lib = "SampleLibrary.dll"; } ...which declares a `lib` string member. On Windows, it was necessary to explicitly state the name of the native dll being called. On macOS, this works a little differently. In order to understand what is different, let's look at how *SampleLibrary.dll* gets built on macOS... 1. In the [SampleLibrary (Mac) section above](#mac), we built SampleLibrary using Xcode. We mentioned in passing that we could build this using `xcodebuild` command line from Visual Studio for Mac. Let's take a look. 1. In the *Solution Explorer*, *double-click* the name of the *SampleRhino.Mac* project to bring up the *Project Options* dialog. Navigate to the *Build* > *Custom Commands* section... ![SampleRhino.Mac Project Options](https://developer.rhino3d.com/images/wrapping-native-libraries-07.png) 1. There are 5 *Custom Commands*: 1. *Before Build*: This before build steps runs a python script called *build_native.py* that uses `xcodebuild` to build the *SampleLibrary.xcodeproj*, which builds *libSampleLibrary.dylib*. 1. *After Build 1*: This after build step copies the *SampleLibrary.dll.config* file from the */SampleLibrary* folder to the target folder. *SampleLibrary.dll.config* creates a mapping between calls to *SampleLibrary.dll* and *libSampleLibrary.dylib*... On macOS, both the *.dll.config* file and the *.dylib* must be in the same folder as the calling .NET dll. 1. *After Build 2*: This after build step copies *libSampleLibrary.dylib* to the same folder as the calling .NET dll. 1. *After Clean 1*: This after build step deletes the *libSampleLibrary.dylib* when a clean is performed. 1. *After Clean 1*: This after build step deletes the *SampleLibrary.dll.config* when a clean is performed. 1. When you *Build* and run *SampleRhino.Mac* you can watch the build steps in the *output* (right) side of the *Errors* panel. You may notice that *build_native.py* will only build *libSampleLibrary.dylib* if it does not find a previous version. You can overwrite this with the `-o --overwrite` command argument if you want to build the native library every time you build the .NET wrapping dll. ## Next Steps You have seen how a basic C/C++ library can be wrapped and called from .NET in a Rhino Plugin. You have also seen what can go wrong when a native method's export declaration is out-of-sync with its .NET counterpart. *Now what?* Check out the [Using methodgen](/guides/rhinocommon/using-methodgen) guide for instructions on programmatically generating UnsafeNativeMethods export declarations. ## Related topics - [Microsoft Platform Invoke Tutorial on MSDN](https://msdn.microsoft.com/en-us/library/aa288468(VS.71).aspx) - [Interop with Native Libraries on mono-project.org](http://www.mono-project.com/docs/advanced/pinvoke/) - [Attributes on MSDN](https://msdn.microsoft.com/en-us/library/z0w1kczw.aspx) - [Moose sample on GitHub](https://github.com/dalefugier/Moose) - [Using methodgen](/guides/rhinocommon/using-methodgen) -------------------------------------------------------------------------------- # Adding Commands to Projects Source: https://developer.rhino3d.com/en/guides/rhinocommon/adding-commands-to-projects/ This brief guide demonstrates how to add additional commands to a RhinoCommon plugin project. ## Problem A fresh project ([Windows](/guides/rhinocommon/installing-tools-windows/#rhinocommon-templates) or [Mac](/guides/rhinocommon/installing-tools-mac/#install-the-rhino-add-in)) is only a skeleton plugin project with a single command. However, plugins can contain more than one command. How does one add additional commands to plugin projects? ## Solution To add a new command to your RhinoCommon plugin project, you simply need to define a new class that inherits from `Rhino.Commands.Command`. An easy way to do this is to just use the *Empty RhinoCommmon Command* template. Here is how you do that: ### Windows 1. Make sure you have the [RhinoCommon Project Wizard](/guides/rhinocommon/installing-tools-windows/#rhinocommon-templates) installed. 1. Launch *Visual Studio* and open your plugin project. 1. From *Visual Studio*, navigate to *Project* > *Add New Item* menu item. 1. Select the *Empty RhinoCommmon Command* template from the list of installed templates. 1. Provide a unique file name that relates to the command you are adding. 1. Click the *Add* button. ### Mac 1. Make sure you have [Rhino.Templates](/guides/rhinocommon/installing-tools-mac/#install-the-rhino-add-in) installed. 1. Launch *Visual Studio Code* and open your plugin project folder. 1. Open _Visual Studio Code's Terminal_ via _Terminal (menu entry)_ > _New Terminal_, or using the command palette _(⌘ ⇧ P)_ and search for "Terminal". 1. Inside Terminal, run: ```pwsh dotnet new rhinocommand -n MyCommandName ``` ## Related Topics - [Installing Tools (Windows)](/guides/rhinocommon/installing-tools-windows) - [Installing Tools (Mac)](/guides/rhinocommon/installing-tools-mac) -------------------------------------------------------------------------------- # Contributing Source: https://developer.rhino3d.com/en/guides/general/contributing/ Pull requests are welcome! ## GitHub Many [McNeel projects](http://github.com/mcneel) are open-source and on [GitHub](http://github.com/). [RhinoCommon](https://github.com/mcneel/rhinocommon) - our cross-platform .NET SDK - is a great example. Even [this very website](https://github.com/mcneel/developer-rhino3d-com) you are reading now. Browse source, fork a repo, correct a typo: we welcome participation and pull-requests. ## Discourse [discourse.mcneel.com](http://discourse.mcneel.com) is the McNeel forum. This is the fastest place to get help. Specific categories to post in are: - [Rhino Developer](http://discourse.mcneel.com/c/rhino-developer): Customizing Rhino through VB, C#, C++, RhinoScript, and Python. - [Scripting](http://discourse.mcneel.com/c/scripting): Topics related to RhinoScript and Python scripting. - [openNURBS](http://discourse.mcneel.com/c/opennurbs): Topics related to the openNURBS toolkit. - [Grasshopper developer](http://discourse.mcneel.com/c/grasshopper-developer): VB, C#, and Python in Grasshopper components. ## Report Bugs Whenever you encounter something that doesn't work as it should, we'd love it if you could file a bug report. Please report issues or bugs with our APIs or SDKs on the appropriate Discourse category, above. ## This website This website is [open source on GitHub](https://github.com/mcneel/developer-rhino3d-com). If you find errors or think a page could be improved, just click the “Edit page on GitHub” link at the bottom of any page. If you are a McNeel employee, you should already have permissions to commit to this repository. If you are a Rhino Plugin Developer, you can [request permissions to commit](mailto:steve@mcneel.com). Anyone can clone the repository and submit a pull-request. This site uses [Markdown](http://daringfireball.net/projects/markdown/) as the base-format for all content. To get started authoring content for this site, please read the following guides: - [Contributing (to this website)](https://github.com/mcneel/developer-rhino3d-com/blob/main/CONTRIBUTING.md) - [Getting Started with Developer Docs](https://github.com/mcneel/developer-rhino3d-com/blob/main/README.md) - [How This Site Works](/guides/general/how-this-site-works) - [Developer Docs Style Guide](/guides/general/developer-docs-style-guide) You may also want to take a look at the [Admin](/admin/) page. ## Contacts Who to talk to for what: - [Steve Baer](/authors/steve) (RhinoCommon) - @stevebaer on [Discourse](http://discourse.mcneel.com/c/rhino-developer) - [Alain Cormier](/authors/alain) (Rhino.Python) - @alain on [Discourse](http://discourse.mcneel.com/c/rhino-developer) - [Dale Fugier](/authors/dale) (C/C++ SDK, Zoo, RAP) - @dale on [Discourse](http://discourse.mcneel.com/c/rhino-developer) - [Dale Lear](/authors/dalelear) (openNURBS) - @dalelear on [Discourse](http://discourse.mcneel.com/c/rhino-developer) - [David Rutten](/authors/david) (Grasshopper) - [David Rutten](http://www.grasshopper3d.com/profile/DavidRutten) on the [Grasshopper 3D forum](http://www.grasshopper3d.com) - [Giulio Piacentino](/authors/giulio) (GhPython) - @piac on [Discourse](http://discourse.mcneel.com/c/rhino-developer) - [Andy le Bihan](/authors/andy) (RDK) - @andy on [Discourse](http://discourse.mcneel.com/c/rhino-developer) - Curtis Wensley (Eto) - @curtisw on [Discourse](http://discourse.mcneel.com/c/rhino-developer) - [Dan Rigdon-Bel](/authors/dan) - @dan on [Discourse](http://discourse.mcneel.com/c/rhino-developer) -------------------------------------------------------------------------------- # Custom GhPython Baking Component Source: https://developer.rhino3d.com/en/guides/rhinopython/ghpython-bake/ Use GhPython to create custom baking components. Many times it is useful to create a custom component in Grasshopper to bake both geometry and other properties into Rhino in the same component. This guide will cover the varying calls to create these custom components using GhPython. There are a few different ways to approach baking. This guide set's up the simplest approach. At the end of this guide is a section on some of the variations possible to fit other bake situations. For more details any of the concepts in this guide, more details can be found in: 1. [GHPython editor basics guide](https://developer.rhino3d.com/guides/rhinopython/ghpython-component/) 1. [RhinoScriptSyntax and Python Guide](http://developer.rhino3d.com/guides/rhinopython/) 1. RhinoCommon API please refer to the [McNeel RhinoCommon Developer site](http://developer.rhino3d.com/guides/rhinocommon). ## Basic GHPython Component Start with a standard [GHPython Component](https://developer.rhino3d.com/guides/rhinopython/ghpython-component/). [Download this sample Gh file here....](https://github.com/mcneel/rhino.inside/raw/master/Autodesk/Revit/doc/samples/Python%20Bake.gh) In this case we use 3 inputs. Change the names of input and output and the Type hint by right clicking on each input: - `G`: Geometry to bake (Guid) - `L`: Layer name to bake to (str). - `B`: Activate toggle to bake (bool) To test this component. the Box Param is the easiest geometry type to use for testing. Add a Layer name using a panel as input. Use a Button or a Switch to toggle `True` and `False`. ## The Python code Here is the code for this component. In this case, the geometry is baked to Rhino on the default layer, then through Python we switch the layer to the one input. ```python """Provides a scripting component. Inputs: G: Geometry to bake L: Layer name for bake B: Bake Activate Output: a: The a output variable""" __author__ = "ScottD" __version__ = "2019.08.10" import rhinoscriptsyntax as rs import scriptcontext import Rhino if B: print(type(G)) #debug message to Python output #we obtain the reference in the Rhino doc doc_object = scriptcontext.doc.Objects.Find(G) print(type(doc_object)) attributes = doc_object.Attributes print('the type of attributes is: ' + str(type(attributes))) #debug message to Python output geometry = doc_object.Geometry print('the type of geometry is: ' + str(type(doc_object))) #debug message to Python output #we change the scriptcontext scriptcontext.doc = Rhino.RhinoDoc.ActiveDoc #we add both the geometry and the attributes to the Rhino doc rhino_brep = scriptcontext.doc.Objects.Add(geometry, attributes) print('the Rhino doc ID is: ' + str(rhino_brep)) #debug message to Python output #we can for example change the layer in Rhino... if not rs.IsLayer(L): rs.AddLayer(L) rs.ObjectLayer(rhino_brep, L) #we put back the original Grasshopper document as default scriptcontext.doc = ghdoc a = G ``` There are various key lines of code to be understand:
Line Description
17 Standard if statement to activate the bake once the B is set to TRUE.
22 The doc.Objects.Find method will attempt to take the Object ID and find the actual Rhino Geometry Object.
25 Split off the object's Attributes. The attributes of an object include such properties as color, layer, linetype, render material, and group membership, amongst others. The Advanced section below covers object attributes some more.
31 Split off the object's Geometry. Used when baking the object into Rhino.
32 Grasshopper has its own document. Rhino has its own document also. Make sure the script is talking to the Rhino Document for the following functions.
35 Add the object to the Rhino document. This is the actual Bake. Take note that if it has been baked previous, there will now be two objects.
39-40 Check to see if the layer name exists in Rhino. If it does not, then create a new layer with that name.
41 Change the Rhino object's layer to the requested layer name.
44 Switch the script context back to the Grasshopper document.
45 Pass the original component input object to the component output if it is needed in any downstream processes.
Hopefully this code will serve as a template in taking objects from Grasshopper into Rhino. There are a multitude of variations on this theme. The following section looks at various ways to use more advanced options to better fit the situation. ## Advanced Options for Baking Beyond simply changing an objects layer after adding the new object to Rhino. There are many other properties that can be changed. Here is a discussion of some of the possibilities. ### Baking colors materials and other properties In the sample above using the layername to specify where the object ultimately lands is quite simple. Here are some other properties that may want to be changed:
Object name rs.ObjectName(object-id, name)
Display color rs.ObjectColorSource(object_ids, source=None) rs.ObjectColor(object_ids, color=None)
Print color rs.ObjectPrintColorSource(object_ids, source=None) rs.ObjectPrintColor(object_ids, color=None)
Unlock object rs.UnlockObject(object_id)
Lock object rs.LockObject(object_id)
Hide object rs.HideObject(object_id)
Set Material rs.ObjectMaterialSource(object_ids, source=None) rs.ObjectMaterialIndex(object_id, material_index=None)
### Adding Key-Value text to the object Additional text data can be stored as a Key:Value pair on the Rhino object. THe Keys and Values can be retrieved later in Rhino. [rs.SetUserText(object_id, key, value=None, attach_to_geometry=False)](https://developer.rhino3d.com/api/RhinoScriptSyntax/#object-ObjectMaterialSource) For more information on [RhinoScript User Text help topic](https://developer.rhino3d.com/api/rhinoscript/user_data_methods/user_data_methods.htm). ### Changing Object Attributes: Through the RhinoCommon functions, the attributes of an object can be set. See the [Object Attribute Class for more details....](https://developer.rhino3d.com/api/RhinoCommon/html/T_Rhino_DocObjects_ObjectAttributes.htm) ## Next Steps That lays out the basics of the GhPython component. Next is a look into the component Python editor for Grasshopper. ## Related Topics - [Your first script with Python in Grasshopper](/guides/rhinopython/what-is-rhinopython) - [What is Python and RhinoScript?](/guides/rhinopython/what-is-rhinopython) - [Editing Python in Grasshopper](/guides/rhinopython/python-running-scripts) - [Python Guide for Rhino](/guides/rhinopython/) -------------------------------------------------------------------------------- # Get Current Model Information Source: https://developer.rhino3d.com/en/samples/rhinopython/current-model-info/ Demonstrates how to get current model information through Python. ```python # Displays information about the currently loaded Rhino document. import rhinoscriptsyntax as rs from System.IO import Path, File, FileInfo, FileAttributes # some helper functions for CurrentModelInfo def __FileAttributes(fullpath): "Returns a string describing a file's attributes." attr = File.GetAttributes(fullpath) if( attr == FileAttributes.Normal ): return "Normal" rc = "" if( attr & FileAttributes.Directory ): rc += "Directory " if( attr & FileAttributes.ReadOnly ): rc += "Read Only " if( attr & FileAttributes.Hidden ): rc += "Hidden " if( attr & FileAttributes.System ): rc += "System " if( attr & FileAttributes.Archive ): rc += "Archive " if( attr & FileAttributes.Compressed ): rc += "Compressed " return rc def __PrintFileInformation( fullpath ): "Displays a file's information." fi = FileInfo(fullpath) info = "Full Path: " + fullpath +"\n" info += "File Name: " + Path.GetFileName(fullpath) + "\n" info += "File Attributes: " + __FileAttributes(fullpath) + "\n" info += "Date Created: " + File.GetCreationTime(fullpath).ToString() + "\n" info += "Last Date Accessed: " + File.GetLastAccessTime(fullpath).ToString() + "\n" info += "Last Date Modified: " + File.GetLastWriteTime(fullpath).ToString() + "\n" info += "File Size (Bytes): " + fi.Length.ToString() + "\n" rs.MessageBox( info, 0, "Current Model Information" ) def CurrentModelInfo(): "Get the current document name and path" name = rs.DocumentName() path = rs.DocumentPath() fileexists = False if( path and name ): filespec = Path.Combine(path, name) fileexists = File.Exists(filespec) if fileexists: __PrintFileInformation(filespec) else: print("Current model not found. Make sure the model has been saved to disk.") ########################################################################## # Check to see if this file is being executed as the "main" python # script instead of being used as a module by some other python script # This allows us to use the module which ever way we want. if( __name__ == "__main__" ): #call function defined above CurrentModelInfo() ``` -------------------------------------------------------------------------------- # How to read and write a CSV files Source: https://developer.rhino3d.com/en/guides/rhinopython/python-csv-file/ Use Python to read and write comma-delimited files. CSV (comma separated values ) files are commonly used to store and retrieve many different types of data. The CSV format is one of the most flexible and easiest format to read. As an example, a CSV file might be used to store point locations in their X, Y, Z coordinate values: ```none 0.58,-3.7,0 0.58,-3.1,0 -0.23,0.91,0 -5,8.2,0 -3,9.2,0 2.1,8.8,0 2.5,5.5,0 6.2,1.6,0 6,1,0 ``` Or a CSV might contain building data, for instance room and usage information in a database type format. Note that this CSV has a header titles on the first row: ```none RoomID,Floor,Use,Square Footage,Capacity,Price 100,1,Retail,6598,55,6500 101,1,Retail,1900,25,3000 102,1,Retail,1850,25,3000 103,1,Restroom,250,0,0 104,1,Maintenance,150,0,0 200,2,Studio,875,2,850 201,2,Studio,734,2,850 202,2,Studio,624,2,850 203,2,Studio,624,2,850 ``` The [csv module](https://docs.python.org/2/library/csv.html) in Python can be used to quickly parse CSV files into different data structures. In this sample the point coordinate file we will read into a list in which the X, Y and Z coordinates can be referenced by the index position in a *list*. The building data will be read in to a *dictionary* object so that the various values can be referenced by the header titles as named pieces of information. ## Reading into a list Here is an example script to read a CSV file into a list. Each row will become its own list and then can be referenced by the index position: ```python import csv import rhinoscriptsyntax as rs def CSVlist(): #prompt the user for a file to import filter = "CSV file (*.csv)|*.csv|*.txt|All Files (*.*)|*.*||" filename = rs.OpenFileName("Open Point File", filter) if not filename: return with open(filename) as csvfile: reader = csv.reader(csvfile) for row in reader: x = float(row[0]) y = float(row[1]) z = float(row[2]) print x, y, z rs.AddPoint(x,y,z) ########################################################################## # Check to see if this file is being executed as the "main" python # script instead of being used as a module by some other python script # This allows us to use the module which ever way we want. if( __name__ == "__main__" ): CSVlist() ``` As usual the script starts with imports. This time the csv module is used. ```python import csv ``` Lines 2 thru 8 are standard lines from the Rhino.Python [How to read and write a simple file](/guides/rhinopython/python-reading-writing) guide. At line 10 the file is opened for reading. ```python with open(filename) as csvfile: ``` Then the key function in the script `.reader` reads each row into a list of values into the variable `reader`: ```python reader = csv.reader(csvfile) ``` The `csv.reader` method does all the parsing of each line into a list. Then each value on each line into a list: ```none ['0.58', '-3.7', '0'] ['0.58', '-3.1', '0'] ['-0.23', '0.91', '0'] ['-5', '8.2', '0'] ['-3', '9.2', '0'] ['2.1', '8.8', '0'] ['2.5', '5.5', '0'] ['6.2', '1.6', '0'] ['6', '1', '0'] ``` Manipulating each line of the list becomes easy: ```python for row in reader: x = float(row[0]) y = float(row[1]) z = float(row[2]) print x, y, z rs.AddPoint(x,y,z) ``` In this case the values are split into 3 variables (x, y, z) and then printed to the console and additionally used to add a new point object to Rhino. Reading a CSV file into a list is great for data that has the same number of values in each line and the position of the values are the same. However sometimes it is easier to read csv values into a dictionary where the values have names. ## Reading into a dictionary The next sample script is very similiar to above. ```python import csv import rhinoscriptsyntax as rs def CSVbuilding(): #prompt the user for a file to import filter = "CSV file (*.csv)|*.csv|*.txt|All Files (*.*)|*.*||" filename = rs.OpenFileName("Open Point File", filter) if not filename: return with open(filename) as csvfile: reader = csv.DictReader(csvfile) for row in reader: print(row['Use'], row['Square Footage']) # print(row) ########################################################################## # Check to see if this file is being executed as the "main" python # script instead of being used as a module by some other python script # This allows us to use the module which ever way we want. if( __name__ == "__main__" ): CSVbuilding() ``` This script uses the dictionary reader in the csv module on line 12: ``` reader = csv.DictReader(csvfile) ``` The [dictionary object](/guides/rhinopython/python-dictionaries) contains *key:value* pairs where the name (key) of the values can be used to search through the data. The complete dictionary object looks like this: ```none {'Floor':'1', 'Use':'Retail', 'Square Footage':'6598', 'Price':'6500', 'RoomID':'100', 'Capacity':'55'} {'Floor':'1', 'Use':'Retail', 'Square Footage':'1900', 'Price':'3000', 'RoomID':'101', 'Capacity':'25'} {'Floor':'1', 'Use':'Retail', 'Square Footage':'1850', 'Price':'3000', 'RoomID':'102', 'Capacity':'25'} {'Floor':'1', 'Use':'Restroom', 'Square Footage':'250', 'Price':'0', 'RoomID':'103', 'Capacity':'0'} {'Floor':'1', 'Use':'Maintenance', 'Square Footage':'150', 'Price':'0', 'RoomID':'104', 'Capacity':'0'} {'Floor':'2', 'Use':'Studio', 'Square Footage':'875', 'Price':'850', 'RoomID':'200', 'Capacity':'2'} {'Floor':'2', 'Use':'Studio', 'Square Footage':'734', 'Price':'850', 'RoomID':'201', 'Capacity':'2'} {'Floor':'2', 'Use':'Studio', 'Square Footage':'624', 'Price':'850', 'RoomID':'202', 'Capacity':'2'} {'Floor':'2', 'Use':'Studio', 'Square Footage':'624', 'Price':'850', 'RoomID':'203', 'Capacity':'2'} {'Floor':'2', 'Use':'Double', 'Square Footage':'850', 'Price':'1245', 'RoomID':'204', 'Capacity':'4'} {'Floor':'2', 'Use':'Double', 'Square Footage':'850', 'Price':'1245', 'RoomID':'205', 'Capacity':'4'} {'Floor':'2', 'Use':'Double', 'Square Footage':'850', 'Price':'1245', 'RoomID':'206', 'Capacity':'4'} {'Floor':'2', 'Use':'Double', 'Square Footage':'850', 'Price':'1245', 'RoomID':'207', 'Capacity':'4'} {'Floor':'2', 'Use':'Common', 'Square Footage':'731', 'Price':'0', 'RoomID':'208', 'Capacity':'0'} {'Floor':'2', 'Use':'Quad', 'Square Footage':'1100', 'Price':'1650', 'RoomID':'209', 'Capacity':'8'} {'Floor':'2', 'Use':'Quad', 'Square Footage':'1005', 'Price':'1650', 'RoomID':'210', 'Capacity':'8'} {'Floor':'2', 'Use':'Quad', 'Square Footage':'1205', 'Price':'1650', 'RoomID':'211', 'Capacity':'8'} ``` Now since each value is preceeded by a key, values may be found by searching for the key names. For instance in this case the script prints the `Use` and `Square Footage` from each line: ``` for row in reader: print(row['Use'], row['Square Footage']) ``` There are many ways to use dictionary data. Just to add to this example, here is code to add up all the square footage from the `Retail` space in the building: ```python with open(filename) as csvfile: reader = csv.DictReader(csvfile) total = 0 for row in reader: if row['Use'] == 'Retail': total += float(row['Square Footage']) print "Retail space = {} sq. ft".format(total) ``` Here the `For` statement loops through each row that contains 'Retail' and then will add the square footage for each into the `total` variable. In the end it prints the total in the console. By using the `csv.DictReader()` method, the simple CSV data can become a more sophisticated dictionary of information. This is a very powerful tool. ## Writing CSV files Writing a file is similar to reading, but the order in which things are done is different. This time we'll look at the ExportPoints.py sample file. ```python import csv import rhinoscriptsyntax as rs def CSVwrite(): #Get the filename to create filter = "CSV File (*.csv)|*.csv|*.txt|All Files (*.*)|*.*||" filename = rs.SaveFileName("Save point coordinates as", filter) if( filename==None ): return points = rs.GetPoints(draw_lines=False, message1="Pick points to save to CSV:") if( points==None): return with open(filename, "wb") as csvfile: csvwriter = csv.writer(csvfile, delimiter=',') for point in points: csvwriter.writerow([point[0], point[1], point[2]]) print "Points written sucessfully to file" ########################################################################## # Check to see if this file is being executed as the "main" python # script instead of being used as a module by some other python script # This allows us to use the module which ever way we want. if( __name__ == "__main__" ): CSVwrite() ``` Lines 1 thru 8 are standard lines from the Rhino.Python [How to read and write a simple file](/guides/rhinopython/python-reading-writing) guide. There are a couple of things to notice compared to the previous example. We need some points to export, so we'll let the user select some point objects. Then check that some points were picked: ```python points = rs.GetPoints(draw_lines=False, message1="Pick points to save to CSV:") if( points==None): return ``` Lines 14 thru 18 open the file for write. The script then runs through all the points in the list using the `.writerow` method. The write row method takes a list of values and adds the list as a line in the CSV file. ```python with open(filename, "wb") as csvfile: csvwriter = csv.writer(csvfile, delimiter=',') for point in points: csvwriter.writerow([point[0], point[1], point[2]]) print "Points written sucessfully to file" ``` The `.writerow` method is great for writing out various values in the simplest CSV form. ## Writing out a dictionary object Like the read methods, there is also a way to write out a dictionary object to a CSV file. This is a great way to format any dictionary data into a spreadsheet or table format. ```python import csv import rhinoscriptsyntax as rs def CSVwrite(): #Get the filename to create filter = "CSV File (*.csv)|*.csv|*.txt|All Files (*.*)|*.*||" filename = rs.SaveFileName("Save point coordinates as", filter) if( filename==None ): return dict = [ {'Floor':'1', 'Use':'Retail', 'Square Footage':'6598', 'RoomID':'100'}, {'Floor':'1', 'Use':'Retail', 'Square Footage':'1900', 'RoomID':'101'}, {'Floor':'1', 'Use':'Retail', 'Square Footage':'1850', 'RoomID':'102'}, {'Floor':'1', 'Use':'Restroom', 'Square Footage':'250', 'RoomID':'103'}, {'Floor':'1', 'Use':'Maintenance', 'Square Footage':'150', 'RoomID':'104'} ] with open(filename, "wb") as csvfile: fieldnames = ('Floor', 'Use', 'Square Footage', 'Price', 'RoomID', 'Capacity') writer = csv.DictWriter(csvfile, fieldnames=fieldnames) writer.writeheader() for row in dict: writer.writerow(row) ########################################################################## # Check to see if this file is being executed as the "main" python # script instead of being used as a module by some other python script # This allows us to use the module which ever way we want. if( __name__ == "__main__" ): CSVwrite() ``` In lines 10-16 we set a list of dictionaries. The header names correspond with the key names in the dictionary. The `.DictWriter` class can write a header in the CSV using the same values in the keys to write the CSV. The resulting CSV file will be formatted as follows: ```none Floor,Use,Square Footage,RoomID 1,Retail,6598,100 1,Retail,1900,101 1,Retail,1850,102 1,Restroom,250,103 1,Maintenance,150,104 ``` For more information on CSV manipulation, see the [Python.org csv module](https://docs.python.org/2/library/csv.html). ## Related Topics - [Python.org csv module](https://docs.python.org/2/library/csv.html) - [How to read and write a simple file](/guides/rhinopython/python-reading-writing) guide. - [How to use JSON with Python](/guides/rhinopython/python-xml-json) -------------------------------------------------------------------------------- # Migrate your plug-in project to Rhino 6 Source: https://developer.rhino3d.com/en/guides/cpp/migrate-your-plugin-manual-windows/ This guide walks you through migrating your Rhino 5 plug-in project to Rhino 6. It is presumed you already have the necessary tools installed and are ready to go. If you are not there yet, see [Installing Tools (Windows)](/guides/cpp/installing-tools-windows). ## Migrate the project 1. Launch *Visual Studio 2017* and click *File* > *Open* > *Project/Solution...*. 2. Navigate to your project's folder and open either your plugin project *(.vcxproj)* or solution *(.sln)* 3. When your plugin project opens, navigate to the project's setting by clicking *Project* > *Properties...*. 4. In the project's settings, set the *Configuration* to *All Configurations*, and set the platform to *x64*. 5. Then, set the *Platform Toolset* to *Visual Studio 2017 (v141)* and the click *Apply*. ![Plugin Settings](https://developer.rhino3d.com/images/migrate-plugin-windows-cpp.png) ## Remove 32-bit support Rhino 6 plugins are 64-bit only. If your plugin project has *Win32* platform support, then it is safe to remove it using *Visual Studio’s Configuration Manager*. 1. From *Visual Studio 2017*, click *Build* > *Configuration Manager...*. ![Configuration Manager](https://developer.rhino3d.com/images/migrate-plugin-windows-cpp-02.png) 2. In *Project Contexts*, click *Platform > Edit...*. ![Select Project Platforms](https://developer.rhino3d.com/images/migrate-plugin-windows-cpp-03.png) 3. In *Edit Project Platforms*, select the *Win32* platform, click *Remove* and then click *Close*. ![Edit Project Platforms](https://developer.rhino3d.com/images/migrate-plugin-windows-cpp-04.png) 4. Repeat the above step for the solution by click *Active solution platform > Edit...*. 5. In *Edit Solution Platforms*, select the *Win32* platform, click *Remove* and then click *Close*. ## Rename build configurations Rhino 6 plugin projects have different project build configuration names. In order to use the new SDK Property Sheets, you will need to rename the plugin project's build configurations so they match the new build configuration names. 1. In *Project Contexts*, click *Configuration > Edit...*. ![Select Project Configurations](https://developer.rhino3d.com/images/migrate-plugin-windows-cpp-05.png) 2. In *Edit Project Configurations*, remove the *Debug* configuration. 3. And then, rename the *PseudoDebug* configuration to *Debug*. ![Edit Project Configurations](https://developer.rhino3d.com/images/migrate-plugin-windows-cpp-06.png) 4. When you have finished renaming the configurations, click *Close*. ![Rename Project Configurations](https://developer.rhino3d.com/images/migrate-plugin-windows-cpp-07.png) 5. Repeat the above step for the solution by click *Active solution Configuration > Edit...*. 6. In *Edit Solution Configurations*, remove the *Debug* configuration, and then rename the *PseudoDebug* configuration to *Debug*. 7. When finished, click *Close*. 8. Close *Configuration Manager*. ## Add property sheet The Rhino C/C++ SDK includes Visual Studio Property Sheets that provide a convenient way to synchronize or share common settings among other plugin projects. 1. From *Visual Studio 2017*, click *View* > *Property Manager*. ![Property Manager](https://developer.rhino3d.com/images/migrate-plugin-windows-cpp-08.png) 2. Right-click on the *Debug | x64* configuration and click *Add Existing Property Sheet*. 3. Navigate to the following location: *C:\Program Files\Rhino 6.0 SDK\PropertySheets* 4. Select *Rhino.Cpp.PlugIn.props* and click *OK*. 5. Repeat the above steps for the the *Release | x64* configuration. ![Add Existing Property Sheet](https://developer.rhino3d.com/images/migrate-plugin-windows-cpp-09.png) ## Modify the project The project's pre-compiled header file, *stdafx.h*, needs to be modified so SDK header file inclusions point to the correct location. Also, the plugin .cpp file needs to include an additional SDK header. Finally, Visual Studio's resource editor and compiler requires the project contain a *targetver.h* file that identifies the target platform. 1. Using *Visual Studio’s Solution Explorer*, open *stdafx.h* and add the following preprocessor directive: ///////////////////////////////////////////////////////////////////////////// // stdafx.h : include file for standard system include files, // or project specific include files that are used frequently, but // are changed infrequently #pragma once #ifndef VC_EXTRALEAN #define VC_EXTRALEAN // Exclude rarely-used stuff from Windows headers #endif // Added for Rhino 6 Migration #define RHINO_V6_READY // If you want to use Rhino's MFC UI classes, then // uncomment the #define RHINO_SDK_MFC statement below. // Note, doing so will require that your plug-in is // built with the same version of Visual Studio as was // used to build Rhino. //#define RHINO_SDK_MFC ... 2. Also, remove the path specifiers to Rhino SDK header files found in *stdafx.h*, as the path to the SDK is provided by the SDK Property Sheet added above. // Rhino SDK Preamble //#include "C:\Program Files (x86)\Rhino 5.0 x64 SDK\Inc\RhinoSdkStdafxPreamble.h" #include "RhinoSdkStdafxPreamble.h" ... // Rhino Plug-in //#include "C:\Program Files (x86)\Rhino 5.0 x64 SDK\Inc\RhinoSdk.h" #include "RhinoSdk.h" // Render Development Kit //#include "C:\Program Files (x86)\Rhino 5.0 x64 SDK\Inc\RhRdkHeaders.h" #include "RhRdkHeaders.h" ... // Rhino Plug-in Linking Pragmas //#include "C:\Program Files (x86)\Rhino 5.0 x64 SDK\Inc\rhinoSdkPlugInLinkingPragmas.h" #include "rhinoSdkPlugInLinkingPragmas.h" 3. Using *Visual Studio’s Solution Explorer*, open the project's *PlugIn.cpp* file and add the following SDK include statement: #include "StdAfx.h" #include "SamplePlugIn.h" // Added for Rhino 6 Migration #include "rhinoSdkPlugInDeclare.h" ... 4. Using *Visual Studio’s Solution Explorer*, right-click on the *Header Files* folder and click *Add* > *New Item...*. 5. Add a new *Header File (.h)* named *targetver.h*. 6. Add the following content to it: #pragma once // Including SDKDDKVer.h defines the highest available Windows platform. // If you wish to build your application for a previous Windows platform, // include WinSDKVer.h and set the _WIN32_WINNT macro to the platform you // wish to support before including SDKDDKVer.h. #include "rhinoSdkWindowsVersion.h" #include Your plugin project should now be ready to build with the Rhino 6 C/C++ SDK. ## Related Topics - [What is a Rhino Plugin?](/guides/general/what-is-a-rhino-plugin) - [Installing Tools (Windows)](/guides/cpp/installing-tools-windows) - [Migrate your plugin project to Rhino 6](/guides/cpp/migrate-your-plugin-windows) -------------------------------------------------------------------------------- # Optional Cloud Zoo endpoints for License Management Source: https://developer.rhino3d.com/en/guides/rhinocommon/cloudzoo/cloudzoo-optional-endpoints/ It is possible to query and modify licensing data stored in Cloud Zoo by a registered issuer. This can be useful when a customer returns a license for a refund, as well as other scenarios. It is not required that you interact with these endpoints, but you may find them useful given your business requirements. ## Endpoint Conventions Unless noted, the following conventions apply to *all* endpoints available to registered issuers in Cloud Zoo. ### Endpoint Location The base URL for all requests is `https://cloudzoo.rhino3d.com/v1`. ### JSON All payload to and from endpoints happens in [JSON format](https://www.json.org). To make this explicit, every response to an endpoint will have the header `Content-Type: application/json` present in the HTTPS response. ### Authentication All endpoints in Cloud Zoo or on the issuer use [Basic Authentication](https://en.wikipedia.org/wiki/Basic_access_authentication). To receive a successful response from an endpoint, you must include an `Authorization` header like so: ``` Authorization: Basic BASE64ENCODEDSTRING ``` where `BASE64ENCODEDSTRING` is a [base64](https://en.wikipedia.org/wiki/Base64) encoded string containing your issuer id and your issuer secret: ```python BASE64ENCODEDSTRING = b64.encode(issuer_id + ":" + issuer_secret) ``` ### Non-successful responses All unsuccessful responses from endpoints will have an HTTP status code greater or equal to `400`. If the status code is also less than `500`, the payload will include the following JSON: { "Error": "SomeErrorCode" "Description": "A description about the error message", "Details": "More details about the error" } - The `Error` field contains a specific error code that can be used by the issuer to recognize a specific error, such as incorrect credentials. - The `Description` field contains a description of the error. - The `Details` field contains details of the error, possibly suggesting how to fix it. If the status code is greater or equal to `500`, the response may not be in JSON format and may be empty. ## Endpoints ### DELETE /license Removes a license from Cloud Zoo. This method deletes the entire [License Cluster object](/guides/rhinocommon/cloudzoo/cloudzoo-licensecluster) the license is in. If the License Cluster the license belongs to contains additional licenses, they will be removed as well. This endpoint expects the arguments to be passed as a query string. #### Example Request DELETE /license?licenseId=LICENSE_ID&productId=PRODUCT_ID&entityId=ENTITY_ID - `entityId`: The id of the entity the license belongs to. - `productId`: The id of the product the license represents. This is a GUID. - `licenseId`: The license id that identifies a unique license within the product id domain. #### Response A successful response (The license was removed): - HTTP Status Code: `200 (OK)` - Response Payload: Empty. A non-successful (error) response (The license cannot be removed): - HTTP Status Code: A code greater or equal to `400 (Bad Request)` - Response Payload: [A non-successful response](#non-successful-responses) ### PUT /license Adds or replaces a License Cluster in Cloud Zoo. If any of the licenses in the [License Cluster object](/guides/rhinocommon/cloudzoo/cloudzoo-licensecluster) passed already exist in the given entity, their license cluster will be overwritten with the License Cluster passed. If there is more than one cluster in the entity containing the licenses in the cluster passed, an error will be returned and the operation will be aborted. #### Example Request PUT /license { "entityId": "9034901491490-|-Group", "licenseCluster": LICENSE_CLUSTER_OBJECT } The `entityId` should be the entity where the License Cluster will be added or updated. The `licenseCluster` should be a [License Cluster object](/guides/rhinocommon/cloudzoo/cloudzoo-licensecluster) representing the license(s) to be added or updated. #### Response A successful response (The license was removed): - HTTP Status Code: `200 (OK)` - Response Payload: Empty. A non-successful (error) response (The license cannot be added/updated): - HTTP Status Code: A code greater or equal to `400 (Bad Request)` - Response Payload: [A non-successful response](#non-successful-responses) -------------------------------------------------------------------------------- # Plugin Installers (Mac) Source: https://developer.rhino3d.com/en/guides/rhinocommon/plugin-installers-mac/ This guide explains how to create a plugin installer for Rhino for Mac. ⚠️ The .macrhi format is no longer in active development. Please see the Package Manager instead. It is presumed you have a plugin that successfully builds and runs already. If you are not there yet, see [Your First Plugin (Mac)](/guides/rhinocommon/your-first-plugin-mac). ## Overview Rhino for Mac does not (yet) have a Plugin Manager. However, installing plugins is very easy. You simply rename your plugin's containing *folder* with an special extension (*.rhp*), compress the folder, and change the extension from *.rhp.zip* to *.macrhi*. Once this is done, you can double-click the archive and Rhino will launch and install the plugin. You can also drag the *.macrhi* onto the dock icon of a running instance of Rhino and it will install the plugin as well. You will, in any case, need to Quit an Restart Rhino for the plugin to activate. ## Step-by-Step 1. *Locate* your plugin folder in *Finder*. Let's imagine our plugin is called *HelloRhinoCommon* and we have built it for *Release*... ![Find Plugin In Finder](https://developer.rhino3d.com/images/plugin-installer-mac-01.png) 1. *Single-click the name* your plugin's *Release* (or *Debug*) folder to *Rename* it. The new name should be your plugin assembly with a *.rhp* suffix. For example, if your plugin is called *HelloRhinoCommon*, rename the folder that contains this file *HelloRhinoCommon.rhp*... 1. You will be prompted to confirm this change. Click the "**Add**" button: ![Click Add](https://developer.rhino3d.com/images/plugin-installer-mac-02.png) 1. The icon of the folder[^1] should now look like this... ![New Icon](https://developer.rhino3d.com/images/plugin-installer-mac-03.png) 1. *Archive* the plugin *folder*. *Right-click* (option-click) the plugin *.rhp* *folder* you created in the previous step and select "*Compress* (your plugin name)." This creates a zip archive of the contents of the folder. 1. *Single-click the name* of the new archive you created in step 5. This allows you *to rename* the archive. 1. Change the *extension* from *.rhp.zip* to *.macrhi*. 1. You will be prompted to confirm this change. Select the "*Use .macrhi*" button: ![Use rhi Extension](https://developer.rhino3d.com/images/plugin-installer-mac-04.png) 1. Notice that *the icon changes* from a zip archive to a Rhino RHI: ![use_macrhi_confirm](https://developer.rhino3d.com/images/plugin-installer-mac-05.png) 1. If Rhino for Mac is open, *drag the* *.macrhi* archive onto Rhino for Mac's icon in the *dock*; OR: 1. If Rhino for Mac is *not* currently open, *double-click the .macrhi archive* to launch and install the plugin... ![plugin_loaded](https://developer.rhino3d.com/images/plugin-installer-mac-06.png) 1. Click *OK* then *Quit* and *Restart* Rhino. Your plugin should load. ## Behind the Scenes The *.macrhi* extension is a file extension associated with the Rhino for Mac application (both *Rhinoceros.app* and *RhinoWIP.app*). This extension denotes a "Rhino for Mac plugin installer." Rhino for Mac knows that such files are actually .zip archives that need to be decompressed and copied into the user's Library folder at the appropriate location, specifically the *~/Library/Application Support/McNeel/Rhinoceros/MacPlugIns/* folder[^2]. When Rhino for Mac launches, it searches the contents of the *~/Library/Application Support/McNeel/Rhinoceros/MacPlugIns/* folder scanning the sub-folders looking for *.rhp* files. When it finds such "file" (which are actually packages), Rhino for Mac attempts to load the assembly with the same name contained within this package. If it cannot load the plugin, it will show an error at launch time. For uninstallation/removal instructions, please see [Uninstalling Plugins (Mac)](/guides/rhinocommon/uninstalling-plugins-mac). #### User Library By default, the User Library folder is hidden from view. To make your Library visible in the Finder: 1. In *Finder*, navigate to your *Home* (*~*) folder. You must be in your Home folder for this to work. 1. Press Command+J to bring up the *Finder View* options dialog... ![finder_view_options](https://developer.rhino3d.com/images/finder-view-options.png) 1. Check the *Show Library Folder* check box. Now your Library should show up in the view. You may want to drag this folder to your Favorites area of the Finder sidebar for easy access later. ## Maintaining Your Own Installer? As explained above, when Rhino for Mac launches, it searches the contents of the: *~/Library/Application Support/McNeel/Rhinoceros/MacPlugIns/* folder scanning the sub-folders looking for *.rhp* files. When it finds such "file" (which are actually packages), Rhino for Mac attempts to load the assembly with the same name contained within this package. If you are maintaining your own installer, simply writing your plugin to this folder should be sufficient. ### Version-Specific Installations *If you are maintaing your own installer*, in Rhino 8 for Mac onward\*, it is possible to register version-specific installations. To do this, your installer will need to create a *MacPlugIns* folder *within the version folder* that resides in: *~/Library/Application Support/McNeel/Rhinoceros/* For example, to register a plugin that only Rhino 8 will load, your installer should write to: *~/Library/Application Support/McNeel/Rhinoceros/8.0/MacPlugIns/YourPluginName* \* Rhino 7's package manager already supports version-specific (and even platform-specific) targets. See the [Anatomy of a Package: Distributions section](/guides/yak/the-anatomy-of-a-package/#distributions) for more information. ## Related topics - [Your First Plugin (Mac)](/guides/rhinocommon/your-first-plugin-mac)-crossplatform) - [Uninstalling Plugins (Mac)](/guides/rhinocommon/uninstalling-plugins-mac) - [Plugin Installers (Windows)](/guides/rhinocommon/plugin-installers-windows) **Footnotes** [^1]: macOS (and Unix) has a special kind of folder that masquerades as a file. These are called "packages." (Most apps found in */Applications/* are actually packages called "bundles"). You can access the contents in Finder by *right-clicking* on the package and selecting *Show Package Contents*. [^2]: Do not confuse this path with */Library/Application Support/McNeel/Rhinoceros/*, which is the system-wide Library location. -------------------------------------------------------------------------------- # Plugin Installers (Windows) Source: https://developer.rhino3d.com/en/guides/rhinocommon/plugin-installers-windows/ This guide explains how to create a plugin installer for Rhino for Windows. ⚠️ The Rhino Installer Engine is no longer in active development. Please see the Package Manager instead. Note: This process is the same for both C/C++ and RhinoCommon plug-ins! ## Overview Creating a plugin installer is very easy. You simply add your compiled plugin to a zip archive and change the extension from *.zip* to *.rhi*. Once this is done, you can double-click the archive and the [Rhino Installer Engine](/guides/general/rhino-installer-engine) will begin to install your plugin. That's all there is to it! This is intended to be a quickstart guide. For a more general overview please see the Rhino Installer Engine guide. ## An Example Imagine you have a plugin and want to support multiple versions of Rhino. For example, you want to: - Install the latest version of the plugin for Rhino WIP - Install an older version of the plugin for 64-bit Rhino 5 - Install yet another version of the plugin for 32-bit Rhino 5 - Include a custom toolbar file (e.g. *MyToolbar.rui*) This is possible. You need to: 1. Create an "installer image" folder. In this example, the folder is the name of the product – _Marmoset_. This folder will contain only the files you want to install on the user's system. Marmoset/ ├── Rhino 6/ │ ├── Marmoset.rhp │ └── required_wip.dll ├── Rhino 5.0/ │ ├── x86/ │ │ ├── Marmoset.rhp │ │ └── required_v5_x86.dll │ └── x64/ │ ├── Marmoset.rhp │ └── required_v5_x86.dll ├── Marmoset.rui ├── Marmoset.chm └── README.txt 1. Copy the appropriate files into the folders[^1]. Note that all three versions of the plugin can have the same name, so long as they are in different folders. 1. Add all the files inside the "installer image" folder to a new ZIP[^2] archive 1. Change the extension from *.zip* to *.rhi* ## Everything but the kitchen sink Because the Rhino Plugin Installer Engine unzips your *.rhi* file into a directory specific to your plugin, you can include anything you want: help files, documentation, etc. These files will end up inside your plugin directory; The Rhino Installer Engine cannot be used to install files to other parts of the hard drive. ## Related topics - [Rhino Installer Engine](/guides/general/rhino-installer-engine) - [Plugin Installers (Mac)](/guides/rhinocommon/plugin-installers-mac) **Footnotes** [^1]: Folder names are not important; the *.rhp* files themselves are inspected to determine for which versions of Rhino they will be installed. [^2]: Other compression algorithms are not supported. -------------------------------------------------------------------------------- # Procedurally Generate Toolbars Source: https://developer.rhino3d.com/en/guides/rhinocommon/procedurally-generate-toolbars/ This guide covers the generation of toolbar button images. ## Question I am trying to generate a toolbar without using Rhino. Having a toolbar with correct macros is done and working, but the bitmap (icons) part is still problematic I’m not sure if the issue is with the image generation or the GUID system of Rhino. For each icon size, the script takes all the icons needed and makes a big one, resulting in a column of icons. Then the image is translated to ```Base64``` format. Rhino does not recognize these images. They are not displayed. And upon saving the toolbar, all the previous icon data is replaced with a blank image. ## Answer Toolbar bitmaps are stored in a grid with 250 columns and enough rows to accommodate the total item count. New items are added to then end of the last row until the row fills up at which time a new row is added to the bitmap. Rhino writes the bitmap as a PNG file to a stream and then writes the raw bitmap to the RUI file. Here is some sample code for writing: ```c# const string NODE_BITMAPITEM = "bitmap_item"; const string NODE_BITMAP = "bitmap"; ... public void Write(System.Xml.XmlWriter writer, string elementName) { writer.WriteStartElement(elementName); { writer.WriteAttributeString("item_width", m_item_size.Width.ToString()); writer.WriteAttributeString("item_height", m_item_size.Height.ToString()); foreach (KeyValuePair kvp in m_id_list) { writer.WriteStartElement(NODE_BITMAPITEM); { writer.WriteAttributeString("guid", kvp.Key.ToString()); writer.WriteAttributeString("index", kvp.Value.m_index.ToString()); } writer.WriteEndElement(); } writer.WriteStartElement(NODE_BITMAP); { if (null != FullBitmap) { try { using (var stream = new MemoryStream()) { FullBitmap.Save(stream, System.Drawing.Imaging.ImageFormat.Png); byte[] bytes = stream.GetBuffer(); if (null != bytes && bytes.Length > 0) writer.WriteString(Convert.ToBase64String(bytes, Base64FormattingOptions.InsertLineBreaks)); } } catch (Exception ex) { Rhino.Runtime.HostUtils.ExceptionReport(ex); } } } writer.WriteEndElement(); } writer.WriteEndElement(); } ``` ## Related Topics - [Creating and Deploying Plugin Toolbars](/guides/rhinocommon/create-deploy-plugin-toolbar) - [Localizing Plugin Toolbars](/guides/rhinocommon/localize-plugin-toolbar) ​ -------------------------------------------------------------------------------- # Rhino Installer Engine Source: https://developer.rhino3d.com/en/guides/general/rhino-installer-engine/ This guide is a brief introduction to the Rhino Installer Engine. ⚠️ This technology is obsolete as of Rhino 7 and has been replaced by the yak format along with the PackageManager. ## Overview (Windows) The Rhino Installer Engine simplifies distribution, installation and updating of plug-ins for Rhino for Windows. ## How It Works ### File and folder structure A Rhino Installer package is a zip file with an *.rhi* extension. The package can include more than one version of a plug-in however all versions must share the same GUID (i.e. they're different versions of the _same_ plug-in). There are no file structure or naming requirements. For example the two packages below are functionally equivalent. Both contain versions of "Marmoset" – a fictitious C++ plug-in compiled for Rhino 5 (32-bit and 64-bit) and Rhino 6[^1]. ``` Marmoset_tree.rhi/ ├── Rhino 6/ │ ├── Marmoset.rhp │ ├── Marmoset.dll │ └── Marmoset.rui └── Rhino 5.0/ ├── x86/ │ ├── Marmoset.rhp │ └── ... └── x64/ ├── Marmoset.rhp └── ... ``` ``` Marmoset_flat.rhi/ ├── Marmoset_rhino6.rhp ├── Marmoset_rhino5_x86.rhp ├── Marmoset_rhino5_x64.rhp ├── Marmoset_rhino6.dll ├── ... └── Marmoset.rui ``` You can include anything you want in the *.rhi* package – supporting DLLs, help files, documentation, [toolbar (*.rui*) files](/guides/rhinocommon/create-deploy-plugin-toolbar.md), etc. The entire contents are unzipped to a directory on the user's machine. ### Installation and compatibility The Rhino Installer Engine examines each *.rhp* file and extracts the plugin GUID, Title, Version, the SDK used (e.g. RhinoCommon, C++) and the SDK version. This information is used to determine which version of the plug-in will be installed for which installed version of Rhino for Windows; the newest compatible plugin is registered with the corresponding version of Rhino. RhinoCommon plug-ins compiled as `AnyCPU` will be installed for both 32- and 64-bit Rhino 5[^1]. Since Rhino 6: Where a RhinoCommon plug-in is found that has been compiled against an earlier _major_ version of Rhino than is installed, an in-depth compatibility check will be performed to determine whether the SDK of the installed Rhino still supports the functionality used by the plug-in. If the check is successful then the outdated plug-in will be installed. ## Limitations - The Rhino Installer Engine will copy files from the *.rhi* archive, and will register the plug-ins it finds. No other execution is done. - Currently, it is not possible to digitally sign *.rhi* files in order to verify the source of *.rhi* files. - The Rhino Installer Engine is available with Rhino 5 and later. ## Related Topics - [Plugin Installers (Windows)](/guides/rhinocommon/plugin-installers-windows) - [Plugin Installers (Mac)](/guides/rhinocommon/plugin-installers-mac) **Footnotes** [^1]: Since version 6 Rhino for Windows has been 64-bit only. -------------------------------------------------------------------------------- # Rhino objects in Python Source: https://developer.rhino3d.com/en/guides/rhinopython/python-rhinoscriptsyntax-objects/ This guide provides an overview of RhinoScriptSyntax Object Geometry in Python. ## Objects Rhino can create and manipulate a number of objects, including points, point clouds, curves, surfaces, B-reps, meshes, lights, annotations, and references. Each object in the Rhino document is identified by a globally unique identifier, or GUID, that is generated and assigned to objects when they are created. Object identifiers are saved in the 3dm file, so an object's identifier will be the same between editing sessions. To view an object's unique identifier, use Rhino's `Properties` command. For convenience, RhinoScriptSyntax returns object identifiers in the form of a string. For example, an object's identifier will look something like the following: `F6E01514-3264-4598-8A07-A58BFE739C38` The majority of RhinoScriptSyntax's object manipulation methods require one or more object identifiers to be acquired before the method can be executed. ## Geometry and Attributes Rhino objects consist of two components: the object's geometry and the object's attributes. The types of geometry support by Rhino include points, point clouds, curves, surfaces, polysurfaces, extrusions, meshes, annotations and dimensions. The attributes of an object include such properties as color, layer, linetype, render material, and group membership, amongst others. ## Example The following example uses Object IDs to create reference geometry: ```python import rhinoscriptsyntax as rs startPoint = (1.0, 2.0, 0.0) endPoint = (4.0, 5.0, 0.0) line1ID = rs.AddLine(startPoint,endPoint) # Adds a line to the Rhino Document and returns an ObjectID startPoint2 = (1.0, 4.0, 0.0) endPoint2 = (4.0, 2.0, 0.0) line2ID = rs.AddLine(startPoint2,endPoint2) # Returns another ObjectID int1 = rs.LineLineIntersection(line1ID,line2ID) # passing the ObjectIDs to the function. print(int1) ``` ## Related Topics - [What is Python and RhinoScript?](/guides/rhinopython/what-is-rhinopython) - [Points](/guides/rhinopython/python-rhinoscriptsyntax-points) - [Vectors](/guides/rhinopython/python-rhinoscriptsyntax-vectors) - [Lines](/guides/rhinopython/python-rhinoscriptsyntax-lines) - [Planes](/guides/rhinopython/python-rhinoscriptsyntax-planes) - [Rhino NURBs Geometry](/guides/rhinopython/python-rhinoscriptsyntax-nurbs) -------------------------------------------------------------------------------- # Using methodgen Source: https://developer.rhino3d.com/en/guides/rhinocommon/using-methodgen/ This guide covers the automatic pInvoke call generator and enum synchronization utility called methodgen. It builds on top of concepts from the [Wrapping Native Libraries](/guides/rhinocommon/wrapping-native-libraries) guide. ## Overview Wrapping a C/C++ library for .NET consumption is a large task. Usually, there is a C/C++ library with every C exported function, that will link to or be itself the content of the exported functionality (we call this *lib_C*, in a directory called *lib_C_dir*), and a C# library that will provide access to the exported functionality (*lib_CS*, in *lib_CS_dir*). Besides other more high-level restructuring, wrapping usually involves: 1. Creating a large amount of C functions to be exported for PInvoke, 2. The definition of some helper enum values that have the only purpose of helping C - C# communication, and 3. Copying many public C++ enum values to C# For each of these tasks, we wrote a tool, *methodgen.exe*, that automatically writes most of the boilerplate code. ## methodgen in your project To run the tool, place it in a folder that is a parent folder to both your C and your C# solutions. Then, add a prebuild event to your project, either using the standard Visual Studio for Window or Visual Studio for Mac interface, of by adding this code to the `.csproj` solution: ``` $(ProjectDir)..\methodgen.exe lib_C_dir lib_CS_dir ``` Each path can be relative. Every *.h* and *.cpp* file in *lib_C_dir* will be parsed. A file called *AutoNativeMethods.cs*, and another one called *AutoNativeEnums.cs* if required, will be placed in *lib_CS_dir* at the end of the process. This will happen in the following 3 steps... ## 1. Export C functions to C\# *methodgen.exe* looks for every line starting with `RH_C_FUNCTION` in every *.h* and *.cpp* file in *cpp_dir*. We define the `RH_C_FUNCTION` macro directive like this: ```cpp #define RH_C_FUNCTION extern "C" __declspec(dllexport) ``` A typical exported C function will look like this: ```cpp RH_C_FUNCTION int ON_Brep_SplitEdgeAtParameters(ON_Brep* pBrep, int edge_index, int count, /*ARRAY*/const double* parameters) { int rc = 0; if (pBrep && count>0 && parameters) rc = pBrep->SplitEdgeAtParameters(edge_index, count, parameters); return rc; } ``` Inside *AutoNativeMethods.cs*, in an `_internal partial class_` called `UnsafeNativeMethods`, *methodgen.exe* will create these lines of code: ```cs [DllImport(Import.lib, CallingConvention=CallingConvention.Cdecl )] internal static extern int ON_Brep_SplitEdgeAtParameters(IntPtr pBrep, int edgeIndex, int count, double[] parameters); ``` ### Parsing function parameter and return types Every parameter to the function will undergo some transformation to be useful in C# as follows: - `const` will be removed. It might be a good idea to name parameter variables in a predictable manner relative to const-ness of the data that is passed. - Any `type*` will be transformed to a generic `IntPtr`. As an exception, if the pointer refers to a fundamental type (such as `int`, `unsigned char`), this will be passed as a inbuilt C# type, with the keyword `ref` prefixed. - Fundamental types will be translated to the corresponding C# type. The `unsigned` modifier will usually be removed. E.g., `unsigned int` will be transformed in `uint`. `unsigned char` will become `byte`. - `/*ARRAY*/` allows to treat pointers to fundamental types (such as `int`, `double`) as C# arrays, rather than `ref` values. The `/*ARRAY*/` string token must not contain extra spaces (see example above). - Enum types exported with `RH_C_SHARED_ENUM` will be translated to the defined C# counterpart (see below for details). ## 2. Define helper enums for Marshalling Enums that are found while scanning every *.h* and *.cpp* file in *cpp_dir* will be exported as nested enums in *AutoNativeMethods.cs*, in the same `UnsafeNativeMethods` class. These should be C enums. For example: ```cpp enum MeshBoolConst : int { mbcHasVertexNormals = 0, mbcHasFaceNormals = 1, mbcHasTextureCoordinates = 2, mbcHasSurfaceParameters = 3, mbcHasPrincipalCurvatures = 4, mbcHasVertexColors = 5, mbcIsClosed = 6 }; ``` This can then be used in `RH_C_FUNCTION` lines: ```cpp RH_C_FUNCTION bool ON_Mesh_GetBool(const ON_Mesh* pMesh, enum MeshBoolConst which) { bool rc = false; if (pMesh) { switch (which) { case mbcIsClosed: rc = pMesh->IsClosed(); break; // other cases omitted } } } ``` and will result in this C# enum and the possibility to call it without using any enum value (just the field name)... ```cs internal enum MeshBoolConst : int { HasVertexNormals = 0, HasFaceNormals = 1, HasTextureCoordinates = 2, HasSurfaceParameters = 3, HasPrincipalCurvatures = 4, HasVertexColors = 5, IsClosed = 6 } [DllImport(Import.lib, CallingConvention=CallingConvention.Cdecl )] [return: MarshalAs(UnmanagedType.U1)] internal static extern bool ON_Mesh_GetBool(IntPtr pMesh, MeshBoolConst which); //your code UnsafeNativeMethods.ON_Mesh_GetBool(ptr, UnsafeNativeMethods.MeshBoolConst.IsClosed); ``` Notice that every enum field had some prefixed lower-case acronym, which is removed by *methodgen*. ## 3. Share enum values between the C++ and C\# Similar to the [Define helper enums for Marshalling](#defining-helper-enums-for-marshalling) step, *methodgen* also allows to harvest enum values directly from any file (being that *.h*, *.cpp*, or anything else) that follows the convention explained here. Again, every *.h* and *.cpp* file in *lib_C_dir* is parsed, looking for lines starting with `RH_C_SHARED_ENUM_PARSE_FILE`. `RH_C_SHARED_ENUM_PARSE_FILE` can be defined by you as an empty macro: ```cpp #define RH_C_SHARED_ENUM_PARSE_FILE(path) #define RH_C_SHARED_ENUM_PARSE_FILE3(path, sectionPrefix, sectionSuffix) ``` `path` becomes then a candidate for shared enum collection. Every `path` file is then opened, and the tool searches for `#pragma region RH_C_SHARED_ENUM` and processes an enum till `#pragma endregion`. The complete `RH_C_SHARED_ENUM` syntax will look like this: ```cpp #pragma region RH_C_SHARED_ENUM [full_cpp_type] [full_cs_type] [options] //... enum #pragma endregion ``` The above code is broken down as follows: - `full_cpp_type`: the full C++ type name. This should be the standard way to reference any instance. - `full_cs_type`: the full C# type name. This will be the standard way to reference the instance type in .NET. - `options`: any combination of one single item within each of these sets, `(`**`public`**`|internal)`, `(`**`unnested`**`|nested)`, `(sbyte|byte|`**`int`**`|uint|short|ushort|long|ulong)`, `(flags)`, `(clsfalse)`; separated by a colon (`:`). If an option from a set is not specified, the one in bold is used. Sets without a bold item have the option turned off. ### Remarks on options Keep the following in mind concerning options: - `nested`: please use this option with care. See the [.NET design guidelines for nested types on MSDN](https://msdn.microsoft.com/en-us/library/ms229027.aspx). Keeping class design simple is paramount. - `type` (`int`, `long`, etc): usually, just choose the corresponding .NET type that is CLSCompliant. If you choose anything else than a type that is sized the same as the C++ one, you are responsible for differences in array sizes. Automatic marshalling usually gets the size of single arguments right and casts accordingly. If the size of the C# element is smaller than the C++ one, there will possibly be problems with uniqueness of fields. If you use any non-CLS-compliant type, then you might have to add the next option. - `clsfalse`: marks the resulting enum code with the `[CLSCompliant(false)]` attribute. Only here for compatibility with already-defined C# enums. - `flags`: marks the resulting enum code with the `[Flags]` attribute. Always mark the .NET type with this attribute if the enum is used as a bitmask. ### Enum design

WARNING

Although similar in purpose, .NET and C++ enums differ considerably both in common usage and in formatting style conventions. Here we list a few gotchas of which to be aware when defining shared enums. Read the following list carefully. Follow these rules when sharing enums: - .NET enums are best defined as deriving from CLS-compliant types: `byte`, `short`, `int` and `long`. When sharing a C++ enum that translates to `sbyte`, `ushort`, `uint`, `ulong`, it might be tempting to use one of these non-CLS-compliant types, and apply the provided option. *This is a bad idea!* The option is only there for support of already-existing enums. Every method with non-CLS-compliant parameters will be non-CLS-compliant. On the other hand, only a solvable issue with the first bit of high-valued enum fields needs to be addressed when translating an `unsigned int` enum to an `int` one in C#. .NET users of the library will find a library that is simpler to read as well. - In an enum shared with .NET, do not define sentinel values or fields cautiously reserved for future use. They are evil. See the [Enum Design on MSDN](https://msdn.microsoft.com/en-us/library/ms229058%28v=vs.110%29.aspx) to better understand why. - Because each enum field name will be the same as in .NET, it is best practice not to use constant prefixes like `k` in `kMyEnum`, and not to use `ALL_CAPS`. Simply use `PascalCase` for enum field names. - Consider using `class enums` in C++ to avoid naming collisions. This is not an issue in .NET, because *methodgen* removes the `class` keyword automatically. - Use .NET-type descriptions also in C++ to comment the enum and each of its field. A `///` is usually sufficient. To illustrate the above rules in action, in a parsed file, place: ```cpp RH_C_SHARED_ENUM_PARSE_FILE3("../../../opennurbs/opennurbs_subd.h", "#if OPENNURBS_SUBD_WIP", "#endif") ``` Then, within that *opennurbs_subd.h* file: ```cpp #pragma region RH_C_SHARED_ENUM [ON_SubD::SubDType] [Rhino.Geometry.SubD.SubDType] [nested:byte] /// /// Subdivision algorithm. /// enum class SubDType : unsigned char { /// /// Built-in Loop-Warren triangle with Bernstein-Levin-Zorin creases and darts. /// TriLoopWarren = 3, /// /// Built-in Catmull-Clark quad with Bernstein-Levin-Zorin creases and darts. /// QuadCatmullClark = 4, }; #pragma endregion ``` This above will result in an enum called `SubDType`, placed inside a `_public partial_ class SubD`, inside *AutoNativeEnums.cs*. ## RH_C_PREPROCESSOR macro Inside every *.h* and *.cpp* file in *lib_C_dir*, *methogen* keeps track of `#ifdef`, `#ifndef`, `#if defined`, `#elif defined`, `#else` and `#endif` lines marked with an `RH_C_PREPROCESSOR` suffix. Lines with both preprocessor instructions and the mark will not be removed and will appear in *AutoNativeMethods.cs*, transformed to the appropriate C# representation. The `RH_C_PREPROCESSOR` string can appear either in plain code or in a comment, provided it is the first word in the comment. For example: `#else //RH_C_PREPROCESSOR` is just as valid as: `#else RH_C_PREPROCESSOR` This is because having extra tokens after `#endif` is non-standard C, and some compilers check against this condition. ## Related Topics - [Wrapping Native Libraries](/guides/rhinocommon/wrapping-native-libraries) - [.NET design guidelines for nested types on MSDN](https://msdn.microsoft.com/en-us/library/ms229027.aspx) - [Enum Design on MSDN](https://msdn.microsoft.com/en-us/library/ms229058%28v=vs.110%29.aspx) -------------------------------------------------------------------------------- # VBScript Logic Source: https://developer.rhino3d.com/en/guides/rhinoscript/vbscript-logic/ This guide discusses the logic, or lack of, in VBScript. ## Logic? Consider the following statements: ```vbnet If blnResult = True Then Print "True!" Else Print "False!" ``` and ```vbnet If blnResult Then Print "True!" Else Print "False!" ``` Is there a difference? ## What Logic? Yes, there is a big difference. If `blnResult` is True or False, then both statements do what you would expect – the same thing. But, the first statement is asking "Is `blnResult` equal to True?" whereas the second question is asking "Is `blnResult` not equal to False?" In a strictly Boolean world, those are equal statements. But the VBScript type system is richer than just Booleans. ## Details For example, what if - in the above example - `blnResult` is the string True? The string True is not equal to the Boolean True, so the first statement is false. But the string is also not equal to False, so the second statement is true, and the statements have different semantics. The same goes for numbers. When converted to a number, True converts to -1 (for reasons which will become clear in a moment) and False converts to 0. So, if `blnResult` is 1, again the first statement is false because 1 <> -1, and the second statement is true because 1 <> 0. What's going on is that VBScript is not logical. VBScript is bitwise. All the so-called logical operators work on numbers, not on Boolean values. `Not`, `And`, `Or`, `XOr`, `Eqv` and `Imp` all convert their arguments to four-byte integers, do the logical operation on each pair of bits in the integers, and return the result. If True is -1 and False is 0 then everything works out, because -1 has all its bits turned on and 0 has all its bits turned off. But if other numbers get in there, all bets are off. This can lead to some strange situations if you're not careful. In VBScript, it is certainly possible for... ```vbnet If blnResult Then ``` and ```vbnet If blnAnswer Then ``` to be both true, but ```vbnet If Blah And Foo Then ``` to be false, if `blnResult` is 1 and `blnAnswer` is 2, for example. ## Best Practices Conditional statements should always take Booleans. Or, in other words, use Booleans as Booleans. Use nothing else as Booleans. Suppose you've got a method that returns a number and you want to do something if it doesn't return zero. Don't do this, even though it does exactly what you want: ```vbnet If intResult Then ``` it's clearer to call it out and make the conditional take a Boolean: ```vbnet If intResult <> 0 Then ``` Conversely, if a value is a Boolean and you know that, there's no need to compare it. When you see: ```vbnet If blnResult = True Then ``` If blnResult can only contain True or False, then you can just say ```vbnet If Blah Then ``` Use the same practice with logical operators. Do not mix-and-match. Either every argument should explicitly be a number and you're doing bitwise comparisons, or every argument is a Boolean. Mixing the two makes the code harder to read and more bug-prone. -------------------------------------------------------------------------------- # Get Curve Length Source: https://developer.rhino3d.com/en/samples/rhinopython/curve-length/ Demonstrates how to get curve length through Python. ```python import rhinoscriptsyntax as rs def CurveLength(): "Calculate the length of one or more curves" # Get the curve objects arrObjects = rs.GetObjects("Select Objects", rs.filter.curve, True, True) if( arrObjects==None ): return rs.UnselectObjects(arrObjects) length = 0.0 count = 0 for object in arrObjects: if rs.IsCurve(object): #Get the curve length length += rs.CurveLength(object) count += 1 if (count>0): print("Curves selected:", count, " Total Length:", length) # Check to see if this file is being executed as the "main" python # script instead of being used as a module by some other python script # This allows us to use the module which ever way we want. if( __name__ == "__main__" ): CurveLength() ``` -------------------------------------------------------------------------------- # Grasshopper data trees and Python Source: https://developer.rhino3d.com/en/guides/rhinopython/grasshopper-datatrees-and-python/ This guide describes how to use data trees in Python. ## Data trees, technically The data tree data structure is a complex data structure that is best kept in Grasshopper realms. It is a .Net class that is part of the Grasshopper SDK and, as such, all its members can be found on the [DataTree class](http://developer.rhino3d.com/api/grasshopper/html/T_Grasshopper_DataTree_1.htm) Grasshopper SDK documentation site. On the implementation side, in Python it can be thought as an object with behavior similar to a `dict` - really, [System.Collections.Generic.SortedList](https://msdn.microsoft.com/en-us/library/system.collections.sortedlist(v=vs.110).aspx) - of `GH_Path`s, or [Grasshopper.Kernel.Data.GH_Path](http://developer.rhino3d.com/api/grasshopper/html/T_Grasshopper_Kernel_Data_GH_Path.htm). For each one of the paths-keys inside, there is an associated [.Net list](https://msdn.microsoft.com/en-us/library/6sh2ey19(v=vs.110).aspx)-value, that is a branch. Items are stored in each list. There is no null-path, but paths can be sparse. Items cannot be sparse, but there can be null-items. Other data structures would also be able to accommodate similar data. In Python, a similar object with better language support would be a list of lists. ```python nested_list = [[0, 1], [2, 3]] ``` However, a list of lists cannot always represent the merging of two datatrees with different dimensional depth in data (example: a datatree with an item at {0;1}[0] and an item at {0}[1]). If the data tree is constructed by normal and integral Grasshopper logic, it will have constant dimensional depth, and therefore this problem can be avoided. Alternatively, the data tree with inferior dimension can be 'grafted' to a branch of an upper dimension, and also this way the problem is avoided. ## Coding against the DataTree class This example shows how to iterate through any data tree and explain the content of it. ```python a = [] for i in range(x.BranchCount): branchList = x.Branch(i) branchPath = x.Path(i) for j in range(branchList.Count): s = str(branchPath) + "[" + str(j) + "] " s += type(branchList[j]).__name__ + ": " s += str(branchList[j]) a.append(s) ``` On the opposite side, this example shows how to create a data tree from scratch: ```python import ghpythonlib.treehelpers as th import Rhino layerTree = [list() for _ in layernames] for i in range(len(layernames)): objs = Rhino.RhinoDoc.ActiveDoc.Objects.FindByLayer(layernames[i]) if objs: geoms = [obj.Geometry for obj in objs] layerTree[i].extend(geoms) layerTree = th.list_to_tree(layerTree, source=[0,0]) a = layerTree ``` ## A simpler way, coding against lists of lists As mentioned under the first heading, when possible, it is easier to code against nested lists (lists of lists) in Python, to leverage this more ubiquitous programing paradigm. The `ghpythonlib.treehelpers` module contains functions that transform trees to nested lists, and vice versa. The first two examples can be translated to ```python import ghpythonlib.treehelpers as th x = th.tree_to_list(x) a = [] for i,branch in enumerate(x): for j,item in enumerate(branch): s = str(i) + "[" + str(j) + "] " s += type(item).__name__ + ": " s += str(item) a.append(s) ``` ```python import ghpythonlib.treehelpers as th import Rhino layerTree = [] for i in range(len(layernames)): objs = Rhino.RhinoDoc.ActiveDoc.Objects.FindByLayer(layernames[i]) if objs: geoms = [obj.Geometry for obj in objs] layerTree.append(geoms) layerTree = th.list_to_tree(layerTree, source=[0,0]) a = layerTree ``` ## Complete sample - [datatree_examples.gh](/files/datatree_examples.gh) ## Source & Q&A A copy of the source is posted as [Gist](https://gist.github.com/piac/ef91ac83cb5ee92a1294) and there is also some Q&A on particular usages, such as [with simplified trees](https://gist.github.com/piac/ef91ac83cb5ee92a1294#gistcomment-3763417). -------------------------------------------------------------------------------- # How This Site Works Source: https://developer.rhino3d.com/en/guides/general/how-this-site-works/ A guide to how this very developer document site works. Every time a commit is pushed to [this git repository](https://github.com/mcneel/developer.rhino3d.com)'s `main` branch, a static site-generator called [Hugo](https://gohugo.io/) churns through all the markdown content to generate html for the site. ## Workflow The best way to understand how this site works is to make a change to it. Follow these steps: 1. If you have not already, read the [README's Getting Started](https://github.com/mcneel/developer.rhino3d.com/blob/main/README.md#getting-started) section. This will get you setup building the entire site locally on your computer so you can preview changes before making them live (by committing and pushing). 1. With the site up and running on your localhost, make a change to one of the pages (find a typo...there are many). A good editor for Markdown is the [Visual Studio Code](https://code.visualstudio.com/). Once you save your changes to the .md file, Hugo will automatically refresh the page with your changes. 1. If you are satisfied with your change, use git to commit your change to the GitHub repository (or submit a pull-request for review). 1. Wait a minute or two (If you issued a pull-request, your change won't be live until a git administrator accepts it). 1. On the live [developer.rhino3d.com](http://developer.rhino3d.com), you should see your change. ## Frontmatter The top of each file in the */contents/en* folder - "content" for short - contains metadata called "frontmatter." This frontmatter is in the [toml format](https://toml.io/en/) between the first `+++` and the second `+++`. For example, this guide's source has: ```toml +++ aliases = ["https://developer.rhino3d.com/5/guides/general/how-this-site-works/", "https://developer.rhino3d.com/6/guides/general/how-this-site-works/", "https://developer.rhino3d.com/7/guides/general/how-this-site-works/", "https://developer.rhino3d.com/wip/guides/general/how-this-site-works/"] authors = [ "dan" ] categories = [ "This Site" ] description = "A guide to how this very developer document site works." keywords = [ "authoring", "writing", "editing", "overview" ] languages = [ "Markdown", "Goldmark", "TOML" ] sdk = [ "General" ] title = "How This Site Works" type = "guides" weight = 7 [admin] TODO = "" origin = "" picky_sisters = "" state = "" [included_in] platforms = [ "Windows", "Mac" ] since = 0 [page_options] byline = true toc = true toc_type = "single" +++ ``` This toml metadata determines how this piece of content is organized and presented in lists. Hugo has some default frontmatter entries (like `title = "Something something"`), but the key/value pairs can be customized to our liking. This frontmatter is used - at the time the site is built - to determine how the page renders. ## Markdown Nearly all content on this site uses [Markdown](http://daringfireball.net/projects/markdown/basics) as the base format. We are using the [Goldmark](https://github.com/yuin/goldmark) markdown parser, which is the default parser with Hugo. A complete guide to Markdown is beyond the scope of this guide. For markdown syntax, refer to the [Hugo Markdown Guide](https://www.markdownguide.org/tools/hugo) or use other files on this site as examples. Everything after the frontmatter (after the second `+++`) is the markdown content for the page. Use the [Style Guide](/guides/general/developer-docs-style-guide/) guide as a reference when writing content for this site. ## Layout and Theme Each page on this site is generated by Hugo's templating engine using a layout. These layouts are found in the */layouts/* folder and the theme's */layouts/* folder. Hugo uses a system called "themes" to apply layouts and styles that can be shared between sites. This site - like rhino3d.com - uses the rhino3d.com-theme found in the */themes/* folder. Unless you know what you are doing, you should not need to edit the rhino3d.com-theme. This theme is shared between multiple websites and changes to the theme can impact other sites. If you are in need of a layout or shortcode that is not provided, feel free to add it to the site-specific folders *outside* the theme folder. If you would like to modify a file in the theme, please ask for help first. Hugo is very opinionated about looking up files; it has a [complex lookup order system](https://gohugo.io/templates/lookup-order/). In general, Hugo has a "specific-to-general" preference for looking up assets like layouts (and data). For example, the default layout applied to a page is first determined by whether or not there is a matching layout with the same name as the content's parent folder. For example, this guide is in a subfolder of a folder called *content/en/guides*. Hugo will first look to see if the site's */layouts* folder contains a matching *guides.html* file and - if not - it will then search the *themes/rhino3d.com-theme/layouts/* folder for a file called *guides.html*. If it doesn't find those, it will apply a more general (or default) layout. You can override which layout should be used by typing the layout's title in the `type = ` toml field at the top of the markdown file. ## Types of content There are 4 types of content on this site: 1. [Index Pages](#index-pages) 1. [Guides](#guides) 1. [Samples](#samples) 1. [APIs](#apis) All types of content - with the exception of APIs - begin with toml, which the site uses to categories and sort the content into appropriate areas. ### Index Pages Index pages are interspersed throughout the site. The [Welcome page](/), the [Guides page](/guides), and the [Samples page](/samples), are all examples of Index pages. The source for such pages start with an underscore `_` and use the *list.html* template (rather than the *single.html*). Here is an example of the toml frontmatter for [a index page](/guides/): ```toml +++ aliases = ["https://developer.rhino3d.com/5/guides/", "https://developer.rhino3d.com/6/guides/", "https://developer.rhino3d.com/7/guides/", "https://developer.rhino3d.com/wip/guides/"] description = "All the guides available for developing for Rhino or Grasshopper." title = "Guides" type = "guides" weight = 2 [...] +++ ``` For illustrative purposes, the toml fields for these do the following: * *aliases*: These are used to create redirects. Hugo can stub out a direct from the alias to this location. * *description*: A brief description of the content, used for SEO, mouseovers, tooltips and social media unfurls. * *title*: This is the title of the page. This is the html page title. * *type*: A possible override to the page's template. * *weight*: The relative sort-order of this page in any collection of pages. ### Guides Guides are contained in the */content/en/guides/* directory. This very document you are reading is a Guide. To create a new guide, simply create a new markdown file and place it in the */content/en/guides/* folder or subfolder. The file be in a folder with a descriptive name (using dashes `-`s as the word separator) and contain an *index.md* file with some toml frontmatter. Here is an example of the YAML for this guide: ```toml +++ aliases = ["https://developer.rhino3d.com/5/guides/general/how-this-site-works/", "https://developer.rhino3d.com/6/guides/general/how-this-site-works/", "https://developer.rhino3d.com/7/guides/general/how-this-site-works/", "https://developer.rhino3d.com/wip/guides/general/how-this-site-works/"] authors = [ "dan" ] categories = [ "This Site" ] description = "A guide to how this very developer document site works." keywords = [ "authoring", "writing", "editing", "overview" ] languages = [ "Markdown", "Goldmark", "TOML" ] sdk = [ "General" ] title = "How This Site Works" type = "guides" weight = 7 [...] +++ ``` The toml fields for Guides determine: * *aliases*: These are used to create redirects. Hugo can stub out a direct from the alias to this location. * *authors*: The original - or responsible - author(s). These values are in the *authors.yml* file in *themes/rhino3d.com-theme/data* folder.. * *categories*: The category of the guide (General, Overview, Advanced). * *description*: A brief description of the content, used for SEO, mouseovers, tooltips and social media unfurls. * *keywords*: Keywords related to this guide (un-used, as of yet). * *languages*: The programming languages this guide references. * *sdk*: The Rhino SDK(s) that this guide pertains to. * *title*: This is the title of the guide. This is the html page title. * *type*: The layout html file used by Liquid (found in */layouts/*) on the guide. * *weight*: The relative sort-order of this page in any collection of pages. etc. ### Samples Samples are contained in the `/content/en/samples/` folder. Here is an example of the YAML for [this sample](/samples/cpp/add-a-cone-surface/): ```toml +++ aliases = ["https://developer.rhino3d.com/5/samples/cpp/add-a-cone-surface/", "https://developer.rhino3d.com/6/samples/cpp/add-a-cone-surface/", "https://developer.rhino3d.com/7/samples/cpp/add-a-cone-surface/", "https://developer.rhino3d.com/wip/samples/cpp/add-a-cone-surface/"] authors = [ "dale" ] categories = [ "Adding Objects", "Surfaces" ] description = "Demonstrates how to create a cone using ON_BrepCone." keywords = [ "rhino" ] languages = [ "C/C++" ] sdk = [ "C/C++" ] title = "Add a Cone Surface" type = "samples/cpp" weight = 1 [...] +++ ``` The toml fields here should look familiar to those found in the guides above. ### APIs The [API documentation](/api/) is automatically generated from source-code and cannot be edited "by hand." ## Other Frontmatter Many of the pages, guides, and samples have additional frontmatter such as `TODO` and `origin` yaml field. These fields are used by the site to toggle different page features, such as the presence of a table-of-contents (`toc = `) or whether or not a page is crawled by search-engines `block_webcrawlers`. In addition to those frontmatter entries shown above here are some of the more common additional frontmatter fields: ### Admin The `[admin]` namespace governs fields related to the editorial process used - internally - to draft, edit, and localize the page. * `TODO`: *(string)* A note on what specifically needs to be done next. It shows up in the [Site Admin page](/admin) * `origin`: *(string)* Used if the page is a conversion from a previous page. The origin field contains the URL address of the original source material. It is often used when setting up redirects from older content to the newer content. * `picky_sisters`: *(string)* These are comments made by the Picky Sisters as editorial feedback to the author. * `state`: *(string)* State identifies the part of the process that a document is in. It shows up in the [Site Admin page](/admin) page. The states we use are: * `In Progress`: This page is a work-in-progress, not yet ready for review, let alone translation. * `To Edit`: This page is ready for "Picky Sisters" review and editing, but not yet ready for translation. * `Complete`: This page is "substantially" complete and ready to be translated. It will show up in the [translators' localization status pages](/admin/localization-status/). ### Page Options The [page_options] namespace governs fields with optional page features such as: * `toc`: *(boolean)* Determines whether or not to display the Table of Contents widget. * `byline`: *(boolean)* Set to `true` to display a byline and the date this page was last edited. * `block_webcrawlers`: *(boolean)* This controls whether or not the page is crawled by search-engine robots: it adds the appropriate html to the head to prevent search engine robots from crawling the page. This is frequently used for draft content. If not set, it defaults to `false`, allowing the page to be indexed. ### Included In The `[included_in]` namespace governs fields related to support on different platforms ("Windows", "Mac") and when the feature or subject was introduced: ```toml [included_in] platforms = [ "Windows", "Mac" ] since = 2 until = 6 ``` * `platforms`: *(list of strings)* which operating systems this topic pertains to. If only one is included, a badge is rendered on the guide or sample that calls out this is a "Windows-only" feautre. * `since`: *(integer)* The major version of Rhino this feature or topic was introduced in. If the current version of Rhino matches this number, a *New* badge might be rendered on the page. * `until`: *(integer)* The major version of Rhino this feature or topic was deprecated (removed) in. If the current version of Rhino is greater than this number, we can render a deprecated warning or badge to call-out that this content is obsolete or removed. ### _Build This is a field to specify whether a page is listed as a page on the website - especially when iterating through pages in layouts/templates. The most common use of the `list` field is to set it to `"never"` like this: ```toml [_build] list="never" ``` ## Authors To add a new author to the site, you must do three things: 1. Add an entry to the *themes/rhino3d.com-theme/data/authors.yml* file with the appropriate fields filled in. 1. Duplicate one of the index.md files in the *content/en/authors* folder and use the `name:` value that you added to the *themes/rhino3d.com-theme/data/authors.yml* file in step 1 above as the folder name. 1. Use the author's `name:` value as a string in the `authors = ['your_new_author_name']` toml entries in guides or samples. That's it. ## Related topics * [Rhino Developer Docs Style Guide](/guides/general/developer-docs-style-guide/) -------------------------------------------------------------------------------- # Loading Tool Palettes (Mac) Source: https://developer.rhino3d.com/en/guides/rhinocommon/loading-tool-palettes-mac/ This guide covers how to create and load a tool palette collection from your RhinoCommon plugin in Rhino for Mac. This guide covers information relevant to Rhino 5-7 for Mac. Rhino 8 is the current shipping version. For updated guides see: - [The Rhino UI System](/guides/general/rhino-ui-system/) - [Creating and Deploying Plugin Toolbars](/guides/rhinocommon/create-deploy-plugin-toolbar/) ## Prerequisites This guide presumes that you have a RhinoCommon plugin that has commands that can be run from a tool palette. In Rhino for Windows, this UI is normally stored in an *rui* file that includes the buttons, the icons, and their associated commands. If you do not yet have a plugin, please begin with the [Your First Plugin (Mac)](/guides/rhinocommon/your-first-plugin-mac) guide. ## Overview There are three steps in creating and loading a tool palette collection for your plugin in Rhino for Mac: 1. The first step is to [create (or convert) a tool palette collection](#create-or-convert-a-tool-palette-collection) that calls the appropriate commands - or to convert a Rhino for Windows *.rui* file - to *ToolPaletteCollection.plist* file. 1. The second step is to [add this *.plist* in your plugin project](#add-the-palette-to-your-project) as a resource. 1. The third and final step is to tell Rhino for Mac to [load the tool palette from the appropriate file](#load-the-tool-palette) when your plugin is being loaded. ## Create or Convert A Tool Palette Collection If you are familiar with the [Command Editor](http://docs.mcneel.com/rhino/mac/help/en-us/index.htm#macpreferencesandsettings/commands.htm) in Rhino for Mac, you are already well on your way to understanding how to create a custom tool palette collection for use in your plugin. If not, don't worry: creating a tool palette collection is relatively easy. If you already have an existing *rui* file from Rhino for Windows, this job is even easier: you can [import that *rui* and convert it to a *plist*](#convert-rui-to-plist). ### Creating from Scratch 1. Open Rhino - if it is not already open - and start a new modeling window. 1. Enter the `TestEditToolPaletteCollection` command. (You will need to type the entire command; it will not autocomplete). This launches a developer tool similar to the Command Editor where tool palette collections can be created, organized, and saved to *plist* files... 1. By default, the `TestEditToolPaletteCollection` editor presumes you have a Rhino for Windows *rui* file you would like to convert. A finder window opens where you can navigate to the *rui* file to import. 1. If you do not have a Rhino for Windows *rui* file that you would like to convert, you will need to create your Tool Palette Collection "from scratch." On the finder window, press *Cancel*. An interface much like the [Command Editor](http://docs.mcneel.com/rhino/mac/help/en-us/index.htm#macpreferencesandsettings/commands.htm) window appears. This is where you can create, configure, organize, and save your tool palette collection. 1. Press the *+* (add) button in the *Palette Browser*... ![TestEditToolPaletteCollection](https://developer.rhino3d.com/images/loading-tool-palettes-mac-01.png) 1. An *Untitled* tool palette appears in the *Palette Browser* (upper left). Click on the name of the *Untitled* palette and give your tool palette a name... ![Name Tool Palette](https://developer.rhino3d.com/images/loading-tool-palettes-mac-02.png) 1. In the *Command Button Browser* (the area in the lower-left corner), click the *+* button to *add a new button*. An *Untitled* button should appear. Select it. ![Add a button](https://developer.rhino3d.com/images/loading-tool-palettes-mac-03.png) 1. In the *Button Editor* (area at lower-right), you can configure your button. Add a Text title, some Menu text, some informative tooltip, and - most importantly - the macro or command (from your plugin) that you wish to run when this button is clicked. ![Button Editor](https://developer.rhino3d.com/images/loading-tool-palettes-mac-04.png) 1. You can drag new images onto the button icon displayed in the *Button Editor*. Rhino for Mac prefers PDF icons as they will scale nicely between Retina and non-Retina displays. If you do not have PDF assets for your icons, use 64 x 64 png images. ![Add a PNG](https://developer.rhino3d.com/images/loading-tool-palettes-mac-05.png) 1. You may add as many buttons as you need to the *Command Button Browser*. These are the buttons that can be added to Tool Palettes. 1. With the tool palette you want to add buttons to, drag buttons from the *Command Button Browser* into the *Palette Contents* area (top, center)... ![Add to Palette](https://developer.rhino3d.com/images/loading-tool-palettes-mac-07.png) 1. When you are satisfied with the contents of your tool palette(s), you can save your converted tool palette collection to a *plist* by clicking on the *Save* button in the lower-right corner of the *Command Editor* window. 1. *NOTE*: Should you want to make changes to this tool palette collection, you can always reload the tool palette collection by re-running the `TestEditToolPaletteCollection` command and opening the *plist* file you created. In order to add a menu to a tool palette button you must save and reload the tool palette in order for the menu to show up in the available menus. ### Convert RUI to plist 1. Open Rhino - if it is not already open - and start a new modeling window. 1. Enter the `TestEditToolPaletteCollection` command. (You will need to type the entire command; it will not autocomplete). This launches a developer tool similar to the Command Editor where tool palette collections can be created, organized, and saved to *plist* files... 1. By default, the `TestEditToolPaletteCollection` editor presumes you have a Rhino for Windows *rui* file you would like to convert. A finder window opens where you can navigate to the *rui* file to import. Navigate to the folder containing your *rui*, select it, click *Open*. Rhino for Mac imports this *rui* and uses it as a template. 1. The contents of your *rui* should appear. Notice that there is a *Modified Tool Palette* with the name of your toolbar(s)... ![Imported RUI](https://developer.rhino3d.com/images/loading-tool-palettes-mac-06.png) 1. The buttons with their associated icons should appear in the *Command Button Browser* (the area in the lower-left corner). If you select buttons in this area, you will notice their editable details appear in the *Button Editor* (area at lower-right). 1. You can drag new images onto the button icon displayed in the *Button Editor*. Rhino for Mac prefers PDF icons as they will scale nicely between Retina and non-Retina displays. If you do not have PDF assets for your icons, use 64 x 64 png images. 1. With the tool palette you want to add buttons to, drag buttons from the *Command Button Browser* into the *Palette Contents* area (top, center)... ![Add to Palette](https://developer.rhino3d.com/images/loading-tool-palettes-mac-07.png) 1. When you are satisfied with the contents of your tool palette(s), you can save your converted tool palette collection to a *plist* by clicking on the *Save* button in the lower-right corner of the *Command Editor* window. 1. *NOTE*: Should you want to make changes to this tool palette collection, you can always reload the tool palette collection by re-running the `TestEditToolPaletteCollection` command and opening the *plist* file you created. In order to add a menu to a tool palette button you must save and reload the tool palette in order for the menu to show up in the available menus. ## Add the Palette to your Project Now that you have Tool Palette Collection *plist*, you need to add it to your plugin as a resource. The best practice is to create a folder within your project called *Resources* (or similar) and move your Tool Palette Collection *plist* into to that folder. *NOTE*: You are free to place your *plist* anywhere you think appropriate. 1. Open *Visual Studio for Mac* if you have not done so already and open your plugin project. 1. Right-click your plugin project in the *Solution Explorer* and select *Add* > *New Folder*... 1. Name this folder *Resources* (or similar). 1. Right-click the new *Resources* folder in the *Solution Explorer* and select *Add* > *Add Files...*. 1. Navigate to the *plist* you saved and add it to the plugin project *Resources* folder. When prompted, *Move* the *plist* to the plugin *Resources* project folder. 1. Select your *ToolPaletteCollection.plist* in *Solution Explorer* and open the *Properties* panel. 1. In the *Build* section of *Properties*, in the *Copy the output directory* entry, select *Copy if newer*. ## Load the Tool Palette 1. In order to load the tool palette, you must reference *RhinoMac.dll* and *Rhino.UI.dll*. In *Visual Studio for Mac*, right-click on the project entry in the *Solution Explorer* and select *Tools* > *Edit File*. This opens up the *csproj* file for your project as xml text in the code editor. 1. Find the area of the xml near where *RhinoCommon* is being referenced and add the following entries: ``` \Applications\Rhinoceros.app\Contents\Frameworks\RhCore.framework\Versions\Current\Resources\Rhino.UI.dll False \Applications\Rhinoceros.app\Contents\Frameworks\RhCore.framework\Versions\Current\Resources\RhinoMac.dll False ``` 1. Close the *csproj* that is open in the code editor. Visual Studio for Mac reloads the project. If you check in the *References* section of your project in the *Solution Explorer*, you should see references to *RhinoMac* and *Rhino.UI*. 1. In your `Plugin` class, if you have not done so already, override the `OnLoad` method. 1. Load your tool palette plist from your desired location by calling the `RhinoMac.Runtime.MacPlatformService.LoadToolPaletteCollection` and passing in the full path to your *plist*. For example, if your *plist* is in the *Resources* folder of your *rhp*, use the following example: ```cs protected override Rhino.PlugIns.LoadReturnCode OnLoad (ref string errorMessage) { var pluginPath = System.IO.Path.GetDirectoryName(Assembly.Location); var resourcesPath = System.IO.Path.Combine (pluginPath, "Resources"); var plistPath = System.IO.Path.Combine (resourcesPath, "ToolPalette.plist"); bool didLoad = RhinoMac.Runtime.MacPlatformService.LoadToolPaletteCollection (plistPath); if (!didLoad) System.Diagnostics.Debug.WriteLine("WARNING: Failed to load tool palette."); return base.OnLoad (ref errorMessage); } ``` Your tool palette collection will be loaded and displayed when Rhino loads your plugin. ## Related Topics - [Your First Plugin (Mac)](/guides/rhinocommon/your-first-plugin-mac) - [Command Editor (from Rhino Help)](http://docs.mcneel.com/rhino/mac/help/en-us/index.htm#macpreferencesandsettings/commands.htm) -------------------------------------------------------------------------------- # Migrate your Options, Document Properties and Object Properties Pages Source: https://developer.rhino3d.com/en/guides/cpp/migrate-properties-pages-windows/ This guide walks you through migrating existing Rhino 5, plug-in provided, Options, Document Properties and Object Properties pages to Rhino 6. You can find instructions regarding migrating your Rhino 5 plugin project to Rhino 6 [here](/guides/cpp/migrate-your-plugin-manual-windows). The code used in this document is available on GitHub [here](https://github.com/mcneel/rhino-developer-samples/tree/6/cpp/SampleMigration). ## Migrating `CRhinoPlugIn` derived class The Rhino 5 `CRhinoPlugIn` class includes `AddPagesToObjectPropertiesDialog`, `AddPagesToOptionsDialog` and `AddPagesToDocumentPropertiesDialog` virtual methods which may be overridden when adding custom pages to the Options, Document Properties and Object Properties dialogs. These methods have been modified in Rhino 6 and will require changes to your derived plug-in classes. #### Rhino 5 code ``` void CSamplePropertiesPagesPlugIn::AddPagesToObjectPropertiesDialog( ON_SimpleArray& pages) { pages.Append(&m_properties_page); } void CSamplePropertiesPagesPlugIn::AddPagesToOptionsDialog( HWND hwndParent, ON_SimpleArray& pages) { pages.Append(new COptionsPage()); } void CSamplePropertiesPagesPlugIn::AddPagesToDocumentPropertiesDialog( CRhinoDoc& doc, HWND hwndParent, ON_SimpleArray& pages) { pages.Append(new CDocumentPropertiesPage(doc)); } ``` #### Rhino 6 code ``` void CSamplePropertiesPagesPlugIn::AddPagesToObjectPropertiesDialog( CRhinoPropertiesPanelPageCollection& collection) { collection.Add(&m_properties_page); } void CSamplePropertiesPagesPlugIn::AddPagesToOptionsDialog( CRhinoOptionsPageCollection& collection) { collection.AddPage(new COptionsPage()); } void CSamplePropertiesPagesPlugIn::AddPagesToDocumentPropertiesDialog( CRhinoOptionsPageCollection& collection) { collection.AddPage(new CDocumentPropertiesPage()); } ``` ## Migrating `CRhinoOptionsDialogPage` derived pages The `CRhinoOptionsDialogPage` class is used to add pages to both the Options and Document Properties dialog sections. The following is a description of what has changed in the Rhino 6 SDK and some simple examples of how to migrate your existing Rhino 5 code. ### Virtual method changes Several virtual methods in the `CRhinoOptionsDialogPage` and the `CRhinoStackedDialogPage` base class have changed in Rhino 6. The following is a list of commonly overridden Rhino 5 virtual methods and their Rhino 6 equivalents. ``` // V5 Method virtual const wchar_t* EnglishPageTitle() = 0; // V6 equivalent virtual const wchar_t* EnglishTitle() const = 0; // V5 Method virtual const wchar_t* LocalPageTitle() = 0; // V6 equivalent, returns EnglishTitle() by default virtual const wchar_t* LocalTitle() const; // V5 Method virtual CRhinoCommand::result RunScript( CRhinoDoc& rhino_doc) = 0; // V6 equivalent virtual CRhinoCommand::result RunScript(CRhinoOptionsPageEventArgs& e) = 0; // V5 Method virtual int OnActivate( BOOL bActive); // V6 equivalent, required in V6, called each time the page is displayed virtual void UpdatePage(CRhinoOptionsPageEventArgs& e) = 0; // V5 Method virtual int OnApply(); // V6 equivalent, called when ever the page is hidden virtual bool Apply(CRhinoOptionsPageEventArgs& e); // V5 Method virtual bool ShowDefaultsButton(void); // V6 equivalent, can be used to add a Apply and/or Restore Defaults buttons to the // main dialog. Returns RhinoOptionPageButtons::None by default. virtual RhinoOptionPageButtons ButtonsToDisplay() const; // V5 Method virtual void OnDefaults(void); // V6 equivalent virtual void OnRestoreDefaultsClick(CRhinoOptionsPageEventArgs& e); ``` ### Header file changes 1. Open the H file containing your derived class. 2. Add the following include to your H file: ``` #include "rhinoSdkTMfcPages.h" ``` 3. Find your class declaration, for this example we will use the following: ``` class CDocumentPropertiesPage : public CRhinoOptionsDialogPage { .... }; ``` Replace it with this: ``` class CDocumentPropertiesPage : public TRhinoOptionsPage { ... }; ``` Your class will now use a Rhino provided template class which implements the `IRhinoOptionsPage` and `IRhinoWindow` interfaces. The `IRhinoWindow` interface will wrap the `CDialog` class and provide direct access to window methods for creating, sizing, painting and destruction. You can use any class that is derived from `CDialog` as long as the class constructor takes a resource Id and parent window. The `TRhinoDialogWindow` template always passes a `nullptr` as the parent window. 4. Modify the `CRhinoObjectPropertiesDialogPage` required virtual methods as follows: #### Rhino 5 class ``` class CDocumentPropertiesPage : public CRhinoOptionsDialogPage { DECLARE_DYNAMIC(CDocumentPropertiesPage) public: CDocumentPropertiesPage(CRhinoDoc& doc); // standard constructor virtual ~CDocumentPropertiesPage(); ///////////////////////////////////////////////////////////////////////////// // CRhinoOptionsDialogPage required overrides const wchar_t* EnglishPageTitle() override; const wchar_t* LocalPageTitle() override; CRhinoCommand::result RunScript( CRhinoDoc& rhino_doc) override; //... the rest of your class }; ``` #### Rhino 6 class ``` class CDocumentPropertiesPage : public TRhinoOptionsPage { DECLARE_DYNAMIC(CDocumentPropertiesPage) public: CDocumentPropertiesPage(); // standard constructor virtual ~CDocumentPropertiesPage(); ///////////////////////////////////////////////////////////////////////////// // CRhinoOptionsDialogPage required overrides const wchar_t* EnglishTitle() const override; const wchar_t* LocalTitle() const override; CRhinoCommand::result RunScript(CRhinoOptionsPageEventArgs& e) override; //... the rest of your class }; ``` 5. See the [Virtual method changes](#migrating-crhinooptionsdialogpage-derived-pages) section above for a list of virtual methods that will need to be modified for Rhino 6. ### CPP Implementation file changes 1. Open the CPP file containing your derived class implementation. 2. Replace all references to `CRhinoOptionsDialogPage` with `__base_class` which dereferences to `CDialog` in our case because we are deriving from `TRhinoOptionsPage.` #### Rhino 5 class ``` IMPLEMENT_DYNAMIC(CDocumentPropertiesPage, CRhinoOptionsDialogPage) BEGIN_MESSAGE_MAP(CDocumentPropertiesPage, CRhinoOptionsDialogPage) END_MESSAGE_MAP() ``` #### Rhino 6 class ``` IMPLEMENT_DYNAMIC(CDocumentPropertiesPage, __base_class) BEGIN_MESSAGE_MAP(CDocumentPropertiesPage, __base_class) END_MESSAGE_MAP() ``` 3. Modify your class constructor. #### Rhino 5 class ``` CDocumentPropertiesPage::CDocumentPropertiesPage() : CRhinoOptionsDialogPage(CDocumentPropertiesPage::IDD) { } ``` #### Rhino 6 class The `TRhinoOptionsPage` constructor takes three arguments: 1. Contains the ID number of a dialog-box template resource. 2. Contains the ID number of a icon resource which is not currently used on Windows and may be zero. 3. If true the host will delete this page when the parent dialog window closes. ``` COptionsPage::COptionsPage() : TRhinoOptionsPage(COptionsPage::IDD, 0, true) { } ``` 4. Rename your `EnglishPageTitle` and `LocalPageTitle` methods to `EnglishTitle` and `LocalTitle`respectively and add a `const` decoration to the functions. #### Rhino 5 code ``` const wchar_t* CDocumentPropertiesPage::EnglishPageTitle() { return L"Test Page"; } const wchar_t* CDocumentPropertiesPage::LocalPageTitle() { return EnglishPageTitle(); } ``` #### Rhino 6 code ``` const wchar_t* CDocumentPropertiesPage::EnglishTitle() const { return L"Test Page"; } const wchar_t* CDocumentPropertiesPage::LocalTitle() const { return EnglishTitle(); } ``` ​ 5. Change the `RunScript` override. #### Rhino 5 code ``` CRhinoCommand::result CDocumentPropertiesPage::RunScript(CRhinoDoc& rhino_doc) { return CRhinoCommand::success; } ``` #### Rhino 6 code Replace the `CRhinoDoc&` argument with a `CRhinoOptionsPageEventArgs&`. ``` CRhinoCommand::result CDocumentPropertiesPage::RunScript(CRhinoOptionsPageEventArgs& e) { // Access to the document assoicated with this options page if the page CRhinoDoc* doc = e.Document(); return CRhinoCommand::success; } ``` ​ 6. Change your`OnActivate` method to `UpdatePage`and modify as appropriate. #### Rhino 5 code This Rhino 5 `OnActivate` method initializes the page when it is activated and shown and applies the page changes when the page is deactivated and hidden. ``` int CDocumentPropertiesPage::OnActivate( BOOL bActive) { // Applychanges when the page is deactivated/hidden and // initialize page values when the page is activated/shown. // This allows multiple pages to share settings values // Apply changes when the page is no longer active if (!bActive) return OnApply(); // Initialize the page when activated m_color_button.SetColor(CApp::App().ObjectColor(RhinoApp().ActiveDoc())); return true; } ``` #### Rhino 6 code This Rhino 6 method gets called each time the page is made activated and shown. The Rhino 6 `Apply` method now gets called whenever the page is deactivated and hidden. ``` void CDocumentPropertiesPage::UpdatePage(CRhinoOptionsPageEventArgs& e) { // Initialize the page when activated m_color_button.SetColor(CApp::App().ObjectColor(e.Document())); } ``` ​ 7. Change your `OnApply` method to `Apply` and add the new `CRhinoOptionsPageEventArgs` parameter. Rhino 5 only called the `OnApply` when the dialog was closed, Rhino 6 calls `Apply` whenever the page is hidden. #### Rhino 5 code ``` int CDocumentPropertiesPage::OnApply() { // Set the document specific object color CApp::App().SetObjectColor(m_doc, m_color_button.Color()); return true; } ``` #### Rhino 6 code Renamed to `Apply` and now takes a `CRhinoOptionsPageEventArgs&` parameter. ``` bool CDocumentPropertiesPage::Apply(CRhinoOptionsPageEventArgs& e) { // Set the document specific object color CApp::App().SetObjectColor(e.Document(), m_color_button.Color()); return true; } ``` 8. If your options page supports the "Restore Defaults" button you will need to do the following: #### Rhino 5 code ``` bool CDocumentPropertiesPage::ShowDefaultsButton(void) { // Override and return true to display the "Restore Defaults" button return true; } // Called when the "Restore Defaults" button is clicked void CDocumentPropertiesPage::OnDefaults(void) { // Set the color button to the default application object color m_color_button.SetColor(CApp::DEFULLT_OBJECT_COLOR); } ``` #### Rhino 6 code Rhino 6 supports optionally adding "Restore Defaults" and "Apply" buttons by overriding the `ButtonsToDisplay` method and returning the buttons to include or `RhinoOptionPageButtons::None` to hide all optional buttons. The base class implementation returns `RhinoOptionPageButtons::None` by default. ``` RhinoOptionPageButtons ButtonsToDisplay() const override { // You may use the following to display both the "Restore Defaults" and "Apply" buttons: // return RhinoOptionPageButtons::DefaultButton | RhinoOptionPageButtons::ApplyButton // Just add the "Restore Defaults" button return RhinoOptionPageButtons::DefaultButton; } ``` The `OnDefaults` method was replace by the `OnRestoreDefaultsClick` method and the Apply button calls the `Apply` method. ``` void CDocumentPropertiesPage::OnRestoreDefaultsClick(CRhinoOptionsPageEventArgs& e) { // Get access to the CRhinoDoc associated with this page instance // CRhinoDoc* doc = e.Document(); // Set the color button to the default application object color m_color_button.SetColor(CApp::DEFULLT_OBJECT_COLOR); } ``` ## Migrating `CRhinoObjectPropertiesDialogPage` or `CRhinoObjectPropertiesDialogPageEx` derived pages The `CRhinoObjectPropertiesDialogPage` and `CRhinoObjectPropertiesDialogPageEx` classes are used to add pages to the Rhino object properties panel in Rhino 5. The following is a description of what has changed in the Rhino 6 SDK and some simple examples of how to migrate your existing Rhino 5 code. ### Virtual method changes Several virtual methods in the `CRhinoObjectPropertiesDialogPage` , `CRhinoObjectPropertiesDialogPageEx` and the `CRhinoStackedDialogPage` base class have changed in Rhino 6. The following is a list of commonly overridden Rhino 5 virtual methods and their Rhino 6 equivalents. ``` // V5 Method virtual const wchar_t* EnglishPageTitle() = 0; // V6 equivalent virtual const wchar_t* EnglishTitle() const = 0; // V5 Method virtual const wchar_t* LocalPageTitle() = 0; // V6 equivalent, returns EnglishTitle() by default virtual const wchar_t* LocalTitle() const; // V5 Method virtual CRhinoCommand::result RunScript(ON_SimpleArray& objects) = 0; // V6 equivalent virtual CRhinoCommand::result RunScRunScript(IRhinoPropertiesPanelPageEventArgs& e) = 0; // V5 Method virtual BOOL AddPageToControlBar( const CRhinoObject* obj = NULL) const = 0; // V6 equivalent virtual bool IncludeInNavigationControl(IRhinoPropertiesPanelPageEventArgs& args) const = 0; // V5 Method virtual void InitControls( const CRhinoObject* new_obj = NULL) = 0; // V6 equivalent, called when ever the page is hidden virtual void UpdatePage(IRhinoPropertiesPanelPageEventArgs& e) = 0; // V5 Method virtual page_type PageType() const; // V6 equivalent, can be used to add a Apply and/or Restore Defaults buttons to the // main dialog. Returns RhinoOptionPageButtons::None by default. virtual RhinoPropertiesPanelPageType PageType() const; // V5 Method virtual int SelectedObjectCount() const; const CRhinoObject* GetSelectedObject( int index) const; // V6 equivalen // You can call the following to get the IRhinoPropertiesPanelPageEventArgs associated // with a specific page. IRhinoPropertiesPanelPageEventArgs contains ObjectCount and // ObjectAt methods which provide access to the selected object list. //IRhinoPropertiesPanelPageEventArgs* args = IRhinoPropertiesPanelPageEventArgs::FromPage(this); // V5 Event watcher methods virtual void OnCloseDocument(CRhinoDoc& doc ); virtual void OnNewDocument(CRhinoDoc& doc ); virtual void OnBeginOpenDocument( CRhinoDoc& doc, const wchar_t* filename, BOOL bMerge, BOOL bReference); virtual void OnEndOpenDocument( CRhinoDoc& doc, const wchar_t* filename, BOOL bMerge, BOOL bReference); virtual void OnBeginSaveDocument( CRhinoDoc& doc, const wchar_t* filename, BOOL bExportSelected); virtual void OnEndSaveDocument( CRhinoDoc& doc, const wchar_t* filename, BOOL bExportSelected); virtual void OnDocumentPropertiesChanged( CRhinoDoc& doc); virtual void OnModifyObjectAttributes( CRhinoDoc& doc, CRhinoObject& object, const CRhinoObjectAttributes& old_attributes); virtual void LayerTableEvent( CRhinoEventWatcher::layer_event event, const CRhinoLayerTable& layer_table, int layer_index, const ON_Layer* old_settings); virtual void LightTableEvent( CRhinoEventWatcher::light_event event, const CRhinoLightTable& light_table, int light_index, const ON_Light* old_settings); virtual void MaterialTableEvent( CRhinoEventWatcher::material_event event, const CRhinoMaterialTable& material_table, int material_index, const ON_Material* old_settings); virtual void GroupTableEvent( CRhinoEventWatcher::group_event event, const CRhinoGroupTable& group_table, int group_index, const ON_Group* old_settings); virtual void FontTableEvent( CRhinoEventWatcher::font_event event, const CRhinoFontTable& font_table, int font_index, const ON_Font* old_settings); virtual void DimStyleTableEvent( CRhinoEventWatcher::dimstyle_event event, const CRhinoDimStyleTable& dimstyle_table, int dimstyle_index, const ON_DimStyle* old_settings); virtual void HatchPatternTableEvent( CRhinoEventWatcher::hatchpattern_event event, const CRhinoHatchPatternTable& hatchpattern_table, int hatchpattern_index, const ON_HatchPattern* old_settings); virtual void LinetypeTableEvent( CRhinoEventWatcher::linetype_event event, const CRhinoLinetypeTable& linetype_table, int linetype_index, const ON_Linetype* old_settings); void OnAppSettingsChanged(const CRhinoAppSettings& new_app_settings); void OnUpdateObjectMesh(CRhinoDoc& doc, CRhinoObject& object, ON::mesh_type mesh_type); void InstanceDefinitionTableEvent( CRhinoEventWatcher::idef_event event, const CRhinoInstanceDefinitionTable& idef_table, int idef_index, const ON_InstanceDefinition* old_settings); //V6 equivalent, see the optional IRhinoPropertiesPanelPageEventWatcher interface ``` ### Header file changes 1. Open the H file containing your derived class. 2. Add the following include to your H file: ``` #include "rhinoSdkTMfcPages.h" ``` 3. Find your class declaration, for this example we will use the following: ``` class CPropertiesPage : public CRhinoObjectPropertiesDialogPageEx { .... }; ``` Replace it with this: ``` CPropertiesPage : public TRhinoPropertiesPanelPage { ... }; ``` Your class will now use a Rhino provided template class which implements the `IRhinoPropertiesPanelPage` and `IRhinoWindow` interfaces. The `IRhinoWindow` interface will wrap the `CDialog` class and provide direct access to window methods for creating, sizing, painting and destruction. You can use any class that is derived from `CDialog` as long as the class constructor takes a resource Id and parent window. The `TRhinoDialogWindow` template always passes a `nullptr` as the parent window. 4. Modify the `CRhinoObjectPropertiesDialogPage` required virtual methods as follows: #### Rhino 5 class ``` class CPropertiesPage : public CRhinoObjectPropertiesDialogPageEx { DECLARE_DYNAMIC(CPropertiesPage) public: CPropertiesPage(); // standard constructor virtual ~CPropertiesPage(); ///////////////////////////////////////////////////////////////////////////// // CRhinoObjectPropertiesDialogPage required overrides void InitControls( const CRhinoObject* new_obj = NULL); BOOL AddPageToControlBar( const CRhinoObject* obj = NULL) const; CRhinoCommand::result RunScript( ON_SimpleArray& objects); ///////////////////////////////////////////////////////////////////////////// // CRhinoObjectPropertiesDialogPageEx required overrides HICON Icon(void) const; ///////////////////////////////////////////////////////////////////////////// // CRhinoStackedDialogPag required overrides const wchar_t* EnglishPageTitle(); const wchar_t* LocalPageTitle(); //... the rest of your class }; ``` #### Rhino 6 class ``` class CPropertiesPage : public TRhinoPropertiesPanelPage { DECLARE_DYNAMIC(CPropertiesPage) public: CPropertiesPage(); // standard constructor virtual ~CPropertiesPage(); ///////////////////////////////////////////////////////////////////////////// // CRhinoOptionsDialogPage required overrides void UpdatePage(IRhinoPropertiesPanelPageEventArgs& e) override; bool IncludeInNavigationControl(IRhinoPropertiesPanelPageEventArgs& e) const override; CRhinoCommand::result RunScript(IRhinoPropertiesPanelPageEventArgs& e) override; ///////////////////////////////////////////////////////////////////////////// // CRhinoObjectPropertiesDialogPageEx required overrides // Handled by TRhinoPropertiesPanelPage, you can delete the Icon override //HICON Icon(void) const; ///////////////////////////////////////////////////////////////////////////// // CRhinoStackedDialogPag required overrides const wchar_t* EnglishTitle() const override; const wchar_t* LocalTitle() const override; //... the rest of your class }; ``` 5. See the [Virtual method changes](#migrating-crhinoobjectpropertiesdialogpage-or-crhinoobjectpropertiesdialogpageex-derived-pages) section above for a list of virtual methods that will need to be modified for Rhino 6. ### CPP Implementation file changes 1. Open the CPP file containing your derived class implementation. 2. Replace all references to `CRhinoObjectPropertiesDialogPageEx` or `CRhinoObjectPropertiesDialogPage` with `__base_class` which dereferences to `CDialog` in our case because we are deriving from `TRhinoOptionsPage.` #### Rhino 5 class ``` IMPLEMENT_DYNAMIC(CPropertiesPage, CRhinoObjectPropertiesDialogPageEx) BEGIN_MESSAGE_MAP(CPropertiesPage, CRhinoObjectPropertiesDialogPageEx) END_MESSAGE_MAP() ``` #### Rhino 6 class ``` IMPLEMENT_DYNAMIC(CPropertiesPage, __base_class) BEGIN_MESSAGE_MAP(CPropertiesPage, __base_class) END_MESSAGE_MAP() ``` 3. Modify your class constructor. #### Rhino 5 class ``` CPropertiesPage::CPropertiesPage() : CRhinoObjectPropertiesDialogPageEx(CPropertiesPage::IDD, NULL) , m_icon(NULL) { } ``` #### Rhino 6 class The `TRhinoPropertiesPanelPage` constructor takes three arguments: 1. Contains the ID number of a dialog-box template resource. 2. Contains the ID number of a icon resource. 3. If true the host will delete this page when it is no longer referenced. ``` CPropertiesPage::CPropertiesPage() : TRhinoPropertiesPanelPage(CPropertiesPage::IDD, IDI_PROPERTIES_PAGE, false) { } ``` 4. Rename your `EnglishPageTitle` and `LocalPageTitle` methods to `EnglishTitle` and `LocalTitle`respectively and add a `const` decoration to the functions. #### Rhino 5 code ``` const wchar_t* CPropertiesPage::EnglishPageTitle() { return L"Test Properties Page"; } const wchar_t* CPropertiesPage::LocalPageTitle() { return EnglishPageTitle(); } ``` #### Rhino 6 code ``` const wchar_t* CPropertiesPage::EnglishTitle() const { return L"Test Properties Page"; } const wchar_t* CPropertiesPage::LocalTitle() const { return EnglishTitle(); } ``` 5. Remove the `CRhinoObjectPropertiesDialogPageEx::Icon` override, the icon resource Id is now passed to the base class constructor. 6. Change the `RunScript` override. #### Rhino 5 code ``` CRhinoCommand::result CPropertiesPage::RunScript(ON_SimpleArray& objects) { return CRhinoCommand::success; } ``` #### Rhino 6 code Replace the `CRhinoDoc&` argument with a `CRhinoOptionsPageEventArgs&`. ``` CRhinoCommand::result CPropertiesPage::RunScript(IRhinoPropertiesPanelPageEventArgs& e) { return CRhinoCommand::success; } ``` 7. Rename your `InitControls` method to `UpdatePage` and change the parameters accordingly. #### Rhino 5 code The selected object list is accessed via the `CRhinoObjectPropertiesDialogPage::SelectedObjectCount` and `CRhinoObjectPropertiesDialogPage::GetSelectedObject` methods in Rhino 5. ``` void CPropertiesPage::InitControls( const CRhinoObject* new_obj) { int our_color_objects = 0; CRhinoDoc* doc = nullptr; for (int i = 0, count = SelectedObjectCount(); i < count; i++) { const CRhinoObject* object = GetSelectedObject(i); if (object && doc == NULL) doc = object->Document(); if (IncludeObject(object) && IsOurColor(object)) our_color_objects++; } ON_wString message; message.Format(L"%d Colored objects selected", our_color_objects); m_message_text.SetWindowText(message); if (doc) m_color_swatch.SetColor(CApp::App().ObjectColor(doc)); else m_color_swatch.SetColor(::GetSysColor(COLOR_BTNFACE)); } ``` #### Rhino 6 code The selected object list is accessed via the provided `IRhinoPropertiesPanelPageEventArgs` parameter in Rhino 6. ``` void CPropertiesPage::UpdatePage(IRhinoPropertiesPanelPageEventArgs& e) { int our_color_objects = 0; CRhinoDoc* doc = nullptr; for (int i = 0, count = e.ObjectCount(); i < count; i++) { const CRhinoObject* object = e.ObjectAt(i); if (object && doc == NULL) doc = object->Document(); if (IncludeObject(object) && IsOurColor(object)) our_color_objects++; } ON_wString message; message.Format(L"%d Colored objects selected", our_color_objects); m_message_text.SetWindowText(message); if (doc) m_color_swatch.SetColor(CApp::App().ObjectColor(doc)); else m_color_swatch.SetColor(::GetSysColor(COLOR_BTNFACE)); } ``` 8. Rename your `AddPageToControlBar` method to `IncludeInNavigationControl` , change the return type to `bool` and change the parameters accordingly. #### Rhino 5 code The selected object list is accessed via the `CRhinoObjectPropertiesDialogPage::SelectedObjectCount` and `CRhinoObjectPropertiesDialogPage::GetSelectedObject` methods in Rhino 5. ``` BOOL CPropertiesPage::AddPageToControlBar( const CRhinoObject* obj) const { // Only care if one or more meshable object is selected for (int i = 0, count = SelectedObjectCount(); i < count; i++) if (IncludeObject(GetSelectedObject(i))) return true; return false; } bool CPropertiesPage::IncludeObject(const CRhinoObject* object) const { return object && object->Document() && object->IsMeshable(ON::render_mesh); } ``` #### Rhino 6 code The selected object list is accessed via the provided `IRhinoPropertiesPanelPageEventArgs` parameter in Rhino 6. ``` bool CPropertiesPage::IncludeInNavigationControl(IRhinoPropertiesPanelPageEventArgs& e) const { // Only care if one or more mesh-able object is selected for (int i = 0, count = e.ObjectCount(); i < count; i++) if (IncludeObject(e.ObjectAt(i))) return true; return false; } bool CPropertiesPage::IncludeObject(const CRhinoObject* object) const { return object && object->Document() && object->IsMeshable(ON::render_mesh); } ``` ​ 9. Replace your object modification code with a call to `ModifyPage` and override `OnModifyPage` to preform the actual object modification. #### Rhino 5 code In Rhino 5 you would typically create a hidden test command and call it to modify the selected object list. This is done to provide undo support and trigger the correct update events in the properties panel. ``` class CPropertiesPageCommand : public CRhinoTestCommand { public: CPropertiesPageCommand() : m_by_layer(false) , m_page(nullptr) { } ~CPropertiesPageCommand() { } // Returns a unique UUID for this command. // If you try to use an id that is already being used, then // your command will not work. Use GUIDGEN.EXE to make unique UUID. UUID CommandUUID() { // {E547CD29-920F-4EF9-92EC-3F4AE9D9E619} static const GUID command_id = { 0xe547cd29, 0x920f, 0x4ef9, { 0x92, 0xec, 0x3f, 0x4a, 0xe9, 0xd9, 0xe6, 0x19 } }; return command_id; } // Returns the English command name. const wchar_t* EnglishCommandName() { return L"TestPropertiesPageModifyObjects"; } // Returns the localized command name. const wchar_t* LocalCommandName() const { return L"TestPropertiesPageModifyObjects"; } // Rhino calls RunCommand to run the command. CRhinoCommand::result RunCommand( const CRhinoCommandContext& ); bool m_by_layer; ON_SimpleArraym_object_ids; CPropertiesPage* m_page; CRhinoCommand::result MakeByLayer(ON_SimpleArray& objectList); CRhinoCommand::result MakeObjectColor(ON_SimpleArray& objectList); }; // The one and only CPropertiesPageCommand object. // Do NOT create any other instance of a CPropertiesPageCommand class. static class CPropertiesPageCommand thePropertiesPageCommand; CRhinoCommand::result CPropertiesPageCommand::RunCommand( const CRhinoCommandContext& context ) { if (m_page == nullptr) return CRhinoCommand::failure; int modified = 0; for (int i = 0, count = m_object_ids.Count(); i < count; i++) { const CRhinoObject* object = context.m_doc.LookupObject(m_object_ids[i]); if (object && !object->IsDeleted()) { modified += m_by_layer ? m_page->ToByLayer(object) : m_page->ToObjectColor(object); } } m_object_ids.Empty(); m_page->ModifiedMessage(m_by_layer, modified); m_page = nullptr; return modified > 0 ? CRhinoCommand::success : CRhinoCommand::nothing; } void CPropertiesPage::RunModifyCommand(bool byLayer) { thePropertiesPageCommand.m_object_ids.Empty(); for (int i = 0, count = SelectedObjectCount(); i < count; i++) { const CRhinoObject* object = GetSelectedObject(i); if (!IncludeObject(object)) continue; if (byLayer && object->Attributes().ColorSource() == ON::color_from_layer) continue; if (!byLayer && IsOurColor(object)) continue; thePropertiesPageCommand.m_object_ids.Append(object->Attributes().m_uuid); } if (thePropertiesPageCommand.m_object_ids.Count() < 1) { ::RhinoMessageBox(RhinoApp().MainWnd(), L"Nothing modified!", LocalPageTitle(), MB_OK); return; } thePropertiesPageCommand.m_page = this; thePropertiesPageCommand.m_by_layer = byLayer; ON_wString macro; macro.Format(L"_noecho !_-%s", thePropertiesPageCommand.EnglishCommandName()); RhinoApp().RunScript(macro); } void CPropertiesPage::OnBnClickedButton1() { RunModifyCommand(false); } void CPropertiesPage::OnBnClickedButton2() { RunModifyCommand(true); } int CPropertiesPage::ToByLayer(const CRhinoObject* object) const { if (!IncludeObject(object)) return 0; ON_3dmObjectAttributes attribs(object->Attributes()); if (attribs.ColorSource() == ON::color_from_layer) return 0; attribs.SetColorSource(ON::color_from_layer); return object->Document()->ModifyObjectAttributes(CRhinoObjRef(object), attribs) ? 1 : 0; } int CPropertiesPage::ToObjectColor(const CRhinoObject* object) const { if (!IncludeObject(object) || IsOurColor(object)) return 0; ON_3dmObjectAttributes attribs(object->Attributes()); attribs.SetColorSource(ON::color_from_object); attribs.m_color = CApp::App().ObjectColor(object->Document()); return object->Document()->ModifyObjectAttributes(CRhinoObjRef(object), attribs) ? 1 : 0; } void CPropertiesPage::ModifiedMessage(bool byLayer, int modifiedCount) const { ON_wString message; if (modifiedCount < 1) message = L"Nothing was modified"; else if (byLayer) message.Format(L"%d Objects were modified and are color by layer", modifiedCount); else message.Format(L"%d Objects colors were changed", modifiedCount); ::RhinoMessageBox(RhinoApp().MainWnd(), message, L"Test Object Properties Page", MB_OK); } ``` #### Rhino 6 code In Rhino 6 you call `ModifyPage` on your page host and override `OnModifyPage` to modify the selected objects. `ModifyPage` Will call `OnModifyPage` when it is safe to modify objects. ``` class CPropertiesPageCommand : public CRhinoTestCommand { public: CPropertiesPageCommand() : m_by_layer(false) , m_page(nullptr) { } ~CPropertiesPageCommand() { } // Returns a unique UUID for this command. // If you try to use an id that is already being used, then // your command will not work. Use GUIDGEN.EXE to make unique UUID. UUID CommandUUID() { // {E547CD29-920F-4EF9-92EC-3F4AE9D9E619} static const GUID command_id = { 0xe547cd29, 0x920f, 0x4ef9, { 0x92, 0xec, 0x3f, 0x4a, 0xe9, 0xd9, 0xe6, 0x19 } }; return command_id; } // Returns the English command name. const wchar_t* EnglishCommandName() { return L"TestPropertiesPageModifyObjects"; } // Returns the localized command name. const wchar_t* LocalCommandName() const { return L"TestPropertiesPageModifyObjects"; } // Rhino calls RunCommand to run the command. CRhinoCommand::result RunCommand( const CRhinoCommandContext& ); bool m_by_layer; ON_SimpleArraym_object_ids; CPropertiesPage* m_page; CRhinoCommand::result MakeByLayer(ON_SimpleArray& objectList); CRhinoCommand::result MakeObjectColor(ON_SimpleArray& objectList); }; // The one and only CPropertiesPageCommand object. // Do NOT create any other instance of a CPropertiesPageCommand class. static class CPropertiesPageCommand thePropertiesPageCommand; CRhinoCommand::result CPropertiesPageCommand::RunCommand( const CRhinoCommandContext& context ) { if (m_page == nullptr) return CRhinoCommand::failure; int modified = 0; for (int i = 0, count = m_object_ids.Count(); i < count; i++) { const CRhinoObject* object = context.m_doc.LookupObject(m_object_ids[i]); if (object && !object->IsDeleted()) { modified += m_by_layer ? m_page->ToByLayer(object) : m_page->ToObjectColor(object); } } m_object_ids.Empty(); m_page->ModifiedMessage(m_by_layer, modified); m_page = nullptr; return modified > 0 ? CRhinoCommand::success : CRhinoCommand::nothing; } void CPropertiesPage::RunModifyCommand(bool byLayer) { thePropertiesPageCommand.m_by_layer = byLayer; PropertiesPanelPageHost()->ModifyPage(); } void CPropertiesPage::OnModifyPage(IRhinoPropertiesPanelPageEventArgs& args) { thePropertiesPageCommand.m_object_ids.Empty(); for (int i = 0, count = args.ObjectCount(); i < count; i++) { const CRhinoObject* object = args.ObjectAt(i); if (!IncludeObject(object)) continue; if (thePropertiesPageCommand.m_by_layer && object->Attributes().ColorSource() == ON::color_from_layer) continue; if (!thePropertiesPageCommand.m_by_layer && IsOurColor(object)) continue; thePropertiesPageCommand.m_object_ids.Append(object->Attributes().m_uuid); } if (thePropertiesPageCommand.m_object_ids.Count() < 1) { ::RhinoMessageBox(RhinoApp().MainWnd(), L"Nothing modified!", LocalTitle(), MB_OK); return; } thePropertiesPageCommand.m_page = this; ON_wString macro; macro.Format(L"_noecho !_-%s", thePropertiesPageCommand.EnglishCommandName()); RhinoApp().RunScript(args.DocumentRuntimeSerialNumber(), macro); } void CPropertiesPage::OnBnClickedButton1() { RunModifyCommand(false); } void CPropertiesPage::OnBnClickedButton2() { RunModifyCommand(true); } int CPropertiesPage::ToByLayer(const CRhinoObject* object) const { if (!IncludeObject(object)) return 0; ON_3dmObjectAttributes attribs(object->Attributes()); if (attribs.ColorSource() == ON::color_from_layer) return 0; attribs.SetColorSource(ON::color_from_layer); return object->Document()->ModifyObjectAttributes(CRhinoObjRef(object), attribs) ? 1 : 0; } int CPropertiesPage::ToObjectColor(const CRhinoObject* object) const { if (!IncludeObject(object) || IsOurColor(object)) return 0; ON_3dmObjectAttributes attribs(object->Attributes()); attribs.SetColorSource(ON::color_from_object); attribs.m_color = CApp::App().ObjectColor(object->Document()); return object->Document()->ModifyObjectAttributes(CRhinoObjRef(object), attribs) ? 1 : 0; } void CPropertiesPage::ModifiedMessage(bool byLayer, int modifiedCount) const { ON_wString message; if (modifiedCount < 1) message = L"Nothing was modified"; else if (byLayer) message.Format(L"%d Objects were modified and are color by layer", modifiedCount); else message.Format(L"%d Objects colors were changed", modifiedCount); ::RhinoMessageBox(RhinoApp().MainWnd(), message, L"Test Object Properties Page", MB_OK); } ``` 10. Replace your `PageType` override with the new version that returns a `RhinoPropertiesPanelPageType` instead of a `page_type`. This is an optional override and typically is only overridden to replace system pages such as the light page with a plug-in provided page. #### Rhino 5 code ``` CRhinoObjectPropertiesDialogPageEx::page_type CPropertiesPage::PageType() const { return CRhinoObjectPropertiesDialogPageEx::custom_page; } ``` #### Rhino 6 code ``` RhinoPropertiesPanelPageType CPropertiesPage::PageType() const { return RhinoPropertiesPanelPageType::Custom; } ``` -------------------------------------------------------------------------------- # Python Looping Source: https://developer.rhino3d.com/en/guides/rhinopython/python-looping/ This guide is an overview of looping through Python code. ## Overview Looping allows you to run a group of statements repeatedly. Some loops repeat statements until a condition is *False*; others repeat statements until a condition is *True*. There are also loops that repeat statements a specific number of times. The following looping statements are available in Python: * `for` - Uses a counter or loops through a each item in a list a specified number of times. * `while` - Loops while a condition is True. * Nested loops - Repeats a group of statements for each item in a collection or each element of an array. Loop statements use a very specific syntax. Unlike other languages, Python does not use an end statement for its loop syntax. The initial Loop statement is followed by a colon `:` symbol. Then the next line will be indented by 4 spaces. It is these spaces to the left of the line that is key. ``` for c in range(0, 3): This is the the loop This is a second line and the last line of the for loop This line is not part of the loop. It is the first line in the rest of the script. ..... ``` Each subsequent lone in the loop also needs to be indented by 4 or more spaces. If a line is not indented it is considered outside the loop and will also terminate any additional lines considered in the loop. A common mistake is remove the spaces and therefore prematurely end the loop. ## For Loop You can use *for* statements to run a block of statements a specific number of times. Using Python to loop through each item in any type of list based structure is very easy. For loops, use a counter variable whose value increases or decreases with each repetition of the loop. The following example causes a procedure to execute 4 times. The *for* statement specifies the counter variable `x` and its start and end values. Python will automatically increments the counter (x) variable by 1 after coming to end of the execution block. ```python for x in range(0, 3): print ("We're on loop " + str(x)) ``` Python can use any iterable method as a the for loop counter. In the case above we are using `range()`. Other iterable objects can be lists or a string. You can also create you own iterable objects if needed. Sometimes it is required to increase or decrease the counter variable by the value you specify. In the following example, the counter variable `j` is incremented by 2 each time the loop repeats. When the loop is finished, the total is the sum of 0, 2, 4, 6 and 8. ```python for j in range(0, 10, 2): print ("We're on loop " + str(j)) ``` To decrease the counter variable, use a negative `range` value. You must specify an end value that is less than the start value. In the following example, the counter variable `j` is decreased by 2 each time the loop repeats. When the loop is finished, total is the sum of 10, 8, 6, 4, and 2. ```python for j in range(10, 0, -2): print ("We're on loop " + str(j)) ``` You can exit any *for* statement before the counter reaches its end value by using the `break` statement. Because you usually want to exit only in certain situations, such as when an error occurs, you could also use the `if` statement in the *True* statement block. If the condition is *False*, the loop runs as usual. More information on the `for` loop can be found at the [Python.org For Loops article](https://wiki.python.org/moin/ForLoop). ## While Loop Use the `while` loop to check a condition before each execution of the loop. ```while var1 = 2 while var1 < 32: var1 = var1 * 2 print var1 print ("Exited while loop.") ``` *while* loops are not used as much as *for* loops. But *while* loops are used often in in cases the following way, polling for specific input or a loop that will execute forever until a condition is met: ```python while True: n = raw_input("Please enter 'hello':") if n.strip() == 'hello': break ``` As you can see, this compacts the whole thing into a piece of code managed entirely by the while loop. Having True as a condition ensures that the code runs until it's broken by n.strip() equaling 'hello'. More information on the `while` loop can be found at the [Python.org While Loop article](https://wiki.python.org/moin/WhileLoop). ## Nested Loops Python allows for loops to be nested inside one another. Any type of loop can be nested within any other type of loop. ```python for x in range(0, 100): if x % 2 == 0: print (str(x) + " is an even number.") ``` ## Related Topics - [What are VBScript and RhinoScript?](/guides/rhinoscript/what-are-vbscript-rhinoscript) -------------------------------------------------------------------------------- # VBScript Looping Source: https://developer.rhino3d.com/en/guides/rhinoscript/vbscript-looping/ This guide is an overview of looping through VBScript code. ## Overview Looping allows you to run a group of statements repeatedly. Some loops repeat statements until a condition is *False*; others repeat statements until a condition is *True*. There are also loops that repeat statements a specific number of times. The following looping statements are available in VBScript: - `Do...Loop` - Loops while or until a condition is True. - `While...Wend` - Loops while a condition is True. - `For...Next` - Uses a counter to run statements a specified number of times. - `For Each...Next` - Repeats a group of statements for each item in a collection or each element of an array. ## Do Loops You can use `Do...Loop` statements to run a block of statements an indefinite number of times. The statements are repeated either while a condition is *True* or until a condition becomes *True*. ## Do While Use the `While` keyword to check a condition in a `Do...Loop` statement. You can check the condition before you enter the loop (as shown in the following `ChkFirstWhile` example), or you can check it after the loop has run at least once (as shown in the `ChkLastWhile` example). In the `ChkFirstWhile` procedure, if myNum is set to 9 instead of 20, the statements inside the loop will never run. In the `ChkLastWhile` procedure, the statements inside the loop run only once because the condition is already *False*. ```vbnet Sub ChkFirstWhile() Dim counter, myNum counter = 0 myNum = 20 Do While myNum > 10 myNum = myNum - 1 counter = counter + 1 Loop MsgBox "The loop made " & counter & " repetitions." End Sub Sub ChkLastWhile() Dim counter, myNum counter = 0 myNum = 9 Do myNum = myNum - 1 counter = counter + 1 Loop While myNum > 10 MsgBox "The loop made " & counter & " repetitions." End Sub ``` ## Do Until There are two ways to use the `Until` keyword to check a condition in a `Do...Loop` statement. You can check the condition before you enter the loop (as shown in the following `ChkFirstUntil` example), or you can check it after the loop has run at least once (as shown in the `ChkLastUntil` example). As long as the condition is *False*, the looping occurs. ```vbnet Sub ChkFirstUntil() Dim counter, myNum counter = 0 myNum = 20 Do Until myNum = 10 myNum = myNum - 1 counter = counter + 1 Loop MsgBox "The loop made " & counter & " repetitions." End Sub Sub ChkLastUntil() Dim counter, myNum counter = 0 myNum = 1 Do myNum = myNum + 1 counter = counter + 1 Loop Until myNum = 10 MsgBox "The loop made " & counter & " repetitions." End Sub ``` ## Exiting a Do Loop You can exit a `Do...Loop` by using the `Exit Do` statement. Because you usually want to exit only in certain situations, such as to avoid an endless loop, you should use the `Exit Do` statement in the True statement block of an `If...Then...Else` statement. If the condition is *False*, the loop runs as usual. In the following example, myNum is assigned a value that creates an endless loop. The `If...Then...Else` statement checks for this condition, preventing the endless repetition. ```vbnet Sub ExitExample() Dim counter, myNum counter = 0 myNum = 9 Do Until myNum = 10 myNum = myNum - 1 counter = counter + 1 If myNum < 10 Then Exit Do Loop MsgBox "The loop made " & counter & " repetitions." End Sub Using While...Wend ``` The `While...Wend` statement is provided in VBScript for those who are familiar with its usage. However, because of the lack of flexibility in `While...Wend`, it is recommended that you use `Do...Loop` instead. ## For...Next You can use `For...Next` statements to run a block of statements a specific number of times. For loops, use a counter variable whose value increases or decreases with each repetition of the loop. The following example causes a procedure called `MyProc` to execute 50 times. The `For` statement specifies the counter variable `x` and its start and end values. The `Next` statement increments the counter variable by 1. ```vbnet Sub DoMyProc50Times() Dim x For x = 1 To 50 MyProc Next End Sub ``` Using the `Step` keyword, you can increase or decrease the counter variable by the value you specify. In the following example, the counter variable `j` is incremented by 2 each time the loop repeats. When the loop is finished, the total is the sum of 2, 4, 6, 8, and 10. ```vbnet Sub TwosTotal() Dim j, total For j = 2 To 10 Step 2 total = total + j Next MsgBox "The total is " & total End Sub ``` To decrease the counter variable, use a negative `Step` value. You must specify an end value that is less than the start value. In the following example, the counter variable `myNum` is decreased by 2 each time the loop repeats. When the loop is finished, total is the sum of 16, 14, 12, 10, 8, 6, 4, and 2. ```vbnet Sub NewTotal() Dim myNum, total For myNum = 16 To 2 Step -2 total = total + myNum Next MsgBox "The total is " & total End Sub ``` You can exit any `For...Next` statement before the counter reaches its end value by using the `Exit For` statement. Because you usually want to exit only in certain situations, such as when an error occurs, you should use the `Exit For` statement in the *True* statement block of an `If...Then...Else` statement. If the condition is *False*, the loop runs as usual. ## For Each...Next A `For Each...Next` loop is similar to a `For...Next` loop. Instead of repeating the statements a specified number of times, a `For Each...Next` loop repeats a group of statements for each item in a collection of objects or for each element of an array. This is especially helpful if you don't know how many elements are in a collection. In the following RhinoScript code example, the contents of a document's layer table is printed to the command line. ```vbnet Sub PrintLayerNames Dim l, n 'Create variables n = Rhino.LayerNames For Each l In n Rhino.Print l Next End Sub ``` ## Continue Both C++ and C# have a continue statement that, when used with a For loop, skips the remaining statements of that iteration and moves on to next iteration. There is no continue or continue-like statement in VBScript. But using a `Do While` loop inside of a `For Each` statement, you can achieve the same functionality. For example: ```vbnet For i = 0 To 10 Do If i = 4 Then Exit Do Rhino.Print i Loop While False Next ``` Here is another example... ```vbnet Sub TestContinue Dim arrTests, arrTest arrTests = Array( _ Array(1) _ , Array(1,2,3 ) _ , Array(1,2) _ , Array(1) _ , Array(1,2,3) _ ) For Each arrTest In arrTests Call Rhino.Print("Process: {" & Join(arrTest, ", ") & "}") Do While True ' Continue trick Call Rhino.Print(" Process: " & arrTest(0)) If 0 = UBound(arrTest) Then Exit Do ' Continue Call Rhino.Print(" Process: " & arrTest(1)) If 1 = UBound(arrTest) Then Exit Do ' Continue Call Rhino.Print(" Process: " & arrTest(2)) Exit Do Loop Next End Sub ``` ## Related Topics - [What are VBScript and RhinoScript?](/guides/rhinoscript/what-are-vbscript-rhinoscript) -------------------------------------------------------------------------------- # Developer Docs Style Guide Source: https://developer.rhino3d.com/en/guides/general/developer-docs-style-guide/ This guide serves as an example and quick reference for the syntax and structure of this site. Below are examples of nearly all the available syntax using Markdown, the table-of-contents UI widget, etc. ## Conventions ### Site file naming The naming convention for files - guides, samples, etc - is lowercase with `-` used as spaces. This leads to more consistent and legible URLs. In addition, Google recommends one construct compound URL names with `-` and not underbars (`_`). For example, consider the name of this guide: "Style Guide". The file name for this guide is `developer-docs-style-guide`. Google treats a hyphen as a word separator, but does not treat an underscore that way. Google treats and underscore as a word joiner — so "red_sneakers" is the same as "redsneakers." In general, when considering new file names for *guides*, please imagine you are saying "Guide to _____". This often leads to verbs ending in "-ing", the progressive or continuous verb tense. Obviously, this is not a hard-and-fast rule, but rather a convention. In general, when considering new file names for *samples*, please imagine you are saying "_____ sample". As with guides, this is not a hard-and-fast rule, but rather a general convention. ### Division of content `# Title` become H1 headers and are reserved for the title of the page only. `## Header` become H2 headers and are reserved for major sections within the page. `### Sub Header` become H3 headers and are reserved for sub-sections within a major section. ### Fonts This fonts section needs to be updated. On Windows, this site attempts to use Segoe UI (font size: 16 px, font weight: 400, line height: 1.6) and falls back to Frutiger Linotype, Dejavu Sans, Helvetica Neue, Helvetica, Arial, in that order. On macOS, the site will (almost) certainly use Helvetica Neue or Helvetica (font size: 16 px, font weight: 300, line height: 1.6). The operating system-specific font weight is set in the footer using javascript. ### Paths & Filenames `*Italics*` are used to denote filenames, paths, and file extensions. For example: Navigate to *C:\Program Files\Rhinoceros 5 (64-bit)\Plug-ins*. ### Bold `**Bold**` (strong emphasis) is used in instructions to highlight critical instructions that are very important. Bold should be **used sparingly** as it is often present in headers as natural division of content. ### Spelling & Case The following spelling and case conventions are adopted on this site: * "Plugins" is not hyphenated unless it refers to a place in the Rhino UI where it is hyphenated. * "openNURBS" (not OpenNURBS, nor opennurbs, nor oPeNnURBs) unless it refers to a namespace in code where it is capitalized, or a path where it is not. ### Images & Screenshots Please use image names without spaces (use a hypen - NOT an underscore - for word separation). It is also safest to use lowercase filenames and extensions. When feasible, it is best to use the _.svg_ vector format for images, especially for diagrams. When using bitmap images, the preferred format is _.png_, but any browser-friendly bitmap format will work. When capturing screenshots, consider that many people have high-DPI (aka: "Retina") displays. Please capture all screenshots on a high-DPI display. See the [Text Modifiers > Images section](#images) of this guide for more information on inserting images. ## Headers Headers demarcate major sections of the page, guide, etc. Headers are created like this: ```markdown ## Headers ``` The example above is an H2 header. Creating a header automatically creates an #anchor tag in the generated html. For headers with multiple words, the markdown parser lowercases all the words, removes non-alphanumeric characters and adds dashes for spaces. For example, if we had a header like this: ```markdown ## This Is My Header ``` the resulting html anchor tag would be: ```markdown #this-is-my-header ``` Read more about anchors [here](#heading-anchors). ### Sub Headers Sub Headers demarcate sub-sections a major section underneath a header. Sub Headers are created like this: ```markdown ### Sub Header ``` The example above is an H3 header, which we are calling a "Sub Header." Just like with H2 headers, H3 headers also create an #anchor tag in the generated html. ### Heading Anchors Anchors are auto generated from heading texts. They're formatted to be URL safe. In cases that it's really needed, an additional anchor tag can be specified. #### Formatting In the process of sanitizing the heading text to generate anchors, all characters will be lowercased. Spaces will be replaced by `-` and special character will be removed. For example a heading reading: ```toml ### This & That ``` becomes: ```toml #this--that ``` In pages that contain headings with idential text, the heading ID for the first occurance will be generated as normal. Next occurances of the same heading will have a numeric suffix like `-1`, `-2` and so on. #### Optional Explicit Anchors Optionally, an additional anchor with explicit ID can be specified using [anchor shortcode](#anchor) (does not replace auto generated heading anchors). This can be useful for making sure lagacy links to headings work when migrating from another existing system to Hugo. For example below heading in markdown: ```toml ### Heading Text {id="custom_anchor"} ``` Will generate two anchors: ```toml #heading-text ``` and ```toml #custom_anchor ``` ## Table of Contents The UI-widget to the left of this column is a the Table of Contents (TOC) for this page. If you are authoring a page that requires a TOC, you can generate one automatically by toggling the `toc = true` frontmatter field (see [How This Site Works](/guides/general/how-this-site-works/#page-options) for more information). TOCs are only generated from *H2* and *H3* headers...*H4* (and smaller) headers are ignored by the TOC-enabled templates. For example, to get a Header to show up in the TOC, you would type this: ```markdown ## Cool Header ``` To get a Sub Header to show up in the TOC, you would type this: ```markdown ### Sweet Sub Header ``` ## Structural Elements ### Paragraphs Consecutive lines of text are considered to be one paragraph. You must add a blank line between paragraphs. ### Block Quotes A blockquote is started using the > marker followed by an optional space; all following lines that are also started with the blockquote marker belong to the blockquote. You can use any block-level elements inside a blockquote: ```markdown > This is a sample block quote > > >Nested blockquotes are also possible. ``` Yields: > This is a sample block quote > > >Nested blockquotes are also possible. ### Code Blocks To create a code block, surround the code with three back-ticks, followed by a language abbreviation. For example: ```markdown ```cs ``` ...followed by the code... ```cs public static Rhino.Commands.Result AddCircle(Rhino.RhinoDoc doc) { Rhino.Geometry.Point3d center = new Rhino.Geometry.Point3d(0, 0, 0); const double radius = 10.0; Rhino.Geometry.Circle c = new Rhino.Geometry.Circle(center, radius); if (doc.Objects.AddCircle(c) != Guid.Empty) { doc.Views.Redraw(); return Rhino.Commands.Result.Success; } return Rhino.Commands.Result.Failure; } ``` ...and finally closed by three back-ticks. The abbreviation after the first set of back-ticks is the language code for syntax highlighting. We are using a syntax highlighting plugin called highlight.js. [Many languages](https://highlightjs.org/download/) are supported. The most common language abbreviations used on this site are: - `cs` is C# - `vbnet` is Visual Basic - `python` is Python - `cpp` is C/C++ A complete list of language aliases can be found in [the individual source files](https://github.com/isagalaev/highlight.js/tree/master/src/languages) for highlight.js. ### Horizontal Rules Horizontal rules (lines) are created by using three dashes: ```markdown --- ``` You can an example of one of these right here... ### Lists You can create ordered lists and unordered lists. **Ordered Lists** Ordered lists are created by typing `1.` at the start of a line, like this: ```markdown This is an ordered list: 1. Item one. 1. Item two. 1. Item three. ``` yields: This is an ordered list: 1. Item one. 1. Item two. 1. Item three. Nested ordered lists are also possible. For example: ```markdown This is a nested ordered list: 1. Do item one. 1. Item one subtask one. 1. Item one subtask two. 1. Do item two. 1. Do item three. ``` yields: This is a nested ordered list: 1. Do item one. 1. Item one subtask one. 1. Item one subtask two. 1. Do item two. 1. Do item three. **Unordered Lists** Unordered lists (bullet lists) are created using the dash (-) symbol at the beginning of a line: ```markdown This is a bullet list: - Item one - Item two - Item three ``` yields: This is a bullet list: - Item one - Item two - Item three ### Tables Markdown supports a syntax for creating simple tables. A line starting with a pipe character starts a table row. However, if the pipe characters is immediately followed by a dash (-), a separator line is created. Separator lines are used to split the table header from the table body (and optionally align the table columns) and to split the table body into multiple parts. If the pipe character is followed by an equal sign (=), the tables rows below it are part of the table footer. Here is the syntax for a simple table: ```markdown | A simple | table | | with multiple | lines| ``` yields: | A simple | table | | with multiple | lines| More complex tables can be added like this: ```markdown | Header1 | Header2 | Header3 | |:--------|:-------:|--------:| | cell1 | cell2 | cell3 | | cell4 | cell5 | cell6 | | cell1 | cell2 | cell3 | | cell4 | cell5 | cell6 | | Foot1 | Foot2 | Foot3 | ``` yields: | Header1 | Header2 | Header3 | |:--------|:-------:|--------:| | cell1 | cell2 | cell3 | | cell4 | cell5 | cell6 | | cell1 | cell2 | cell3 | | cell4 | cell5 | cell6 | | Foot1 | Foot2 | Foot3 | ## Text Modifiers ### Emphasis Emphasis (bold and italic) can be added to text by surrounding the text with asterisks: For example: ```markdown I like *my* coffee **bold**. ``` yields: I like *my* coffee **bold**. ### Links **Simple Links** A simple link can be created by surrounding the text with square brackets and the link URL with parentheses: ```markdown This is a [link](http://www.rhino3d.com) to the Rhino 3D homepage. ``` yields: This is a [link](http://www.rhino3d.com) to the Rhino 3D homepage. You can also add title information to the link: ```markdown A [link](http://www.rhino3d.com "Rhino 3D homepage") to the homepage. ``` yields: A [link](http://www.rhino3d.com "Rhino 3D homepage") to the homepage. There is another way to create links which does not interrupt the text flow. The URL and title are defined using a reference name and this reference name is then used in square brackets instead of the link URL: ```markdown A [link][rhino3d homepage] to the homepage. [rhino3d homepage]: http://www.rhino3d.com "Modeling tools for designers" ``` yields: A [link][Rhino3D homepage] to the homepage. [Rhino3D homepage]: http://www.rhino3d.com "Modeling tools for designers" If the link text itself is the reference name, the second set of square brackets can be omitted: ```markdown A link to the [Rhino3D homepage]. [Rhino3D homepage]: http://www.rhino3d.com "Modeling tools for designers" ``` yields: A link to the [Rhino3D homepage]. **Anchor Links** As discussed above, [Headers](#headers) and [Sub Headers](#sub-headers) automatically create anchors in the resulting rendered html output. You can link to any anchor within a page using the hash `#` symbol in a normal link. For example: ```markdown [Sub Headers](#sub-headers) automatically create anchors in the resulting rendered html output ``` yields the sentence fragment shown above. To create new anchors within the site, you can use html inline. For example: ```html ``` was added to the [top of this page](#top). ### Images Images can be created in a similar way to links: just use an exclamation mark before the square brackets. The link text will become the alternative text of the image and the link URL specifies the image source: ```markdown ![pluginlogo](https://developer.rhino3d.com/images/rhinodevlogo148x128.png) ``` yields: ![pluginlogo](https://developer.rhino3d.com/images/rhinodevlogo148x128.png) ### Inline Code Text phrases can be easily marked up as code by surrounding them with back-ticks: ```markdown To write a line to the command line use the `Rhino.RhinoApp.WriteLine` method. ``` yields: To write a line to the command line use the `Rhino.RhinoApp.WriteLine` method. **Footnotes** Footnotes can easily be used in Markdown. Just set a footnote marker (consists of square brackets with a caret and the footnote name inside) in the text and somewhere else the footnote definition (which basically looks like a reference link definition): ```markdown This is a text with a footnote[^1]. [^1]: This is an example of a footnote. ``` yields: This is a text with a footnote[^1]. ### MathJax & LaTeX Markdown has support for LaTeX to PNG rendering via [MathJax](http://www.mathjax.org/). For example: ```LaTeX $$y = {\sqrt{x^2+(x-1)} \over x-3} + \left| 2x \over x^{0.5x} \right|$$ ``` yields: $$y = {\sqrt{x^2+(x-1)} \over x-3} + \left| 2x \over x^{0.5x} \right|$$ See the [MathJax basic tutorial and quick reference on StackExchange](http://meta.math.stackexchange.com/questions/5020/mathjax-basic-tutorial-and-quick-reference). ## Shortcodes [Shortcodes are Hugo's way](https://gohugo.io/content-management/shortcodes/) of inserting a html into your content that generate some special type of formatted output. Hugo comes with some built-in shortcodes for things like inserting [Vimeo](https://gohugo.io/content-management/shortcodes/#vimeo) or [YouTube](https://gohugo.io/content-management/shortcodes/#vimeo) videos, [Instagram](https://gohugo.io/content-management/shortcodes/#instagram) or [Twitter](https://gohugo.io/content-management/shortcodes/#twitter) posts, etc. but we can author our own shortcodes to suit our needs. * Shortcodes are very picky about formatting: even the slightest missing brace can cause the site not to deploy. Don't worry, you won't break the site, you just won't see your change go live until it is fixed. * Shortcodes only work when used in the content of a post, not in the frontmatter. You may be tempted to use them in the frontmatter, but they don't work there. * If you are referencing images, pease make sure to only use files with lowercase names and NO SPACES! So far, we've got the following shortcodes of our own: ### 3dm Use the `3dm` shortcode to embed a 3D view of a 3dm file to a page. ``` {{}} ``` yields: {{< 3dm path="key.3dm" camera=`{"x":0,"y":50,"z":50}` width="80%" height="400px" >}} and ``` {{}} ``` yields: {{< 3dm path="logo.3dm" width="80%" height="400px" background="transparent" settings=`{"controls":false, "camera":{"type":"orthographic", "zoom":8}}` animation=`{"frames":[{"title":"perspective","camera":{"x":10,"y":-30,"z":10},"layers":"*"},{"title":"right","camera":{"x":0,"y":40,"z":5},"layers":"*"},{"title":"front","camera":{"x":40,"y":0,"z":5},"layers":"*"}]}` >}} #### Required Arguments: * `path`: *string*. Path to the 3dm file, for example file residing in the same folder as .md content file could be renference as `path="example.3dm"`. * `width`: *String*. Pixel or Percentage value for width of the element. * `height`: *String*. Pixel or Percentage value for height of the element. #### Optional Arguments: * `camera`: *JSON*. XYZ Position of the camera. Example: `` camera=`{"x":0,"y":50,"z":50}` `` * `background`: *String*. Background color of the viewer. Example: `background="transparent"` * `title`: *String*. Text overlay at the bottom of the element. * `text_color`: *String*. Text color of the title. Example: `text_color="red"` * `settings`: *JSON*. General settings for the viewer. Example: `` settings=`{"controls":false, "camera":{"type":"orthographic", "fov":50, "zoom":8}}` `` * `animation`: *JSON*. Flip trhough set of predefined cameras, titles (TODO: and layers). Has two sub-objects; `settings` and `frames`. Example: `` animation=`{"settings":{"repeat":1, "eachDelay":2, "eachDuration":2}, "frames":[{"title":"left","camera":{"x":10,"y":-30,"z":10},"layers":"*"},{"title":"right","camera":{"x":0,"y":40,"z":5},"layers":"*"},{"title":"front","camera":{"x":40,"y":0,"z":5},"layers":"*"}]}` `` ### Anchor Creates an empty div with the specified ID and can be used as an #anchor anywhere in the page: `` produces an empty div above this line, and can be referenced with a link [#custom_anchor](#custom_anchor) ### Awesome (Font Awesome) [ Font Awesome](https://fontawesome.com/icons?d=gallery) provides a huge library of vector [icons](https://fontawesome.com/icons?d=gallery) that area easy to use with this sortcode: `` produces: You can also specify a size and a color like this: `` produces: ### BeforeAfter You can create image-based before-and-after comparisons with the following shortcode: ``` ``` produces: You can also supply optional captions for the left and right images like this: ``` ``` This shortcode uses the [twentytwenty](https://github.com/zurb/twentytwenty) jQuery widget. ### Call-Out To call attention to a specific area of content on the page, use the call-out shortcode. For example: ``` Don't click that button man...it's dangerous dude. ``` produces: Don't click that button man...it's dangerous dude. The following arguments can be passed to the shortcode to determine the type: - `note` - `abstract` - `info` - `tip` - `success` - `question` - `warning` - `failure` - `danger` - `bug` - `example` - `quote` ### Align ``` Left Wing Centrist Right Wing ``` produces Left Wing Centrist Right Wing ### Center Use the `center` shortcode to center images, text, etc. in the `div`. For example: ``` This text is centered ``` produces: This text is centered You can center images too using the `image` argument like this: ``` ![This image is centered](https://developer.rhino3d.com/en/guides/general/developer-docs-style-guide/logo.png) ``` produces ![This image is centered](https://developer.rhino3d.com/en/guides/general/developer-docs-style-guide/logo.png) ### Figure Use the `figure` shortcode to insert images with captions like this: ``` ``` produces: Developing Software in Public Optionally, you can also use the [gallery](#gallery) and [load-photoswipe](#load-photoswipe) shortcodes to embed figures in a gallery and pop-up an image overlay when users click or tap on the image. ### Image (shortcode) There is a basic `image` shortcode for doing simple sizing (this is an html resizing, not an actual image process - see `imgproc` below)... Here is an example that changes the width of an image ``` ``` produces ### Image Processing (imgproc) Hugo comes with [a number of useful image processing routines like resizing and cropping](https://gohugo.io/content-management/image-processing/). It's important to understand that these are not just css styles, but actual processes the generate entirely new derivative files based on the image you use as your input. This is especially useful for generating thumbnails from large images so that a smaller (in kb) image is delivered in those situations where a larger one would cause slow pageloads. Think of these image processing routines as the equivalent of opening up your file in a program like Photoshop and performing resizing, format, or cropping operations on them. Currently, this shortcode only works on page-bundle images and not on images in the static folder (the main images folder). You will have to use GitHub to upload these into the proper spot. To use the Image Processing routines on the page, use the `imgproc` shortcode. Here is an example that resizes an image: ``` ``` Make sure you close the final brace (not shown above, unfortunately) with a forward slash `/` like this... ``` />}} ``` produces ### Latest Rhino Version Avoid writing the latest Rhino version in text. Instead, the site itself knows which is the current shipping version. So use the following shortcode: `` produces 8 #### Standard size: `` produces: #### Small size: `An inline small new label` produces: An inline small new label. There's a bug here that causes a line-wrap in the middle. Brian doesn't know how to fix it. #### Bullet in list: ``` * Feature description * A feature from a previous version * A feature from a previous version ``` produces: * Feature description * A feature from a previous version * A feature from a previous version ### Open Rhino To open URL in Rhino, use the: ``` ``` to produce: To open Rhino by clicking an image: ``` ![computelogo](https://developer.rhino3d.com/images/rhino_compute_logo.png) ``` ![computelogo](https://developer.rhino3d.com/images/rhino_compute_logo.png) ### Row and Column Use the `` and `` shortcodes together to create responsive flow layouts like this: ``` Row 1: Column 1 Row 1: Column 2 Row 1: Column 3 Row 2: Column 1 Row 2: Column 2 Row 2: Column 3 Row 3: Column 1 Row 3: Column 2 Row 3: Column 3 ``` produces: Row 1: Column 1 Row 1: Column 2 Row 1: Column 3 Row 2: Column 1 Row 2: Column 2 Row 2: Column 3 Row 3: Column 1 Row 3: Column 2 Row 3: Column 3 ### Vimeo and YouTube Videos #### Vimeo We have extended [the built-in vimeo shortcode](https://gohugo.io/content-management/shortcodes/#vimeo) to support Autoplay and looping... For example: ``` ``` produces: #### YouTube Hugo has a [built-in youtube shortcode](https://gohugo.io/content-management/shortcodes/#youtube) that can be used to embed videos like this: ``` ``` produces: ### What's New The What's New shortcode is not yet documented and may be rolled-into the command shortcode. ### Wikipedia Link Use: `` to get: ## Related topics * [How This Site Works](/guides/general/how-this-site-works/) * [Markdown Cheatsheet](https://github.com/adam-p/markdown-here/wiki/Markdown-Cheatsheet) **Footnotes** [^1]: This is an example of a footnote. -------------------------------------------------------------------------------- # Draw a Parametric Curve Source: https://developer.rhino3d.com/en/samples/rhinopython/draw-parametric-curve/ Demonstrates how to draw a parametric curve Python. ```python import rhinoscriptsyntax as rs import math # Something really interesting about this script is # that we are passing a function as a parameter def DrawParametricCurve(parametric_equation): "Create a interpolated curve based on a parametric equation." # Get the minimum parameter t0 = rs.GetReal("Minimum t value", 0.0) if( t0==None ): return # Get the maximum parameter t1 = rs.GetReal("Maximum t value", 1.0) if( t1==None ): return # Get the number of sampling points to interpolate through count = rs.GetInteger("Number of points", 50, 2) if count<1: return arrPoints = list() #Get the first point point = parametric_equation(t0) arrPoints.append(point) #Get the rest of the points for x in range(1,count-2): t = (1.0-(x/count))*t0 + (x/count)*t1 point = parametric_equation(t) arrPoints.append(point) #Get the last point point = parametric_equation(t1) arrPoints.append(point) #Add the curve rs.AddInterpCurve(arrPoints) #Customizable function that solves a parametric equation def __CalculatePoint(t): x = (4*(1-t)+1*t ) * math.sin(3*6.2832*t) y = (4*(1-t)+1*t ) * math.cos(3*6.2832*t) z = 5*t return x,y,z ########################################################################## # Check to see if this file is being executed as the "main" python # script instead of being used as a module by some other python script # This allows us to use the module which ever way we want. if( __name__ == "__main__" ): #Call the function passing another function as a parameter DrawParametricCurve(__CalculatePoint) ``` -------------------------------------------------------------------------------- # Uninstalling Plugins (Mac) Source: https://developer.rhino3d.com/en/guides/rhinocommon/uninstalling-plugins-mac/ This guide explains how to uninstall or remove plugins in Rhino for Mac. This guide presumes you have plugins installed that you would like to remove. ## Overview Rhino for Mac does not (yet) have a Plugin Manager. However, uninstalling plugins is very easy. You simply remove the plugin folder from the *~/Library/Application Support/McNeel/Rhinoceros/MacPlugIns/* folder[^1] and then restart Rhino. ## Step-by-Step 1. *Quit Rhino*, if it is current running. 1. In *Finder*, navigate to the *~/Library/Application Support/McNeel/Rhinoceros/MacPlugIns/* folder. If you [can't find this folder](#user-library), you can do the following... 1. In the *Finder* toolbar, in the *Go* menu, select *Go to Folder...* ![finder_go](https://developer.rhino3d.com/images/uninstalling-plugins-mac-01.png) 1. In the *Go to Folder* dialog, paste the following path: *~/Library/Application Support/McNeel/Rhinoceros/MacPlugIns/* ![paste_path](https://developer.rhino3d.com/images/uninstalling-plugins-mac-02.png) 1. Click *Go*. A Finder window should open showing the contents of the folder. 1. *Remove* (move or delete) the plugin's folder from the *MacPlugIns* folder... ![drag_to_trash](https://developer.rhino3d.com/images/uninstalling-plugins-mac-03.png) 1. *Restart* Rhino. ## Behind the Scenes When Rhino for Mac launches, it searches the contents of the: *~/Library/Application Support/McNeel/Rhinoceros/MacPlugIns/* folder scanning the sub-folders recursively looking for *.rhp* files. When it finds such file, Rhino for Mac attempts to load this plugin. If it cannot find a plugin, it will not load said plugin...it's *that* simple. #### User Library By default, the User Library folder is hidden from view. To make your Library visible in the Finder: 1. In *Finder*, navigate to your *Home* (*~*) folder. You must be in your Home folder for this to work. 1. Press Command+J to bring up the *Finder View* options dialog... ![finder_view_options](https://developer.rhino3d.com/images/finder-view-options.png) 1. Check the *Show Library Folder* check box. Now your Library should show up in the view. You may want to drag this folder to your Favorites area of the Finder sidebar for easy access later. ## Related topics - [Creating a Plugin Installer (Mac)](/guides/rhinocommon/plugin-installers-mac/) **Footnotes** [^1]: Do not confuse this path with */Library/Application Support/McNeel/Rhinoceros/*, which is the system-wide Library location. -------------------------------------------------------------------------------- # VBScript Passing Parameters Source: https://developer.rhino3d.com/en/guides/rhinoscript/vbscript-passing-parameters/ This guide discusses parameter passing in VBScript. ## Overview In VBScript, there are two ways values can be passed: `ByVal` and `ByRef`. Using `ByVal`, we can pass arguments as values whereas with the use of `ByRef`, we can pass arguments are references. This is the obvious bit, but, how do these two differ in practice? ## Passing By Value Consider the following code snippet: ```vbnet Function GetValue(ByVal var) var = var + 1 End Function Dim x: x = 5 'Pass the variable x to the GetValue function ByVal Call GetValue(x) Call Rhino.Print("x = " & CStr(x)) ``` When you run the block of code above, you will get the following output: ```vbnet x = 5 ``` In other words, when we passed the variable `x` (`ByVal`) to the function `GetValue`, we were simply passing a copy of the variable `x`. When `GetValue` executes, var stores a copy of the variable `x` and increments itself by 1. Therefore, because what we are passing to `GetValue` is a copy of `x`, it cannot be modified. ## Passing By Reference Now, let’s look at another way of passing variables: By Reference. Consider the following code snippet: ```vbnet Function GetReference(ByRef var) var = var + 1 End Function Dim x: x = 5 'Pass the variable x to the GetReference function ByRef Call GetReference(x) Call Rhino.Print("x = " & CStr(x)) ``` When you run the block of code above, you will get the following output: ```vbnet x = 6 ``` Variable `x` was increment by 1. But why was `x` incremented? Only var must have incremented by 1, and not `x`? Well, that is the core concept behind passing variables by reference. When the function `GetReference` executes, var becomes a reference of `x`, and therefore, any changes made to var would impact `x`. So if `var` increments itself by 1, so would `x`. If `var` becomes 0 (zero), so would `x`. Let’s look at another example: ```vbnet Function GetReference(ByRef arrArray) ReDim Preserve arrArray(UBound(arrArray)+1) arrArray(UBound(arrArray)) = 2 End Function Dim newArray: newArray = Array(0, 1) Call GetReference(newArray) ``` Will the size of `newArray` increase? Look at: ```vbnet ' new size: 2 Call Rhino.Print(UBound(newArray)) ' new elements: 0, 1, 2 For x = LBound(newArray) To UBound(newArray) Call Rhino.Print(newArray(x)) Next ``` Since `newArray` was passed as a reference to the `GetReference` function, the change made to `arrArray` was reflected upon `newArray` as well. Thus, sizes of both arrays incremented by 1. Is there a way to pass variables `ByRef` and still avoid this? Yes, there is. The answer lies in passing temporary variables... ## ByRef & Temporary Variables The advantage of using this approach is that you can pass temporary variables to n number of functions accepting arguments as reference, without having the base (original) variables modified. This is a good (and recommended) approach, since your original variables stay intact and you do not lose track of them when working with extensive function libraries. Please see a simple demonstration of this approach below: ```vbnet Function GetReference(ByRef var) var = var + 1 End Function Dim x: x = 5 Dim y: y = x Call GetReference(y) ' Returns 5 (x remains unchanged) Call Rhino.Print(x) ' Returns 6 Call Rhino.Print(y) ``` Above, you will notice that as `y` became a temporary variable and was passed to `GetReference`, only it was modified. The variable `x` was unchanged. Thus, it’s recommended to use temporary variables when passing variables as reference. ## Summary Here is a summary: - (ByVal) Arguments do not change when passed by value. - (ByRef) If the function parameter is modified, it will have the same impact on the parameter that was passed by reference. - (ByRef) Because the passed parameters can be changed, we can pass multiple values from functions. - (ByRef) In a large function library, it can be hard to tell where the value was changed and what function the variable was supposed to perform. ---- # Related Topics - [ByRef vs ByVal](/guides/rhinoscript/byref-vs-byval) - [Sub Statement (VBScript) on MSDN](http://msdn.microsoft.com/en-us/library/tt223ahx(v=vs.85).aspx) - [Function Statement (VBScript) on MSDN](http://msdn.microsoft.com/en-us/library/x7hbf8fa(v=vs.85).aspx) -------------------------------------------------------------------------------- # Export Control Points Source: https://developer.rhino3d.com/en/samples/rhinopython/export-control-points/ Demonstrates how to export control points with Python. ```python import rhinoscriptsyntax as rs def ExportControlPoints(): "Export curve's control points to a text file" #pick a curve object object_id = rs.GetObject("Select curve", rs.filter.curve) #get the curve's control points points = rs.CurvePoints(object_id) if not points: return #prompt the user to specify a file name filter = "Text File (*.txt)|*.txt|All files (*.*)|*.*||" filename = rs.SaveFileName("Save Control Points As", filter) if not filename: return file = open( filename, "w" ) for pt in points: file.write( str(pt.X) ) file.write( ", " ) file.write( str(pt.Y) ) file.write( ", " ) file.write( str(pt.Z) ) file.write( "\n" ) file.close() ########################################################################## # Check to see if this file is being executed as the "main" python # script instead of being used as a module by some other python script # This allows us to use the module which ever way we want. if( __name__ == "__main__" ): ExportControlPoints() ``` -------------------------------------------------------------------------------- # Garden Path Sample Source: https://developer.rhino3d.com/en/samples/rhinopython/garden-path/ Demonstrates basic syntax for writing python scripts. ```python import rhinoscriptsyntax as rs import math #Use this to get sine, cosine and radians. import scriptcontext as sc def test(): #Assign variables to the sin and cos functions for use later. Sin = math.sin Cos = math.cos # Acquire information for the garden path # set default values for the distances default_hwidth = 1 default_trad = 1 default_tspace = 1 # look for any previously used values stored in sticky and use those if available. if "GP_WIDTH" in sc.sticky: default_hwidth = sc.sticky["GP_WIDTH"] if "GP_RAD" in sc.sticky: default_trad = sc.sticky["GP_RAD"] if "GP_Space" in sc.sticky: default_tspace = sc.sticky["GP_SPACE"] #get the path direction, length and location from two points entered by the user sp = rs.GetPoint("Start point of path centerline") if sp is None: return ep = rs.GetPoint("End point of path centerline", sp) if ep is None: return #now ask the user what the distances should be, offering the defaults arrived at above hwidth = rs.GetDistance(sp, default_hwidth, second_pt_msg = "Half width of path") if hwidth is None: return #Store the new value in sticky for use next time sc.sticky["GP_WIDTH"] = hwidth trad = rs.GetDistance(sp, default_trad, second_pt_msg = "Radius of tiles") if trad is None: return #Store the new value in sticky for use next time sc.sticky["GP_RAD"] = trad tspace = rs .GetDistance(sp, default_tspace, second_pt_msg = "Distance between tiles") if tspace is None: return #Store the new value in sticky for use next time sc.sticky["GP_SPACE"] = tspace # Calculate angles temp = rs.Angle(sp, ep) pangle = temp[0] plength = rs.Distance(sp, ep) width = hwidth * 2 angp90 = pangle + 90.0 angm90 = pangle - 90.0 # To increase speed, disable redrawing rs.EnableRedraw (False) # Draw the outline of the path #make an empty list pline = [] #add points to the list pline.append(rs.Polar(sp, angm90, hwidth)) pline.append(rs.Polar(pline[0], pangle, plength)) pline.append(rs.Polar(pline[1], angp90, width)) pline.append(rs.Polar(pline[2], pangle + 180.0, plength)) #add the first point back on to the end of the list to close the pline pline.append (pline[0]) #create the polyline from the lst of points. rs.AddPolyline (pline) # Draw the rows of tiles #define a plane - #using the WorldXY plane the reults will always be added parallel to that plane, #regardless of the active plane where the points are picked. plane = rs.WorldXYPlane() pdist = trad + tspace off = 0.0 while (pdist <= plength - trad): #Place one row of tiles given distance along path # and possibly offset it pfirst = rs.Polar(sp, pangle, pdist) pctile = rs.Polar(pfirst, angp90, off) pltile = pctile while (rs.Distance(pfirst, pltile) < hwidth - trad): plane = rs.MovePlane(plane, pltile) rs.AddCircle (plane, trad) pltile = rs.Polar(pltile, angp90, tspace + trad + trad) pltile = rs.Polar(pctile, angm90, tspace + trad + trad) while (rs.Distance(pfirst, pltile) < hwidth - trad): plane = rs.MovePlane(plane, pltile) rs.AddCircle (plane, trad) pltile = rs.Polar(pltile, angm90, tspace + trad + trad) pdist = pdist + ((tspace + trad + trad) * Sin(math.radians(60))) if off == 0.0: off = (tspace + trad + trad) * Cos(math.radians(60)) else: off = 0.0 if __name__ == "__main__": test() ``` The goal of the garden path sample is to develop a script that draws a garden path and fills it with circular concrete tiles. For those familiar with AutoLISP®, the programming language of Autodesk's AutoCAD®, you are probably also familiar with the garden path tutorial. -------------------------------------------------------------------------------- # How to use JSON Source: https://developer.rhino3d.com/en/guides/rhinopython/python-xml-json/ How to format in JSON or XML. [JSON (JavaScript Object Notation)](http://www.json.org/) is an easy to read, flexible text based format that can be used to store and communicate information to other products. It is mainly based on key:value pairs and is web and .NET friendly. There are many libraries and products that support JSON. One of the reasons JSON might be used is to collect data from the Rhino model to be used in other places. Use JSON to store information for a door schedule, or a parts list. A report can be created on the name, size and location of all the bitmaps in a model. A JSON file can have the endpoints of all the lines in a model representing column or beam connection points. JSON files are used in several other places and products. JSON is also easy to display on dynamic webpages. Here is an example of a JSON structure describing a medical office, taken from a set of polylines off a Rhino floorplan. As you will see in the example, the medical space includes 5 rooms and parking, with square footage and pricing for each dedicated space. ```json { "office": {"medical": [ { "room-number": 100, "use": "reception", "sq-ft": 50, "price": 75 }, { "room-number": 101, "use": "waiting", "sq-ft": 250, "price": 75 }, { "room-number": 102, "use": "examination", "sq-ft": 125, "price": 150 }, { "room-number": 103, "use": "examination", "sq-ft": 125, "price": 150 }, { "room-number": 104, "use": "office", "sq-ft": 150, "price": 100 } ], "parking": { "location": "premium", "style": "covered", "price": 750 } } } ``` It is this dictionary setup that works best for Json. For more information on creating and manipulating this type of information in Python see the [Dictionary as a Database Guide](/guides/rhinopython/python-dictionary-database/) ## JSON in Python JSON can store Lists, bools, numbers, tuples and dictionaries. But to be saved into a file, all these structures must be reduced to strings. It is the string version that can be read or written to a file. Python has a JSON module that will help converting the datastructures to JSON strings. Use the `import` function to import the JSON module. ```python import json ``` The JSON module is mainly used to convert the python dictionary above into a JSON string that can be written into a file. ```python json_string = json.dumps(datastore) ``` The JSON module can also take a JSON string and convert it back to a dictionary structure: ```python datastore = json.loads(json_string) ``` While the JSON module will convert strings to Python datatypes, normally the JSON functions are used to read and write directly from JSON files. ## Writing a JSON file Not only can the `json.dumps()` function convert a Python datastructure to a JSON string, but it can also dump a JSON string directly into a file. Here is an example of writing a structure above to a JSON file: ```python #Get the file name for the new file to write filter = "JSON File (*.json)|*.json|All Files (*.*)|*.*||" filename = rs.SaveFileName("Save JSON file as", filter) # If the file name exists, write a JSON string into the file. if filename: # Writing JSON data with open(filename, 'w') as f: json.dump(datastore, f) ``` Remember only a JSON formatted string can be written to the file. For more information about using Rhino.Python to read and write files see the [How to read and write a simple file](/guides/rhinopython/python-reading-writing/) ## Reading JSON Reading in a JSON file uses the `json.load()` function. ```python import rhinoscriptsyntax as rs import json #prompt the user for a file to import filter = "JSON file (*.json)|*.json|All Files (*.*)|*.*||" filename = rs.OpenFileName("Open JSON File", filter) #Read JSON data into the datastore variable if filename: with open(filename, 'r') as f: datastore = json.load(f) #Use the new datastore datastructure print datastore["office"]["parking"]["style"] ``` The result of the code above will result in the same data structure at the top of this guide. For more information about using Rhino.Python to read and write files see the [How to read and write a simple file](/guides/rhinopython/python-reading-writing/) For more details on accessing the information in the dictionary datastructure see, [Dictionary as a Database Guide](/guides/rhinopython/python-dictionary-database/) -------------------------------------------------------------------------------- # VBScript Constants Source: https://developer.rhino3d.com/en/guides/rhinoscript/vbscript-constants/ This brief guide is an overview of VBScript constants. ## Overview A constant is a meaningful name that takes the place of a number or string and never changes. VBScript defines a number of intrinsic constants. You can get information about these intrinsic constants from the VBScript language reference. ## Creating Constants You create user-defined constants in VBScript using the `Const` statement. Using the `Const` statement, you can create string or numeric constants with meaningful names and assign them literal values. For example: ```vbnet Const MyString = "This is my string." Const MyAge = 49 ``` Note that the string literal is enclosed in quotation marks (`" "`). Quotation marks are the most obvious way to differentiate string values from numeric values. You represent Date literals and time literals by enclosing them in number signs (`#`). For example: ```vbnet Const CutoffDate = #11-17-2008# ``` You may want to adopt a naming scheme to differentiate constants from variables. This will prevent you from trying to reassign constant values while your script is running. For example, you might want to use a "vb" or "con" prefix on your constant names, or you might name your constants in all capital letters. Differentiating constants from variables eliminates confusion as you develop more complex scripts. ## Related Topics - [What are VBScript and RhinoScript?](/guides/rhinoscript/what-are-vbscript-rhinoscript) -------------------------------------------------------------------------------- # Canceling a Python script in Rhino Source: https://developer.rhino3d.com/en/guides/rhinopython/python-canceling-scripts/ This guide demonstrates how to cancel a Python script in Rhino. In Rhino 6, when a script is running and it is not waiting for user input, it can be cancelled by pressing the ESC. In Rhino.Python this is done by adding a `scriptcontext.escape_test` test. The following script is not be cancelled by pressing the ESC key. ```python def TightLoopEscapeTest(): for i in range(10000): TightLoopEscapeTest() ``` By `scriptcontext.escape_test` function the loop can now be canceled: ```python import scriptcontext def TimeConsumingTask(): for i in range(10000): # Was escape key pressed? if (scriptcontext.escape_test(False)): print "TimeConsumingTask cancelled." break print i TimeConsumingTask() ``` It might be necessary to press the `ESC` key a couple times to catch the `scriptcontext.escape_test` test in the correct state. ## Related Topics - [What is Python and RhinoScript?](/guides/rhinopython/what-is-rhinopython) - [Your First Python Script in Rhino (Windows)](/guides/rhinopython/your-first-python-script-in-rhino-windows) - [Running Scripts](/guides/rhinopython/python-running-scripts) - [Canceling Scripts](/guides/rhinopython/python-canceling-scripts) - [Editing Scripts](/guides/rhinopython/python-editing-scripts) - [Scripting Options](/guides/rhinopython/python-scripting-options) -------------------------------------------------------------------------------- # Creating a Grasshopper Plug-In Package Source: https://developer.rhino3d.com/en/guides/yak/creating-a-grasshopper-plugin-package/ This is a step by step guide to creating a package for a Grasshopper plug-in (.gha). The [Package Manager](../yak/) was introduced in Rhino 7. It makes it easier to discover, install and manage Grasshopper plug-ins from within Rhino. This guide will describe how to create a package from a Grasshopper plug-in that can be published to the package server. The package manager is cross-platform. The examples below are for Windows. For Mac, replace the path to the Yak CLI tool with "https://developer.rhino3d.com/Applications/Rhino 8.app/Contents/Resources/bin/yak". First, let's assume you have a folder on your computer which contains all the files that you would like to distribute in your package. Something like this... ```commandline C:\Users\Bozo\dist ├── Marmoset.gha ├── icon.png └── misc\ ├── README.md └── LICENSE.txt ``` This is just an example. The only files that matter are Marmoset.gha and icon.png (we'll reference the icon in the manifest.yml file later). We're going to use the Yak CLI tool to create the package, so open up a Command Prompt and navigate to the directory above. ```commandline > cd C:\Users\Bozo\dist ``` Now, we need a `manifest.yml` file! You can easily create your own by studying the [Manifest Reference Guide](../the-package-manifest). Alternatively, you can use the `spec` command to generate a skeleton file. We'll do the latter here. ```commandline > "C:\Program Files\Rhino 8\System\Yak.exe" spec Inspecting content: Marmoset.gha --- name: marmoset version: 1.0.0 authors: - Park Ranger description: > This plug-in does something. I'm not really sure exactly what it's supposed to do, but it does it better than any other plug-in. url: https://example.com Saved to C:\Users\Bozo\dist\manifest.yml ``` The `spec` command takes a look at the current directory and, if present, will glean useful information from the `.gha` assembly and use it generate a `manifest.yml` with name, version, authors, etc. pre-populated. If you haven't added this information, then placeholders will be used. The `spec` command is useful for generating the manifest.yml file initially. Once you have one, keep it with your project and update it for each release. Open the manifest file with your [favourite editor](https://code.visualstudio.com) and fill in the gaps. Afterwards, you should have something that looks a little like this... ```yaml --- name: marmoset version: 1.0.0 authors: - Park Ranger description: > This plug-in does something. I'm not really sure exactly what it's supposed to do, but it does it better than any other plug-in. url: https://example.com icon: icon.png keywords: - mammal ``` Now that we have a manifest file, we can build the package! ```commandline > "C:\Program Files\Rhino 8\System\Yak.exe" build Building package from contents of C:\Users\Bozo\dist Found manifest.yml for package: marmoset (1.0.0) Inspecting content: Marmoset.gha Creating marmoset-1.0.0-rh6_18-any.yak --- name: marmoset version: 1.0.0 authors: - Will Pearson description: > This plug-in does something. I'm not really sure exactly what it's supposed to do, but it does it better than any other plug-in. url: example.com keywords: - mammal - guid:c9beedb9-07ec-4974-a0a2-44670ddb17e4 C:\Users\Bozo\dist\marmoset-1.0.0-rh6_18-any.yak ├── Marmoset.dll ├── Marmoset.gha ├── manifest.yml ├── misc\LICENSE.txt └── misc\README.md ``` The filename includes a "distribution tag" (in this case rh6_18-any). The first part, rh6_18, is inferred from the version of Grasshopper.dll or Rhinocommon.dll that is referenced in the plug-in project. The second part, any, refers to the platform that the plug-in is intended for. To build a platform-specfic package, run the build command again with the --platform <platform> argument, where <platform> can be either win or mac. Currently, if you publish a package with a rh6* distribution tag, it will not be installable for Rhino 7. If your plug-in also works in Rhino 7, please mark it as compatible by copying the .yak file, updating the distribution tag part of the filename (i.e. rh6_18rh7_0) and pushing both to the package server. You might notice your plug-in's GUID lurking in the keywords. More information on how this is used can be found in the "Package Restore in Grasshopper" guide. Congratulations! 🙌 You've just created a package for your Grasshopper plug-in. ## Next Steps Now that you've created a package, [push it to the package server](../pushing-a-package-to-the-server) to make it available in the package manager! ## Related Topics - [Package Manager Guides and Tutorials](/guides/yak/) - [Creating a Rhino Plug-in Package](/guides/yak/creating-a-rhino-plugin-package/) - [Package Restore in Grasshopper](/guides/yak/package-restore-in-grasshopper/) - [Grasshopper: Your First Component (Windows)](/guides/grasshopper/your-first-component-windows/) - [Grasshopper: Your First Component (Mac)](/guides/grasshopper/your-first-component-mac/) -------------------------------------------------------------------------------- # Creating a Multi-targeted Rhino Plug-In Package Source: https://developer.rhino3d.com/en/guides/yak/creating-a-multi-targeted-rhino-plugin-package/ This is a step by step guide to creating a package for a Rhino plug-in (.rhp). The [Package Manager](/guides/yak/) was introduced in Rhino 7. It makes it easier to discover, install and manage Rhino plug-ins from within Rhino. This guide will describe how to create a package from a Rhino plug-in that can be published to the package server. The package manager is cross-platform. The examples below are for Windows. For Mac, replace the path to the Yak CLI tool with "https://developer.rhino3d.com/Applications/Rhino 8.app/Contents/Resources/bin/yak". First, let's assume you have a build directory on your computer which contains all the files that you would like to distribute in your multi-targeted package. Something like below. ```commandline C:\Users\Bozo\dist\ ├───net48 │ │ icon.png │ │ Tamarin.rhp │ └───misc │ License.txt │ README.md └───net7.0 │ icon.png │ Tamarin.rhp └───misc License.txt README.md ``` This is just an example. The only files that matter are Tamarin.rhp and icon.png (we'll reference the icon in the manifest.yml file later). We're going to use the Yak CLI tool to create the package, so open up a Command Prompt and navigate to the directory above. ``` commandline cd C:\Users\Bozo\dist\ ``` Now, we need a `manifest.yml` file! You can easily create your own by studying the [Manifest Reference Guide](../the-package-manifest). Alternatively, you can use the `spec` command to generate a skeleton file. We'll do the latter here. ``` commandline > "C:\Program Files\Rhino 8\System\Yak.exe" spec Inspecting content: Tamarin.rhp --- name: tamarin version: 1.0.0 authors: - Park Ranger description: An example RhinoCommon plug-in url: https://example.com Saved to C:\Users\Bozo\dist\manifest.yml ``` The `spec` command takes a look at the current directory and, if present, will glean useful information from the `.rhp` assembly and use it generate a `manifest.yml` with name, version, description etc. pre-populated. If you haven't added this information, then placeholders will be used. The RhinoCommon plug-in inspector extracts the assembly attributes that you set when creating your plug-in. The `AssemblyInformationalVersion` attribute is used to populate the version field, since this attribute isn't bound to the Microsoft four-digit version spec and can contain a SemVer-compatible version string. The `AssemblyVersion` attribute is used as a fallback. The `spec` command is useful for generating the manifest.yml file initially. Once you have one, keep it with your project and update it for each release. Next, open the manifest file with your [favourite editor](https://code.visualstudio.com) and fill in the gaps. Afterwards, you should have something that looks a little like this... ``` yaml --- name: tamarin version: 1.0.0 authors: - Park Ranger description: > This plug-in does something. I'm not really sure exactly what it's supposed to do, but it does it better than any other plug-in. url: https://example.com icon: icon.png keywords: - something ``` Now that we have a manifest file, we can build the package! ``` commandline > "C:\Program Files\Rhino 8\System\Yak.exe" build Building package from contents of C:\Users\Bozo\dist Found manifest.yml for package: tamarin (1.0.0) Inspecting content: Tamarin.rhp Creating tamarin-1.0.0-rh8_0-any.yak --- name: tamarin version: 1.0.0 authors: - Will Pearson description: > This plug-in does something. I'm not really sure exactly what it's supposed to do, but it does it better than any other plug-in. url: https://example.com keywords: - something - guid:c9beedb9-07ec-4974-a0a2-44670ddb17e4 C:\Users\Bozo\dist\tamarin-1.0.0-rh8_0-any.yak ├── manifest.yml ├── net48/ │ ├── Tamarin.dll │ ├── Tamarin.rhp │ ├── icon.png │ └── misc/ │ ├── License.txt │ └── README.md └── net7.0/ ├── Tamarin.dll ├── Tamarin.rhp ├── icon.png └── misc/ ├── License.txt └── README.md ``` The filename includes a "distribution tag" (in this case rh8_0-any). The first part, rh8_0, is inferred from the version of Rhinocommon.dll or Rhino C++ SDK that is referenced in the plug-in project. The second part, any, refers to the platform that the plug-in is intended for. To build a platform-specfic package, run the build command again with the --platform <platform> argument, where <platform> can be either win or mac. You might notice your plug-in's GUID lurking in the keywords. More information on how this is used can be found in the "Package Restore in Grasshopper" guide. Congratulations! 🙌 You've just created a multi-targeted package for your Rhino plug-in. ## Next Steps Now that you've created a package, [push it to the package server](../pushing-a-package-to-the-server) to make it available in the package manager! ## Related Topics - [Creating a Grasshopper Plug-in Package](/guides/yak/creating-a-grasshopper-plugin-package/) - [RhinoCommon: Your First Plugin (Windows)](/guides/rhinocommon/your-first-plugin-windows) - [RhinoCommon: Your First Plugin (Mac)](/guides/rhinocommon/your-first-plugin-mac) - [Creating your first C/C++ plugin for Rhino](/guides/cpp/your-first-plugin-windows/) -------------------------------------------------------------------------------- # Creating a Rhino Plug-In Package Source: https://developer.rhino3d.com/en/guides/yak/creating-a-rhino-plugin-package/ This is a step by step guide to creating a package for a Rhino plug-in (.rhp). The [Package Manager](/guides/yak/) was introduced in Rhino 7. It makes it easier to discover, install and manage Rhino plug-ins from within Rhino. This guide will describe how to create a package from a Rhino plug-in that can be published to the package server. The package manager is cross-platform. The examples below are for Windows. For Mac, replace the path to the Yak CLI tool with "https://developer.rhino3d.com/Applications/Rhino 8.app/Contents/Resources/bin/yak". First, let's assume you have a folder on your computer which contains all the files that you would like to distribute in your package. Something like this... ```commandline C:\Users\Bozo\dist ├── Tamarin.rhp ├── icon.png └── misc\ ├── README.md └── LICENSE.txt ``` This is just an example. The only files that matter are Tamarin.rhp and icon.png (we'll reference the icon in the manifest.yml file later). We're going to use the Yak CLI tool to create the package, so open up a Command Prompt and navigate to the directory above. ```commandline > cd C:\Users\Bozo\dist ``` Now, we need a `manifest.yml` file! You can easily create your own by studying the [Manifest Reference Guide](../the-package-manifest). Alternatively, you can use the `spec` command to generate a skeleton file. We'll do the latter here. ```commandline > "C:\Program Files\Rhino 8\System\Yak.exe" spec Inspecting content: Tamarin.rhp --- name: tamarin version: 1.0.0 authors: - Park Ranger description: An example RhinoCommon plug-in url: https://example.com Saved to C:\Users\Bozo\dist\manifest.yml ``` The `spec` command takes a look at the current directory and, if present, will glean useful information from the `.rhp` assembly and use it generate a `manifest.yml` with name, version, description etc. pre-populated. If you haven't added this information, then placeholders will be used. The RhinoCommon plug-in inspector extracts the assembly attributes that you set when creating your plug-in. The `AssemblyInformationalVersion` attribute is used to populate the version field, since this attribute isn't bound to the Microsoft four-digit version spec and can contain a SemVer-compatible version string. The `AssemblyVersion` attribute is used as a fallback. The `spec` command is useful for generating the manifest.yml file initially. Once you have one, keep it with your project and update it for each release. Next, open the manifest file with your [favourite editor](https://code.visualstudio.com) and fill in the gaps. Afterwards, you should have something that looks a little like this... ```yaml --- name: tamarin version: 1.0.0 authors: - Park Ranger description: > This plug-in does something. I'm not really sure exactly what it's supposed to do, but it does it better than any other plug-in. url: https://example.com icon: icon.png keywords: - something ``` Now that we have a manifest file, we can build the package! ```commandline > "C:\Program Files\Rhino 8\System\Yak.exe" build Building package from contents of C:\Users\Bozo\dist Found manifest.yml for package: tamarin (1.0.0) Inspecting content: Tamarin.rhp Creating tamarin-1.0.0-rh6_18-any.yak --- name: tamarin version: 1.0.0 authors: - Will Pearson description: > This plug-in does something. I'm not really sure exactly what it's supposed to do, but it does it better than any other plug-in. url: https://example.com keywords: - something - guid:c9beedb9-07ec-4974-a0a2-44670ddb17e4 C:\Users\Bozo\dist\tamarin-1.0.0-rh6_18-any.yak ├── Tamarin.dll ├── Tamarin.rhp ├── manifest.yml └── misc/ ├── LICENSE.txt └── README.md ``` The filename includes a "distribution tag" (in this case rh6_18-any). The first part, rh6_18, is inferred from the version of Rhinocommon.dll or Rhino C++ SDK that is referenced in the plug-in project. The second part, any, refers to the platform that the plug-in is intended for. To build a platform-specfic package, run the build command again with the --platform <platform> argument, where <platform> can be either win or mac. Currently, if you publish a package with a rh6* distribution tag, it will not be installable for Rhino 7. If your plug-in also works in Rhino 7, please mark it as compatible by copying the .yak file, updating the distribution tag part of the filename (i.e. rh6_18rh7_0) and pushing both to the package server. You might notice your plug-in's GUID lurking in the keywords. More information on how this is used can be found in the "Package Restore in Grasshopper" guide. Congratulations! 🙌 You've just created a package for your Rhino plug-in. ## Next Steps Now that you've created a package, [push it to the package server](../pushing-a-package-to-the-server) to make it available in the package manager! ## Related Topics - [Creating a Grasshopper Plug-in Package](/guides/yak/creating-a-grasshopper-plugin-package/) - [RhinoCommon: Your First Plugin (Windows)](/guides/rhinocommon/your-first-plugin-windows) - [RhinoCommon: Your First Plugin (Mac)](/guides/rhinocommon/your-first-plugin-mac) - [Creating your first C/C++ plugin for Rhino](/guides/cpp/your-first-plugin-windows/) -------------------------------------------------------------------------------- # Export Points Source: https://developer.rhino3d.com/en/samples/rhinopython/export-points/ Demonstrates how to export points with Python. ```python # Export the coordinates of point and point cloud objects to a text file. import rhinoscriptsyntax as rs def ExportPoints(): #Get the points to export objectIds = rs.GetObjects("Select Points",rs.filter.point | rs.filter.pointcloud,True,True) if( objectIds==None ): return #Get the filename to create filter = "Text File (*.txt)|*.txt|All Files (*.*)|*.*||" filename = rs.SaveFileName("Save point coordinates as", filter) if( filename==None ): return """ Using a 'with' loop to open the file, we do not need to clean up or close the file when we are done, Python takes care of it. Here, we'll write the points with a line break, otherwise all the points will end up on one line. """ with open(filename, "w")as file: for id in objectIds: #process point clouds if( rs.IsPointCloud(id) ): points = rs.PointCloudPoints(id) for pt in points: # convert the point list to a string, # add a new line character, and write to the file file.write(str(pt)+ "\n") elif( rs.IsPoint(id) ): point = rs.PointCoordinates(id) file.write(str(point)+ "\n") ########################################################################## # Here we check to see if this file is being executed as the "main" python # script instead of being used as a module by some other python script # This allows us to use the module which ever way we want. if( __name__ == '__main__' ): ExportPoints() ``` -------------------------------------------------------------------------------- # Extending Compute with Custom Endpoints Source: https://developer.rhino3d.com/en/guides/compute/custom-endpoints/ Extending Compute with custom endpoints By default Compute exposes a long list of endpoints, designed to expose functionality in RhinoCommon as well as enabling Grasshopper definitions and Python scripts to be solved. It's also possible to extend Compute with your own custom endpoints by registering functions in a Rhino plug-in. Compute handles the serialization. First define a static class in your plug-in and add the new function as a static method (you can add more than one!). ```csharp static class MyCustomComputeFunctions { public static double Add(double, x, double y, double z) { return x+y+z; } public static double AdjustedArea(Curve curve, double adjuster) { var amp = Rhino.Geometry.AreaMassProperties.Compute(curve); return amp.Area * adjuster; } } ``` Then, in the `OnLoad` method of the plug-in's `PlugIn` class, register the function. This code is called once when the plug-in is loaded by Rhino. ```csharp protected override LoadReturnCode OnLoad(ref string errorMessage) { Rhino.Runtime.HostUtils.RegisterComputeEndPoint("Rhino.CustomEndPoint", typeof(MyCustomComputeFunctions)); return base.OnLoad(ref errorMessage); } ``` You will need to install your plug-in in Rhino, both locally for testing and on any servers where Compute runs. Once this is complete, you should be able to open the `/sdk` endpoint in a browser and see the new functions listed at the bottom of the page. Check out the ComputePlugIn sample in the Rhino Developer Samples repository. -------------------------------------------------------------------------------- # Getting Started with Rhino.Compute on Linux Source: https://developer.rhino3d.com/en/guides/compute/compute-linux-getting-started/ A guide to getting started with Rhino.Compute on Linux - This project is part of the Rhino WIP (work in progress) and as such should be considered WIP software. We do not recommend using this for production work. - Things we are still working on: - GH: RhinoCode enabled script components - File IO: Importing and exporting files other than 3dm - 3rd Party Plug-in Management. There is currently no mechanism to load 3rd party plugins, other than what can be installed by the yak-cli and only in cases where the package includes a GHA for Grasshopper to load. - Probably many other things ## Prerequisites - A system capable of running Ubuntu Server 24.04 or AmazonLinux 2023. The steps in this guide should also work for Debian 13. This system can exist locally as a docker container or a VM, or it can be set up in production environments that support Linux instances. - A Core-Hour Billing Token. Please see the [Compute: Licensing & Billing](../core-hour-billing#setting-up-core-hour-billing) to get a token. Your core-hour billing token allows anyone with it to charge your team at will. Do **NOT** share this token with anyone. ## Provision System Rhino.Compute on Linux has been tested to run on Ubuntu Server 24.04 and AmazonLinux 2023. You can run these systems as virtual machines or in docker containers on macOS, Windows, or Linux. We have also tested running Rhino.Compute on Linix on AWS EC2 and Azure instances. ### Containers (Docker) Running rhino-compute in a container is a straightforward way of getting started, but is not recommended for production environments as docker containers lack the systemd service manager which enables the start of the rhino-compute service when the system reboots. At the time of writing, we are typically developing on Ubuntu 24.04 and ensuring that things work on AmazonLinux 2023. There has been no additional effort to ensure these instructions work on Debian 13, other than testing them on this base image. 1. Install Docker - You need either Docker Desktop (macOS, Windows, Linux Desktop) of Docker Engine (Linux, CLI) - Desktop: https://docs.docker.com/get-started/get-docker/ - Engine: https://docs.docker.com/engine/install/ 1. Start container ```bash docker run --rm -it -p 5000:5000 amazonlinux:2023 /bin/bash ``` or ```bash docker run --rm -it -p 5000:5000 ubuntu:noble /bin/bash ``` - `--rm`: remove the container after exit. - `-it`: interactive terminal (stdin and stdout). - `-p 5000:5000`: port mapping [Host Port:Container Port]. Please note, your host OS might already have certain ports reserved. For example, macOS reserves port 5000 for AirPlay. In this case, you should choose a different HOST port: `-p 5001:5000` - `amazonlinux:2023` or `ubuntu:noble`: the base image for the container. ubuntu:noble = Ubuntu 24.04. debian:trixie = Debian 13 - `/bin/bash`: start bash shell 3. Continue to the setup section for your Linux distribution: - [Ubuntu](#ubuntu) - [AmazonLinux](#amazonlinux) ### VMs (Multipass, WSL, etc) Running rhino-compute on a VM is a good way to test out how to run rhino-compute as a service, which is how it is meant to be run in production. #### Multipass Multipass can be used to run **Ubuntu** on macOS, Windows, and Linux host operating systems. 1. Download [Multipass](https://canonical.com/multipass) 2. Create an instance. The recommendation for a lightweight local VM is to provision an instance with 4 cpus, 8gb ram, and 10gb of storage. After launching, you can open a shell right from the multipass interface. 3. Make a note of the IP address of the instance you just created, as that will be necessary for connecting to Rhino.Compute running on this VM. 4. Continue to the [Ubuntu](#ubuntu) setup. #### WSL2 The Windows Subsystem for Linux (WSL2) can be used to run **Ubuntu and AmazonLinux** on a Windows 10 or 11 host operating system. While it should be possible, we have yet to setup and test AmazonLinux on WSL2 1. Install the default Ubuntu image: ```powershell wsl --install ``` 1. Continue to the setup section for your Linux distribution: - [Ubuntu](#ubuntu) - [AmazonLinux](#amazonlinux) ### Production Environment (AWS, Azure, etc) #### AWS EC2 TODO #### Azure TODO ### Other #### Raspberry Pi We have tested running Rhino.Compute on a rpi 400 running Ubuntu. It is highly recommended to use the advanced settings in the [RPi imager](https://www.raspberrypi.com/software/) to add your wifi credentials and a ssh key so that your rpi can connect to the internet. https://ubuntu.com/tutorials/how-to-install-ubuntu-on-your-raspberry-pi#1-overview. After you complete this, you can follow the [Ubuntu](#ubuntu) setup. ## Setup If you are on a VM or Production Environment, switch to root first: `sudo -s` ### Ubuntu 1. Install dependencies: ```bash # dotnet wget https://dotnet.microsoft.com/download/dotnet/scripts/v1/dotnet-install.sh -O dotnet-install.sh chmod +x ./dotnet-install.sh ./dotnet-install.sh --version 9.0.102 --install-dir /usr/share/dotnet ``` 1. Add mcneel-packages to package sources: ```bash # Import the GPG key wget -qO- https://mcneel-packages.s3.amazonaws.com/mcneel-packages.gpg.key | gpg --dearmor -o /usr/share/keyrings/mcneel-archive-keyring.gpg # Add repository echo "deb [signed-by=/usr/share/keyrings/mcneel-archive-keyring.gpg] https://mcneel-packages.s3.amazonaws.com/deb stable main" | tee /etc/apt/sources.list.d/mcneel.list ``` 1. Install rhino-compute: ```bash apt update && apt install -y rhino-compute ``` 1. Set the `RHINO_TOKEN` and the `RHINO_COMPUTE_KEY`: The `RHINO_TOKEN` is your [Core-Hour Billing](../core-hour-billing/#setting-up-core-hour-billing) token. The `RHINO_COMPUTE_KEY` is a key of your choosing. Use this key to authenticate clients calling this server. ```bash cp /etc/rhino-compute/environment.example /etc/rhino-compute/environment nano /etc/rhino-compute/environment # control + x, y, enter to save the file and exit nano ``` 1. Optional: Install yak-cli to install 3rd party packages. ```bash apt install yak-cli ``` Expect that 3rd party plugins will not work at this time. Only packages that are marked "-any" (i.e. `package-0.0.0-\\_any.yak`) will be installed, and only packages with Grasshopper add-ons will be loaded. We are actively working on expanding the support for loading 3rd party plugins on Rhino.Compute on Linux. 6. Continue to the [Run rhino-compute](#run-rhino-compute) section. ### AmazonLinux 1. Install dependencies: ```bash # dotnet wget https://dotnet.microsoft.com/download/dotnet/scripts/v1/dotnet-install.sh -O dotnet-install.sh chmod +x ./dotnet-install.sh ./dotnet-install.sh --version 9.0.102 --install-dir /usr/share/dotnet ``` Install additional dependencies if you are running AmazonLinux in a container: `dnf install -y wget tar gzip nano findutils` 2. Add mcneel-packages to package sources ```bash wget -O /etc/yum.repos.d/mcneel.repo https://mcneel-packages.s3.amazonaws.com/rpm/repos/mcneel-amzn2023.repo ``` 1. Install rhino-compute ```bash dnf install -y rhino-compute ``` 1. Set the `RHINO_TOKEN` and the `RHINO_COMPUTE_KEY` The `RHINO_TOKEN` is your [Core-Hour Billing](../core-hour-billing/#setting-up-core-hour-billing) token. The `RHINO_COMPUTE_KEY` is a key of your choosing. Use this key to authenticate clients calling this server. ```bash cp /etc/rhino-compute/environment.example /etc/rhino-compute/environment nano /etc/rhino-compute/environment # control + x, y, enter to save the file and exit nano ``` 1. *Optional:* Install yak-cli to install 3rd party packages. ```bash dnf install -y yak-cli ``` Expect that 3rd party plugins will not work at this time. Only packages that are marked "-any" (i.e. `package-0.0.0-\\_any.yak`) will be installed, and only packages with Grasshopper add-ons will be loaded. We are actively working on expanding the support for loading 3rd party plugins on Rhino.Compute on Linux. 6. Continue to the [Run rhino-compute](#run-rhino-compute) section. If you are running AmazonLinux in a container, access on the container from the host with `http://localhost:5001` or whichever port you have set in [docker run](#containers-docker) ## Run Rhino.Compute If you had previously run the setup as root (`sudo -s`), please exit root now and return to the default user: `exit`. ### VM or Production Environments - Start the Rhino.Compute service ```bash sudo systemctl start rhino-compute ``` - Stop the Rhino.Compute service ```bash sudo systemctl stop rhino-compute ``` - Enable automatic startup of the Rhino.Compute service on system reboot ```bash sudo systemctl enable rhino-compute ``` - Check the status of the service ```bash sudo systemctl status rhino-compute ``` - Follow real-time logs ```bash sudo journalctl -u rhino-compute -f ``` ### Containers ```bash rhino-compute-start ``` Logs are written to `/var/log/rhino-compute` on the system running Rhino.Compute. ## Solve a Grasshopper definition on Rhino.Compute This section requires the Host computer to have Rhino 8 or Rhino WIP with Hops installed for Grasshopper. Grasshopper should have the IP of the VM or container, as well as the API Key, which is the same as the `RHINO_COMPUTE_KEY` set in the [Setup](#setup). It also helps to have a definition ready to pass to Hops. 1. Open Rhino 8 or WIP, open GH, and navigate to the Grasshopper Solver Settings. 1. In the text field under "Hops - Compute server URLs", enter in the IP and port of your Linux system 1. In the text field next to "API Key" enter in your Core-Hour Billing token aka the `RHINO_COMPUTE_KEY`. 1. Drag the Hops component onto the canvas and reference a definition. If you are following the logs in real-time, you should see your Linux system start to log events related to Rhino.Compute. -------------------------------------------------------------------------------- # GhPython Common Questions and Answers Source: https://developer.rhino3d.com/en/guides/rhinopython/ghpython-question-answer/ Here are the most common questions and answers about GhPython. The GhPython component allows to use both RhinoCommon and RhinoScript from within Grasshopper. Here some Q&As. ### How can I use the rhinoscriptsyntax? By importing RhinoScript, for example by writing: `import rhinoscriptsyntax as rs` ...and then calling some rhinoscript functions... ```python import rhinoscriptsyntax as rs line = rs.AddLine((1, 2, 3), (10, 11, 12)) a = line ``` ### How can I use RhinoCommon? By importing from the Rhino module, for example by writing: `from Rhino.Geometry import Point3d, Line` ...and then assigning some new geometry to the results ```python from Rhino.Geometry import Point3d, Line a = Line(Point3d(1, 2, 3), Point3d(10, 11, 12)) ``` ### What is "ghdoc" and how does it relate to the rhinoscriptsyntax? The `ghdoc` variable is provided by the component for better RhinoScript library support. This library is imperative, and it is build from a set of functions that act on geometrical types through one level of indirection: most of the time, the user does not work with the geometry itself but with an identifier (Guid) of geometry that is present in a document. This is exactly what ghdoc is: it is a reference to the document that the RhinoScript library implicitly targets with all Add__() calls (for example, AddLine()). The scriptcontext module has a doc variable with the currently active document, that can be assigned by you to ghdoc, or RhinoDoc.ActiveDoc, the Rhino document. ### Is RhinoScript use within GhPython less ideal than RhinoCommon? While targeting the `ghdoc` variable, the special Grasshopper document is used, therefore we can use Grasshopper while leaving the Rhino document unchanged. This saves uncountable Undo's, and makes it easy to structure ideas through the Grasshopper definition. This means that both RhinoCommon or RhinoScript are good in practice. ### Is the rhinoscriptsyntax target irrelevant if using solely RhinoCommon classes? Yes. If you create class instances (objects), you will need to create also your own collection objects to store them (mostly lists, trees). You can imagine the `ghdoc` as being an alternative to them, just that you do not access data by index (number), but by Guid. So you can use the RhinoScript or the RhinoCommon libraries independently or mix them. The RhinoScript implementation in Rhino is open-source and is all written in RhinoCommon. Also the `ghdoc` implementation is open-source, and is here. ### Are there RhinoScript and/or RhinoCommon objects which are not recognized as valid Grasshopper geometry? Yes, sure, Grasshopper handles only a portion of all available types. Basically, unhandled types are all the types that do not exists in the 'Params' tab. For example, there is no text dot and no leader. When/if Grasshopper one day will support these types, these calls will be implemented. ### How do I use DataTree's? [Here](http://www.grasshopper3d.com/forum/topics/datatreelistitem-access-from) is a small sample. However, 80% of the times it is not necessary to program for DataTrees, as the logic itself can be applied per-list and Grasshopper handles list-iteration. **If you have more questions, please go to the [Rhino Developers Forum](https://discourse.mcneel.com/c/rhino-developer)** ## Related Topics - [Your first script with Python in Grasshopper](/guides/rhinopython/what-is-rhinopython) - [What is Python and RhinoScript?](/guides/rhinopython/what-is-rhinopython) - [Editing Python in Grasshopper](/guides/rhinopython/python-running-scripts) - [Python Guide for Rhino](/guides/rhinopython/) -------------------------------------------------------------------------------- # Python Operators Source: https://developer.rhino3d.com/en/guides/rhinopython/python-operators/ This guide is an overview of Python operators. ## Overview Python has a full range of operators, including arithmetic operators, comparison operators, concatenation operators, and logical operators. ## Operator Precedence When several operations occur in an expression, each part is evaluated and resolved in a predetermined order called operator precedence. You can use parentheses to override the order of precedence and force some parts of an expression to be evaluated before others. Operations within parentheses are always performed before those outside. Within parentheses, however, standard operator precedence is maintained. When expressions contain operators from more than one category, arithmetic operators are evaluated first, comparison operators are evaluated next, and logical operators are evaluated last. Comparison operators all have equal precedence; that is, they are evaluated in the left-to-right order in which they appear. Arithmetic and logical operators are evaluated in the following order of precedence. ### Arithmetic | Description | | Symbol | | :---------- | ---- | -----: | | Exponentiation | | `**` | | Unary negation | | `-` | | Multiplication | | `*` | | Division | | `/` | | Integer Division | | `//` | | Modulus arithmetic | | `%` | | Addition | | `+` | | Subtraction | | `-` | | String concatenation | | `+` | ### Comparison | Description | | Symbol | | :---------- | ---- | -----: | | Less than | | `<` | | Greater than | | `>` | | Less than or equal to | | `<=` | | Greater than or equal to | | `>=` | | Equality | | `==` | | Inequality | | `!=` | | Object equivalence | | `is` | ### Logical | Description | | Symbol | | :---------- | ---- | -----: | | Logical negation | | `not` | | Logical conjunction | | `and` | | Logical disjunction | | `or` | ## Considerations When multiplication and division occur together in an expression, each operation is evaluated as it occurs from left to right. Likewise, when addition and subtraction occur together in an expression, each operation is evaluated in order of appearance from left to right. The string concatenation (`+`) operator is not an arithmetic operator, but in precedence it falls after all arithmetic operators and before all comparison operators. The `is` operator is an object reference comparison operator. It does not compare objects or their values; it checks only to determine if two object references refer to the same object. ## Related Topics - [What are VBScript and RhinoScript?](/guides/rhinoscript/what-are-vbscript-rhinoscript) -------------------------------------------------------------------------------- # VBScript Operators Source: https://developer.rhino3d.com/en/guides/rhinoscript/vbscript-operators/ This guide is an overview of VBScript operators. ## Overview VBScript has a full range of operators, including arithmetic operators, comparison operators, concatenation operators, and logical operators. ## Operator Precedence When several operations occur in an expression, each part is evaluated and resolved in a predetermined order called operator precedence. You can use parentheses to override the order of precedence and force some parts of an expression to be evaluated before others. Operations within parentheses are always performed before those outside. Within parentheses, however, standard operator precedence is maintained. When expressions contain operators from more than one category, arithmetic operators are evaluated first, comparison operators are evaluated next, and logical operators are evaluated last. Comparison operators all have equal precedence; that is, they are evaluated in the left-to-right order in which they appear. Arithmetic and logical operators are evaluated in the following order of precedence. ### Arithmetic | Description | | Symbol | | :---------- | ---- | -----: | | Exponentiation | | `^` | | Unary negation | | `-` | | Multiplication | | `*` | | Division | | `/` | | Integer division | | `\` | | Modulus arithmetic | | `Mod` | | Addition | | `+` | | Subtraction | | `-` | | String concatenation | | `&` | ### Comparison | Description | | Symbol | | :---------- | ---- | -----: | | Equality | | `=` | | Inequality | | `<>` | | Less than | | `<` | | Greater than | | `>` | | Less than or equal to | | `<=` | | Greater than or equal to | | `>=` | | Object equivalence | | `Is` | ### Logical | Description | | Symbol | | :---------- | ---- | -----: | | Logical negation | | `Not` | | Logical conjunction | | `And` | | Logical disjunction | | `Or` | | Logical exclusion | | `Xor` | | Logical equivalence | | `Eqv` | | Logical implication | | `Imp` | ## Considerations When multiplication and division occur together in an expression, each operation is evaluated as it occurs from left to right. Likewise, when addition and subtraction occur together in an expression, each operation is evaluated in order of appearance from left to right. The string concatenation (`&`) operator is not an arithmetic operator, but in precedence it falls after all arithmetic operators and before all comparison operators. The `Is` operator is an object reference comparison operator. It does not compare objects or their values; it checks only to determine if two object references refer to the same object. ## Related Topics - [What are VBScript and RhinoScript?](/guides/rhinoscript/what-are-vbscript-rhinoscript) -------------------------------------------------------------------------------- # Annotate Curve Endpoints Source: https://developer.rhino3d.com/en/samples/rhinopython/annotate-curve-endpoints/ Demonstrates how to add a NURBS curve to Rhino using Python. ```python # Annotate the endpoints of curve objects import rhinoscriptsyntax as rs def AnnotateCurveEndPoints(): """Annotates the endpoints of curve objects. If the curve is closed then only the starting point is annotated. """ # get the curve object objectId = rs.GetObject("Select curve", rs.filter.curve) if objectId is None: return # Add the first annotation point = rs.CurveStartPoint(objectId) rs.AddPoint(point) rs.AddTextDot(point, point) # Add the second annotation if not rs.IsCurveClosed(objectId): point = rs.CurveEndPoint(objectId) rs.AddPoint(point) rs.AddTextDot(point, point) # Check to see if this file is being executed as the "main" python # script instead of being used as a module by some other python script # This allows us to use the module which ever way we want. if __name__ == "__main__": AnnotateCurveEndPoints() # Call the function defined above ``` -------------------------------------------------------------------------------- # Import Points Source: https://developer.rhino3d.com/en/samples/rhinopython/import-points/ Demonstrates importing points from a file into Rhino using Python. ```python # Import points from a text file import rhinoscriptsyntax as rs def ImportPoints(): #prompt the user for a file to import filter = "Text file (*.txt)|*.txt|All Files (*.*)|*.*||" filename = rs.OpenFileName("Open Point File", filter) if not filename: return #read each line from the file file = open(filename, "r") contents = file.readlines() file.close() # local helper function def __point_from_string(text): items = text.strip("()\n").split(",") x = float(items[0]) y = float(items[1]) z = float(items[2]) return x, y, z contents = [__point_from_string(line) for line in contents] rs.AddPoints(contents) ########################################################################## # Check to see if this file is being executed as the "main" python # script instead of being used as a module by some other python script # This allows us to use the module which ever way we want. if( __name__ == "__main__" ): ImportPoints() ``` -------------------------------------------------------------------------------- # Using Python Dictionary as a database Source: https://developer.rhino3d.com/en/guides/rhinopython/python-dictionary-database/ This guide discusses using Python's Dictionary object to access nested data. ## Overview There are many modern data structures that use a structured key:value pairs to describe objects and the data that is stored within them. A few popular ones are XML, JSON and Amazon S3(Dynamo). The Dictionary object is used to hold a set of data values in the form of (key, value) pairs. The values can be any standard datatype including lists. This article may serve help understand how Python can be used to create and access nested information. ## Creating a Key:Value datastore Using Dictionaries, list and a key:values can be used together to create this datastore. Here is an example of a nested dictionary that stores many different items. In this case, we have a series of polylines representing various rooms for a medical office. Look closely at the bracket and parens that are used. The curly braces `{}` denote the a dictionary. The square brackets `[]` represent a list as a value in the `medical` key. The list in 'medical' actually contains a series of dictionaries for each individual office. ```python datastore = { "office": { "medical": [ { "room-number": 100, "use": "reception", "sq-ft": 50, "price": 75 }, { "room-number": 101, "use": "waiting", "sq-ft": 250, "price": 75 }, { "room-number": 102, "use": "examination", "sq-ft": 125, "price": 150 }, { "room-number": 103, "use": "examination", "sq-ft": 125, "price": 150 }, { "room-number": 104, "use": "office", "sq-ft": 150, "price": 100 } ], "parking": { "location": "premium", "style": "covered", "price": 750 } } } ``` ## Accessing the Datastore There are many ways to access the data in this datastore: ```python print datastore["office"]["parking"] ``` This returns the `parking` dictionary object`{ "location": "premium", "style": "covered", "price": 750 }` Knowing that the `value` for `medical` is a list. Use and index number to access any single book: ```python print datastore["office"]["medical"][0] ``` This returns the dictionary object for room 100, reception. The objects and values in the datastore can also be accessed with the `.get` method. The direct method shown above will return an error if a key does not exist. The `.get` method is a little safer. It will return a value or `None`. This is much safer if you are not sure the key is always present. The `isbn` key is a good example of this. ```python print datastore["office"]["law"] # this produces an error. print datastore["office"].get("law") #This will produce the value of None. ``` A convenient way to efficiently address a portion of the datastore is to assign the portion to a variable. In this case we can assign the list of books to a `spaces` variable: ```python spaces = datastore['office']['medical'] ``` The variable is a reference to the object. Any changes made with `spaces` will also be reflected in the original datastore. Also, because `spaces` contains only the list of spaces in the datastore, it is quite easy to step through the spaces with a `for` statement. In the example below, the for loop is looking for a specific space then updates the price: ```python # Here is a method to find and change a value in the database. for item in spaces: if item.get('use') == "examination" : item['price'] = 175 for item in datastore['office']['medical']: # This loop shows the change is not only in books, but is also in database if item.get('use') == "examination" : print 'The {} rooms now cost {}'.format(item.get("use"), item.get("price")) ``` -------------------------------------------------------------------------------- # Annotate Curve Form Source: https://developer.rhino3d.com/en/samples/rhinopython/annotate-curve-form/ The following sample shows how to create a custom Windows form. ```python """ NOTE: - Reference to RhinoCommmon.dll is added by default - You can specify your script requirements like: # r: [, ] # requirements: [, ] For example this line will ask the runtime to install the listed packages before running the script: # requirements: pytoml, keras You can install specific versions of a package using pip-like package specifiers: # r: pytoml==0.10.2, keras>=2.6.0 - Use env directive to add an environment path to sys.path automatically # env: /path/to/your/site-packages/ """ #! python3 # Imports import Rhino import rhinoscriptsyntax as rs import scriptcontext import System import Rhino.UI import Eto.Drawing as drawing import Eto.Forms as forms # SampleEtoRoomNumber dialog class class SampleEtoCurveAnnotateDialog(forms.Dialog[bool]): # Dialog box Class initializer def __init__(self, curveid): super().__init__() # Initialize dialog box self.Title = 'Sample Eto: Curve Annotation' self.Padding = drawing.Padding(10) self.Resizable = False # Create controls for the dialog self.m_label = forms.Label() self.m_label.Text = 'Curve ID:' self.m_idlabel = forms.Label() self.m_idlabel.Text = curveid self.m_tlabel = forms.Label() self.m_tlabel.Text = 'Curve Label:' self.m_textbox = forms.TextBox() self.m_textbox.Text = 'Start' # Create the default button self.DefaultButton = forms.Button() self.DefaultButton.Text ='OK' self.DefaultButton.Click += self.OnOKButtonClick # Create the abort button self.AbortButton = forms.Button() self.AbortButton.Text ='Cancel' self.AbortButton.Click += self.OnCloseButtonClick # Create a table layout and add all the controls layout = forms.DynamicLayout() layout.Spacing = drawing.Size(5, 5) layout.AddRow(self.m_label, self.m_idlabel) layout.AddRow(None) # spacer layout.AddRow(self.m_tlabel, self.m_textbox) layout.AddRow(None) # spacer layout.AddRow(self.DefaultButton, self.AbortButton) # Set the dialog content self.Content = layout # Start of the class functions # Get the value of the textbox def GetText(self): return self.m_idlabel.Text # Close button click handler def OnCloseButtonClick(self, sender, e): self.m_idlabel.Text = "" self.Close(False) # OK button click handler def OnOKButtonClick(self, sender, e): if self.m_idlabel.Text == "": self.Close(False) else: self.Close(True) ## End of Dialog Class ## # The script that will be using the dialog. def AnnotateCurve(): curveId = rs.GetCurveObject("Select Curve"); if curveId is None: print("no curve selected") else: location = rs.CurveStartPoint(curveId[0]) if location is not None: dialog = SampleEtoCurveAnnotateDialog(str(curveId[0])); rc = dialog.ShowModal(Rhino.UI.RhinoEtoApp.MainWindow) if (rc): text = dialog.m_textbox.Text if len(text) > 0: # create a new text dot at the start of the curve rs.AddTextDot(text, location) ######################################################################### # Check to see if this file is being executed as the "main" python # script instead of being used as a module by some other python script # This allows us to use the module which ever way we want. if __name__ == "__main__": AnnotateCurve() ``` -------------------------------------------------------------------------------- # 3 Script Anatomy Source: https://developer.rhino3d.com/en/guides/rhinopython/primer-101/3-script-anatomy/ ## 3.1 Programming in Rhino Rhinoceros offers various ways of programmatic access. We've already met macros and scripts, but the plot thickens. Please invest a few moments of your life into looking at the diagram below, which you will never be asked to reproduce: The above is a complete breakdown of all developer tools that Rhino has to offer. I'll give you a brief introduction as to what this diagram actually represents and although that is not vital information for our primary goal here ("learning how to script" in case you were wondering), you might as well familiarize yourself with it so you have something to talk about on a first date. At the very core of Rhino are the code libraries. These are essentially collections of procedures and objects which are used to make life easier for the programs that link to them. The most famous one is the openNURBS library which was developed by Robert McNeel & Associates but is completely open source and has been ported by 3rd party programmers to other operating systems such as Unix and Linux. OpenNURBS provides all the required file writing and reading methods as well the basic geometry library. Practically all the 3D applications that support the 3dm file format use the openNURBS library. These code libraries have no knowledge of Rhino at all, they are 'upstream' so to speak. Rhino itself (the red blob) is tightly wrapped around these core libraries, it both implements and extends them. Apart from this obvious behavior, Rhino also adds the possibility of plugins. Whereas most companies provide plugin support for 3rd party developers, McNeel has taken a rather exotic approach which eliminates several big problems. The technical term for this approach is "eating your own dogfood" and it essentially boils down to McNeel programmers using the same tools as 3rd party programmers. Rather than adding code to Rhino itself, McNeel programmers prefer writing a plugin instead. For one, if they screw up the collateral damage is usually fairly minor. It also means that the SDK (Software Development Kit, that which is used to build plugins) is rigorously tested internally and there is no need to maintain and support a separate product. Unfortunately the result of this policy has made plugins so powerful that it is very easy for ill-informed programmers to crash Rhino. This is slightly less true for those developers that use the dotNET SDK to write plugins and it doesn't apply at all to us, scripters. A common proverb in the software industry states that you can easily shoot yourself in the foot with programming, but you can take your whole leg off with C++. Scripters rarely have to deal with anything more severe than a paper-cut. The orange pimples on Rhino's smooth surface are plugins. These days plugins can be written in C++ and all languages that support the DotNET framework (VB.NET, CSharp, Delphi, J#, IronPython etc. etc.). One of these plugins is the Python plugin and it implements and extends the basic IronPython Language. language as well as Python at the front end, while tapping into all the core Rhino resources at the back end. Scripts thus gain access to Rhino, the core libraries and even other plugins through the RhinoScriptSyntax plugin. Right, enough fore-play, time to get back to hard core programming. ## 3.2 The Bones Once you run a script through the in-build editor (remember you can access the editor by typing "Scripteditor" in Rhino's command line) the Python interpreter will thumb through your script and superficially parse the syntax. It will not actually execute any of the code at this point, before it starts doing that it first wants to get a feel for the script. The interpreter is capable of finding certain syntax errors during this prepass. If you see a dialog box like this: before anything has actually taken place, it means the compiler ran into a problem with the syntax and decided it wasn't worth trying to run the script. If the script crashes while it is running, the Source of the error message will not be the Python Compiler. However, even scripts without syntax errors might not function as expected. In order for a script to run successfully, it must adhere to a few rules. Apart from syntax errors -which must be avoided- every script must implement a certain structure which tells the interpreter what's what: Note that the example script on page 11 did not adhere to these rules. It ran just the same, but it was a bad example in this respect. The Import Statement allows the user to import different modules that are either built into Python when its downloaded, or from external developments. Importing modules allows a user to access methods outside of the current file and reference objects, functions or other information. There are various types of Import Statements: *import X, from X import, from X import a, b, c, X = __import__(‘X’)*, each with advantages and disadvantages. For simplicity we can stick with import X for the time being. This technique imports module X and allows us to use any methods within that module. Comments (blocks of text in the script which are ignored by the compiler and the interpreter), can be used to add explanations or information to a file, or to temporarily disable certain lines of code. It is considered good practice to always include information about the current script at the top of the file such as author, version and date. Comment lines are indicated with a # sign. Global variables are variables that can be accessed anywhere in your code (outside of functions, within functions and within classes). Variable scope refers to the limitation or accessibility of a variable across different portions of code. Global variables obviously can be accessed globally, while other variables may be limited to certain areas of your code. For example, any variable that is created within a class or a function (we will cover classes and functions later) is limited to within that function. This means they cannot be used outside of that function or class (unless they are specifically passed as input/output). For now, we don't need to worry about different types of scope and let's assume that our variables are globally accessible unless otherwise noted. Functions are blocks of code that compact certain functionality into a small package. Functions can have variables, take input, provide output and do a number of other important tasks. We will go into further detail about functions in the coming chapters. Classes are similar in that they provide an opportunity for creating module code to package/compress segments of your code, while also providing other powerful tools. Functions and classes must be created before they can be used (this is rather obvious). For that reason, the *Functions & Classes section* comes before the *Function Calls and Class Instances section*. This just means that before we can actually *Call* (use) a Function, we need to first create the function. ## 3.3 The Guts The following example shows the essential structure that was just described, including: the Import Statement (always needed!), Global Variables, a Function and a Call to the Function. The importance of syntax should also be stated - Please take note of the capitalization and indentation within this example. Python is both case sensitive and indent sensitive. If you spell a variable name once with a capital letter and another time with a lowercase letter, it will not recognize it as the same variable! The indent is used to indicate if certain lines should be included within a Function, Class, Loop or Conditional statement. In this example, the line "print (text)" is indented to be contained within the function "simpleFunction" because it should only be executed once that function is called (Don't worry yet about how and why functions work, we will explain them soon). Indentation and Case Sensitivity should be highly emphasized since they are a couple of the most common mistakes that you will run into! ```python #! python3 # Language Type import rhinoscriptsyntax as rs # Import Statements import scriptcontext as sc import math import System import System.Collections.Generic import Rhino import rhinoscriptsyntax as rs #Script written by Ehsan Iran-Nijad # Default comments strInfo = "This is just a test" # Global Variable def simpleFunction(text): # Function Declaration print(text) # Code to Execute Within the Function # (Note the Indentation) simpleFunction(strInfo) # Calling the Function (After it's created) ``` One of the key features of RhinoScriptSyntax that makes it easy to write powerful scripts is the large library of Rhino specific functions. This set of functions is known as the rhinoscriptsyntax package. To import the rhinoscriptsyntax package you must include the `import rhinoscriptsyntax` statement, `as rs` indicates that we will be using the name "rs" whenever we refer to this package. In the Editor, go to Help>Python Help for a list of all the rhinoscriptsyntax methods. Documentation can also be found at [http://www.rhino3d.com/5/ironpython/index.html](http://www.rhino3d.com/5/ironpython/index.html) Note: McNeel has made all of the classes in the .NET Framework available to Python, including the classes available in RhinoCommon. This allows you to do some pretty amazing things inside of a python script. Many of the features that once could only be done in a .NET plug-in can now be done in a python script! (Don't stress about this until you become a master of the basics...for now, just know its available!) ## 3.4 The Skin After a script has been written and tested, you might want to put it in a place which has easy access such as a Rhino toolbar button. If you want to run scripts from within buttons, there are two things you can do: 1. Link the script 2. Implement the script If you want to implement the script, you'll have to wrap it up into a *_RunPythonScript* command. Imagine the script on the previous page has been saved on the hard disk as an \*.py file. The following button editor screenshot shows how to use the two options: ## 3.5 The Editor A Code Editor is an essential tool for any programmer. Luckily, the script-editor within Rhino is built-in. It includes a Debugger for testing and working line-by-line through any script! It is extremely good practice to use the debugger when writing any code longer than just a few lines. The expression "bug in your code," means that something has gone wrong in your code - i.e your code fails, cannot continue to run or has given the wrong output. *(Of interesting note - the first computer bug is said to have been found in 1947, when Harvard University's Mark II Aiken Relay Calculator machine was experiencing problems. An investigation showed that there was a moth trapped in the machine. The operators removed the moth and taped it into the log book. The entry reads: "First actual case of bug being found." And thus, the world of debugging was born!)* With any malfunctioning code, the programmers job is to quickly and easily identify the bug, however, this can be sometimes extremely difficult, especially if the code has many loops, conditional statements, functions, classes and spans hundreds or thousands of lines. To find out more about using the editor and its integrated debugger, see the [Script Editing Guide](/guides/scripting/scripting-command/) ## Next Steps That was a basic overview of Python running in Rhino. Now learn to use [operators and functions](/guides/rhinopython/primer-101/4-operators-and-functions/) to get something done. -------------------------------------------------------------------------------- # 3 Script Anatomy Source: https://developer.rhino3d.com/en/guides/rhinoscript/primer-101/3-script-anatomy/ ## 3.1 Programming in Rhino Rhinoceros offers various ways of programmatic access. We've already met macros and scripts, but the plot thickens. Please invest a few moments of your life into looking at the diagram below, which you will never be asked to reproduce: The above is a complete breakdown of all developer tools that Rhino has to offer. I'll give you a brief introduction as to what this diagram actually represents and although that is not vital information for our primary goal here ("learning how to script" in case you were wondering), you might as well familiarize yourself with it so you have something to talk about on a first date. At the very core of Rhino are the code libraries. These are essentially collections of procedures and objects which are used to make life easier for the programs that link to them. The most famous one is the openNURBS library which was developed by Robert McNeel & Associates but is competely open source and has been ported by 3rd party programmers to other operating systems such as Unix and Linux. OpenNURBS provides all the required file writing and reading methods as well the basic geometry library. Practically all the 3D applications that support the 3dm file format use the openNURBS library. These code libraries have no knowledge of Rhino at all, they are 'upstream' so to speak. Rhino itself (the red blob) is tightly wrapped around these core libraries, it both implements and extends them. Apart from this obvious behaviour, Rhino also adds the possibility of plugins. Whereas most companies provide plugin support for 3rd party developers, McNeel has taken a rather exotic approach which elimates several big problems. The technical term for this approach is "eating your own dogfood" and it essentially boils down to McNeel programmers using the same tools as 3rd party programmers. Rather than adding code to Rhino itself, McNeel programmers prefer writing a plugin instead. For one, if they screw up the collateral damage is usually fairly minor. It also means that the SDK (Software Development Kit, that which is used to build plugins) is rigorously tested internally and there is no need to maintain and support a separate product. Unfortunately the result of this policy has made plugins so powerful that it is very easy for ill-informed programmers to crash Rhino. This is slightly less true for those developers that use the dotNET SDK to write plugins and it doesn't apply at all to us, scripters. A common proverb in the software industry states that you can easily shoot yourself in the foot with programming, but you can take your whole leg off with C++. Scripters rarely have to deal with anymore more severe than a paper-cut. The orange pimples on Rhino's smooth surface are plugins. These days plugins can be written in C++ and all languages that support the DotNET framework (VB.NET, CSharp, Delphi, J#, IronPython etc. etc.). One of these plugins is the RhinoScript plugin and it implements and extends the basic Microsoft Visual Basic Scripting language at the front end, while tapping into all the core Rhino resources at the back end. Scripts thus gain access to Rhino, the core libraries and even other plugins through the RhinoScript plugin. Right, enough fore-play, time to get back to hard core programming. ## 3.2 The Bones Once you run a certain script, either through the in-build editor or as an external file, the VBScript interpreter will thumb through your script and superficially parse the syntax. It will not actually execute any of the code at this point, before it starts doing that it first want to get a feel for the script. The interpreter is capable of finding certain syntax errors during this prepass. If you see a dialog box like this: before anything has actually taken place, it means the compiler ran into a problem with the syntax and decided it wasn't worth trying to run the script. If the script crashes while it is running, the Source of the error message will not be the Microsoft VBScript Compiler. However, even scripts without syntax errors might not function as expected. In order for a script to run successfully, it must adhere to a few rules. Apart from syntax errors -which must be avoided- every script must implement a certain structure which tells the interpreter what's what: Note that the example script on page 11 did not adhere to these rules. It ran just the same, but it was a bad example in this respect. The Option Explicit area is named after the Option Explicit statement which it contains. The Option Explicit statement is optional, but I highly recommend adding it to every single script you ever write. If you are running a script in Option Explicit mode, you have to define all your variables before you can use them (see paragraph 2.3.5). If you omit Option Explicit, your variables will be declared for you by the compiler. Although this may sound as a good thing at first, it is much harder to find problems which are caused by typos in variable names. Option Explicit will save you from yourself. In addition to the Option Explicit statement, the Option Explicit area may also contains a set of comments. Comments are blocks of text in the script which are ignored by the compiler and the interpreter. You can use comments to add explanations or information to a file, or to temporarily disable certain lines of code. It is considered good practise to always include information about the current script at the top of the file such as author, version and date. Comments are always preceded by an apostrophe. Global variables are also optional. Typically you do not need global variables and you're usually better off without them. The area of the script which is outside the function declarations is referred to as 'script level'. All script level code will be executed by the interpreter whenever it feels like it so you're usually better off by putting all the code into functions and having them execute at your command. ## 3.3 The Guts Every script requires at least one function (or subroutine) which contains the main code of the script. It doesn't have to be a big function, and it can place calls to any number of other functions but it is special because it delineates the extents of the script. The script starts running as soon as this function is called and it stops when the function completes. Without a main function, there is nothing to run. Functions are not run automatically by the interpreter. They have to be called specifically from other bits of code. The only way to start the cascade of functions calling functions, is to place a call to the main subroutine somewhere outside all function declarations. You could put it anywhere, including at the very bottom of the script file, but I prefer to keep it near the top, just after the Option Explicit statement and just before the main subroutine begins. Without a main function call your script will be parsed and compiled, but it will not be executed. Do not get confused by terms such as 'function', 'subroutine', 'procedure' or 'method', at this time they all pretty much mean the same thing. A script file may contain any number of additional functions/subroutines/procedures. But since I haven't told you yet what they are (apart from the fact that they are very similar), we'll skip this bit. For now. Don't get too comfortable. ``` Option Explicit < Option Explicit statement `Script written by David Rutten on 28-08-2006 < Default comments Public intCount < A Global variable Call Main() < Main function call Sub Main() < Main function declaration Dim strInfo < Main function start strInfo = "This is just a test" Rhino.Print strInfo Rhino.Print "I repeat: " \& strInfo End Sub < Main function end ``` ## 3.4 The Skin After a script has been written and tested, you might want to put it in a place which has easy access such as a Rhino toolbar button. If you want to run scripts from within buttons, there's two things you can do: 1. Link the script 2. Implement the script If you link the script you'll only have to hardcode the *_LoadScript* command to point to the script file on the hard disk. If you want to implement the script, you'll have to wrap it up into a *_RunScript* command. Imagine the script on the previous page has been saved on the hard disk as an **.rvb file*. The following button editor screenshot shows how to use the two options: ## Next Steps That was a basic overview of Python running in Rhino. Now learn to use [operators and functions](/guides/rhinoscript/primer-101/4-operators-and-functions/) to get something done. -------------------------------------------------------------------------------- # Sticky Values Source: https://developer.rhino3d.com/en/samples/rhinopython/sticky-values/ This module contains a standard python dictionary called sticky which sticks around. ```python import rhinoscriptsyntax as rs import scriptcontext stickyval = 0 # restore stickyval if it has been saved if "my_key" in scriptcontext.sticky: stickyval = scriptcontext.sticky["my_key"] nonstickyval = 12 print("sticky =", stickyval) print("nonsticky =", nonstickyval) val = rs.GetInteger("give me an integer") if val: stickyval = val nonstickyval = val # save the value for use in the future scriptcontext.sticky["my_key"] = stickyval ``` The `scriptcontext` module contains a standard python dictionary called sticky which "sticks" around during the running of Rhino. This dictionary can be used to save settings between execution of your scripts and then get at those saved settings the next time you run your script or from a completely different script. -------------------------------------------------------------------------------- # 4 Operators and Functions Source: https://developer.rhino3d.com/en/guides/rhinopython/primer-101/4-operators-and-functions/ ## 4.1 What on Earth are they and why should I care? When we were discussing numeric variables in paragraph 2.3.1, there was an example about mathematical operations on numbers: ```python x = 15 + 26 * 2.33 x = math.sin(15 + 26) + math.sqrt(2.33) x = math.tan(15 + 26) / math.log(55) ``` The four lines of code above contain four kinds of code: 1. Numbers `15, 26, 2.33 and 55` 2. Variables `x` 3. Operators `=, +, * and /` 4. Functions `math.sin(), math.sqrt(), math.tan() and math.log()` Numbers and variables are well behind us now. Arithmetic operators should be familiar from everyday life, Python uses them in the same way as you used to during math classes. Python comes with a limited amount of arithmetic operators and they are always positioned between two variables or constants (a constant is a fixed number). The function first signifies that we have imported math at the top of our code, using "import math", and then call a function that is within the math module called "sin()". Thus we write: math.sin(value). ## 4.2 Careful... One thing to watch out for is operator precedence. As you will remember from math classes, the addition and the multiplication operator have a different precedence. If you see an equation like this: ```python x = 4 + 5 * 2 x = (4 + 5) * 2 # wrong precedence x = 4 + (5 * 2) # correct precedence ``` x doesn't equal 18, even though many cheap calculators seem to disagree. The precedence of the multiplication is higher which means you first have to multiply 5 by 2, and then add the result to 4. Thus, x equals 14. Python is not a cheap calculator and it has no problems whatsoever with operator precedence. It is us, human beings, who are the confused ones. The example above is fairly straightforward, but how would you code the following? $$y =\frac{\sqrt{x^2+(x+1)}}{x-3} + \left|{\frac{2x}{x^{0.5x}}}\right|$$ Without extensive use of parenthesis, this would be very nasty indeed. By using parenthesis in equations we can force precedence, and we can easily group different bits of mathematics. All the individual bits in the mathematical notation have been grouped inside parenthesis and extra spaces have been inserted to accentuate transitions from one top level group to the next: ```python y = ( math.sqrt(x ** 2 + (x - 1)) / (x - 3) ) + abs( (2 * x) / (x ** (0.5 * x)) ) ``` It is still not anywhere near as neat as the original notation, but I guess that is why the original notation was invented in the first place. Usually, one of the best things to do when lines of code are getting out of hand, is to break them up into smaller pieces. The equation becomes far more readable when spread out over multiple lines of code: ```python A = x**2 + (x-1) B = x-3 C = 2*x D = x**(0.5* x) y = (math.sqrt(A) / B) + abs(C / D) ``` ## 4.3 Logical Operators I realize the last thing you want right now is an in-depth tutorial on logical operators, but it is an absolute must if we want to start making smart code. I'll try to keep it as painless as possible. Logical operators mostly work on booleans and they are indeed very logical. As you will remember, booleans can only have two values. Boolean mathematics were developed by George Boole (1815-1864) and today they are at the very core of the entire digital industry. Boolean algebra provides us with tools to analyze, compare and describe sets of data. Although George originally defined six boolean operators we will only discuss three of them: 1. Not 2. And 3. Or The Not operator is a bit of an oddity among operators. It is odd because it doesn't require two values. Instead, it simply inverts the one on the right. Imagine we have a script which checks for the existence of a bunch of Block definitions in Rhino. If a block definition does not exist, we want to inform the user and abort the script. The English version of this process might look something like: ``` Ask Rhino if a certain Block definition exists If not, abort this sinking ship ``` The more observant among you will already have noticed that English version also requires a "not" in order to make this work. Of course you could circumvent it, but that means you need an extra line of code: ``` Ask Rhino if a certain Block definition exists If it does, continue unimpeded Otherwise, abort ``` When we translate this into Python code we get the following: ```python if (not rs.IsBlock("SomeBlockName")): print("Missing block definition: SomeBlockName") ``` And and Or at least behave like proper operators; they take two arguments on either side. The And operator requires both of them to be True in order for it to evaluate to True. The Or operator is more than happy with a single True value. Let's take a look at a typical 'one-beer-too-many' algorithm: ```python person = GetPersonOverThere() colHair = GetHairColour(person) if((IsGirl(person)) and (colHair == Blond or colHair == Brunette) and (Age(person) >= 18)): neighbour = GetAdjacentPerson(person) if(not IsGuy(neighbour) or not LooksStrong(neighbour)): print("Hey baby, you like Heineken?") else: RotateAngleOfVision 5.0 ``` As you can see the problem with Logical operators is not the theory, it's what happens when you need a lot of them to evaluate something. Stringing them together, quickly results in convoluted code not to mention operator precedence problems. A good way to exercise your own boolean logic is to use Venn-diagrams. A Venn diagram is a graphical representation of boolean sets, where every region contains a (sub)set of values that share a common property. The most famous one is the three-circle diagram: Every circular region contains all values that belong to a set; the top circle for example marks off set {A}. Every value inside that circle evaluates True for {A} and every value not in that circle evaluates False for {A}. If you're uncomfortable with "A, B and C", you can substitute them with *"Employed"*, *"Single"* and *"HomeOwner"*. By coloring the regions we can mimic boolean evaluation in programming code: Try to color the four diagrams below so they match the boolean logic: Venn diagrams are useful for simple problems, but once you start dealing with more than three regions it becomes a bit opaque. The following image is an example of a 6-regional Venn diagram. Pretty, but not very practical: ## 4.4 Functions and Procedures In the end, all that a computer is good at is shifting little bits of memory back and forth. When you are drawing a cube in Rhino, you are not really drawing a cube, you are just setting some bits to zero and others to one. At the level of Python there are so many wrappers around those bits that we can't even access them anymore. A group of 32 bits over there happens to behave as a number, even though it isn't really. When we multiply two numbers in Python, a very complicated operation is taking place in the memory of your PC and we may be very thankful that we are never confronted with the inner workings. As you can imagine, a lot of multiplications are taking place during any given second your computer is turned on and they are probably all calling the same low-level function that takes care of the nasty bits. That is what functions are about, they wrap up nasty bits of code so we don't have to bother with it. This is called encapsulation. A good example is the *math.sin()* function, which takes a single numeric value and returns the sine of that value. If we want to know the sine of -say- 4.7, all we need to do is type in *x = math.sin(4.7)*. Internally the computer might calculate the sine by using a digital implementation of the Taylor series: $$f(x) = \sum_{n=0}^\infty \frac{f^n(a)}{n!} {(x-a)^n}$$ In other words: you don't want to know. The good people who develop programming languages predicted you don't want to know, which is why they implemented a *math.sin()* function. Python comes with a long list of predefined functions all of which are available to RhinoScripters. Some deal with mathematical computations such as *math.sin()*, others perform String operations such as *abs()* which returns the absolute value. Python lists 75 native procedures plus many more in any of the modules that can be imported (i.e. the *math* module). I won't discuss them here, except when they are to be used in examples. Apart from implementing the native Python functions, Rhino adds a number of extra ones for us to use. The current RhinoScriptSyntax helpfile for Rhino5 claims a total number of about 800 additional functions, and new ones are added frequently. Rhino's built in functions are referred to as "methods". They behave exactly the same as Python procedures although you do need to look in a different helpfile to see what they do. [http://www.rhino3d.com/5/ironpython/index.html](http://www.rhino3d.com/5/ironpython/index.html) So how do functions/procedures/methods behave? Since the point of having procedures is to encapsulate code for frequent use, we should expect them to blend seamlessly into written code. In order to do this they must be able to both receive and return variables. *math.sin()* is an example of a function which both requires and returns a single numeric variable. The *datetime.now()* function on the other hand only returns a single value which contains the current date and time. It does not need any additional information from you, it is more than capable of finding out what time it is all by itself. An even more extreme example is the *rs.Exit()* method which does not accept any argument and does not return any value. There are two scenarios for calling procedures. We either use them to assign a value or we call them out of the blue: ```python strPointID = rs.AddPoint([0.0, 0.0, 1.0]) # Correct rs.AddPoint([0.0, 0.0, 1.0]) # Correct rs.AddPoint [0.0, 0.0, 1.0] # Wrong ``` If you look in the RhinoScriptSyntax helpfile and search for the *AddLayer()* method, you'll see the following text: ```python rs.AddLayer (name=None, color=0, visible=True, locked=False, parent=None) ``` *rs.AddLayer()* is capable of taking five arguments, all of which are optional. We can tell they are optional because it says *"Optional"* next to each item under the *"Parameters"* section of the helpfile. The *"Parameters"* signify the Input values for the Function, while the *"Returns"* section tells us what the Function will return. Optional arguments have a default value which is used when we do not override it. If we omit to specify the `lngColor` argument for example the new layer will become black. ### 4.4.1 A Simple Function Example This concludes the boring portion of the primer. We now have enough information to actually start making useful scripts. I still haven't told you about loops or conditionals, so the really awesome stuff will have to wait until Chapter 5, though. We're going to write a script which uses some Python functions and a few RhinoScriptSyntax methods. Our objective for today is to write a script that applies a custom name to selected objects. First, I'll show you the script, then we'll analyze it line by line: ```python import rhinoscriptsyntax as rs import time #This script will rename an object using the current system time strObjectID = rs.GetObject("Select an object to rename",0,False,True) if strObjectID: strNewName = "Time: " + str(time.asctime(time.localtime())) rs.ObjectName(strObjectID, strNewName) ``` This is a complete script file which can be run directly from the disk. It adheres to the basic script structure according to page 13. We'll be using two variables in this script, one to hold the ID of the object we're going to rename and one containing the new name. On line 5 we declare a new variable. Although the "str" prefix indicates that we'll be storing Strings in this variable, that is by no means a guarantee. You can still put numbers into something that starts with str. It is simply the convention to name a variable with strSomething if it is storing a string, similarly you can use intSomething for integers etc. On line 5, we're assigning a value to *strObjectID* by using the RhinoScriptSyntax method *GetObject()* to ask the user to select an object. The help topic on *GetObject()* tells us the following: ```python Rhino.GetObject (message=None,filter=0,preselect=False,Select=False,custom_filter=None,subobjects=False) ``` ``` Returns: String » The identifier of the picked object if successful. None » If not successful, or on error. ``` This method accepts six arguments, all of which happen to be optional. In our script we're only specifying the first and fourth argument. The *strMessage* refers to the String which will be visible in the command-line during the picking operation. We're overriding the default, which is "Select object", with something a bit more specific. The second argument is an integer which allows us to set the selection filter. The default behavior is to apply no filter; all objects can be selected whether they are points, textdots, polysurfaces, lights or whatever. We want the default behavior. The same applies to the third argument which allows us to override the default behavior of accepting preselected objects. The fourth argument is False by default, meaning that the object we pick will not be actually selected. This is not desired behavior in our case. The fifth argument takes a bit more explaining so we'll leave it for now. Note that we can simply omit optional arguments and put a closing bracket after the last argument that we do specify. When the user is asked to pick an object -any object- on line 5, there exists a possibility they changed their mind and pressed the escape button instead. If this was the case then *strObjectID* will not contain a valid Object ID, it will be None instead. If we do not check for variable validity (line 7) but simply press on, we will get an error on line 11 where we are trying to pass that None value as an argument into the *rs.ObjectName()* method. We must always check our return values and act accordingly. In the case of this script the proper reaction to an Escape is to abort the whole thing. The If: structure on Line 7 will abort the current script if *strObjectID* turns out to be None. If *strObjectID* turns out to be an actual valid object identifier, our next job is to fabricate a new name and to assign it to the selected object. The first thing we need is a variable which contains this new name. We declare it and assign it a value on line 9. The name we are constructing always has the same prefix but the suffix depends on the current system time. In order to get the current system time we use the *time.localtime()* function which is a function built into the time module (which we have imported at the top of our script). Since a Time and a String are not the same thing, we cannot concatenate them with the ampersand operator. We must first convert the Time into a valid String representation. The *str()* function is another Python native function which is used to convert non-string variables into Strings. When I tested this script, the value assigned to *strNewName* at line 11 was: ``` Time: (2011, 3, 10, 22, 17, 53, 3, 69, 0) ``` Finally, at line 11, we reach the end of our quest. We tell Rhino to assign the new name to the old object: Instead of using *strNewName* to store the name String, we could have gotten away with the following: ```python rs.ObjectName(strObjectID, "Time: " & str(time.localtime())) ``` This one line replaces lines 9 through 11 of the original script. Sometimes brevity is a good thing, sometimes not. Especially in the beginning it might be smart to be explicit and take up multiple lines; it makes debugging a lot easier (until you feel comfortable making your code shorter and possibly harder to decipher). ### 4.4.2 Advanced Function Syntax Whenever you call a function it always returns a value, even if you do not specifically set it. By default, every function returns a *None* value, since this is the default value for all variables and functions in Python. So if you want to write a function which returns you a String containing the alphabet, doing this is not enough: ```python def Alphabet(): strSeries = "abcdefghijklmnopqrstuvwxyz" ``` The word "def" signifies the start of a function. "Alphabet" is a name we have made-up for our function. Again, the indentation indicates that line 2 is within the function and should only be run after the function is called. Although the function actually assigns the alphabet to the variable called strSeries, this variable will go out of scope once the function ends on line #2 and its data will be lost. You have to assign the return value to the function name, like so: ```python def Alphabet(): strSeries = "abcdefghijklmnopqrstuvwxyz" return strSeries print(Alphabet()) ``` The "return value" identifies what will be returned once the method is called and the code within its scope is executed. When this code is run, it will call the function *Alphabet()*, the code within the function's scope will be run and the function will return the value of strSeries. This returned value will then be printed to the command line. It should be noted that at first glance, the return and print functions appear to be very similar. However, they are not! *print()* will print anything to the command line and console. return(), on the other hand, will only return a value from a function - basically assigning a value to a variable whenever the function was called. Return is used for the output of a function (in this case the function "Alphabet"), print is used for debugging code or whenever the user wants to see a value printed to the screen. Imagine you want to lock all curve objects in the document. Doing this by hand requires three steps and it will ruin your current selection set, so it pays to make a script for it. A function which performs this task might fail if there are no curve objects to be found. If the function is designed-not-to-fail you can always call it without thinking and it will sort itself out. If the function is designed-to-fail it will crash if you try to run it without making sure everything is set up correctly. The respective functions are: ```python def lockcurves_fail(): rs.LockObjects(rs.ObjectsByType(rs.filter.curve)) ``` ```python def lockcurves_nofail(): curves = rs.ObjectsByType(rs.filter.curve) if not curves: return False rs.LockObjects(curves) return True ``` If you call the first function when there are no curve objects in the document, the *rs.ObjectsByType()* method will return a None variable. It returns None because it was designed-not-to-fail and the *None* variable is just its way of telling you; "tough luck". However, if you pass a None variable as an argument to the *rs.LockObjects()* method it will keel over and die, generating a fatal error! ``` Error Message: iteration over non-sequence of type NoneType ``` This means that the *rs.LockObjects()* method requires a list to iterate through and we have provided None variable - thus the error! The second function, which is designed-not-to-fail, will detect this problem on line 6 and abort the operation. As you can see, it takes a lot more lines of code to make sure things run smoothly... A custom defined function can take any amount of arguments between nill and a gazillion. Anyone who calls this function must provide a matching signature or an error will occur. More on the argument list in a bit. The first line which contains the name and the arguments is called the function declaration. Everything in between is called the function body, and is noted by the indentation. In the function body you can declare variables, assign values, call other functions and return variables. The argument list takes a bit more of explaining. Usually, you can simply comma separate a bunch of arguments and they will act as variables from there on end: ```python def MyBogusFunction(intNumber1, intNumber2): ``` This function declaration already provides three variables to be used inside the function body: 1. MyBogusFunction - (when a user calls this function - it will provide the return value) 2. intNumber1 - (the first argument) 3. intNumber2 - (the second argument) Let's assume this function determines whether *intNumber1* plus 100 is larger than twice the value of *intNumber2*. The function could look like this: ```python def MyBogusFunction(intNumber1, intNumber2): intNumber1 = intNumber1 + 100 intNumber2 = intNumber2 * 2 return (intNumber1 > intNumber2) ``` In this function, we can see that we have used "def" to indicate that we are creating a new function, we have called it "MyBogusFunction" and have given it two input variables (intNumber1, intNumber2). Within the indentation (the guts of the function), we have done a few calculations and we have used the "return" statement to output an evaluation of our calculations. Now, when we call the function somewhere else in our code, the variable will be set to the return value of our function.: ```python print(MyBogusFunction(5, 6)) ``` The result will be True (105 is indeed greater than 36)! Previously, we mentioned something called variable scope - this refers to where a variable has been defined and where it can be used. Functions and Classes are very specific when it comes to variable scope. Variables that are defined within a function cannot be referenced outside of the function unless they are passed through the input or return statements!! For example: ```python def testFunction(): y=20 return y print(y*testFunction()) ``` This code will return an error, "'y' is not defined" because the variable named "y" has only been defined within a function. That means that we cannot use that variable outside of the function unless we pass it through the input or return statements. The code literally does not understand what "y" means because it was created inside of the function. Otherwise, we could have defined y outside of the function which would make it have global scope and we could use it anywhere within the code. ```python y = 20 def testFunction(): return y print(testFunction()) ``` ## 4.5 Mutability Python includes a fairly confusing, although sometimes useful, quality pertaining to variables, tuples, lists and dictionaries (the last three we will dive into deeper a bit later). When we create a variable it points to a specific place in memory and if we create a second variable that is equal to the first - Does y point to the same space in memory as x, or does it now have its own referenced space? For example: ```python #VARIABLE EXAMPLE: x = 10 y = x x = 5 print(y) ``` What will be printed? It turns out, the result is 10! That means that y is referencing the initial value of x, it is NOT referencing the variable (and thus it does not change when x changes). Although we haven't gone through them, Tuples will act the same as this variable example, while Lists and Dictionaries will be changed based on the referenced variable. For example: ```python #TUPLE EXAMPLE: x = (1,2) y = x x = (3,4) print(y) # The result = (1,2) ``` Tuples act very similar to variables with regard to referencing other items. In this example, the tuple called "y" is NOT changed once we change the value of "x". ```python #LIST EXAMPLE - BAD: x = [1,2] y = x x.append(3) print(y) # The result = (1,2,3) ``` In this example, the List "y" DOES change once we change the value of "x". Thus, the result is (1,2,3), not (1,2) as was the case in the previous examples. This demonstrates that Lists are referencing the variable not the value of "x". In order to make "y" act as its own, independent variable and value, we must create a copy of the first variable: ```python #LIST EXAMPLE - GOOD: x = [1,2] y = x[:] #This creates a copy of the list "x" x.append(3) print(y) # The result = (1,2) ``` The variable[:] symbol creates a copy of the variable. This means that "y" will now be an independent list and will not change when "x" changes! One more example: ```python #DICTIONARY EXAMPLE - BAD: x = {1:'a',2:'b'} y = x x[3] = 'c' print(y) # The result = {1:'a',2:'b',3:'c'} ``` In this example the dictionary "y" will be changed with the dictionary "x", unless we use *x.copy()*. ```python #DICTIONARY EXAMPLE - GOOD: x = {1:'a',2:'b'} y = x.copy() x[3] = 'c' print(y) # The result = {1: 'a', 2: 'b'} ``` This gets into the topic of mutability. An element is considered mutable if it can be changed/modified once they are created. Variables and Tuples are considered Immutable, meaning that they cannot be changed unless you create a new variable with the newly desired value (or copy over top of the old variable). Lists and Dictionaries are considered mutable, because they can be modified once they have been created. This means that we can freely add, remove, slice the values within a List or Dictionary. This is an exciting and powerful tool, that was previously not available with VBscript Arrays! More on this later when we get into Tuples, Lists and Dictionaries... ## Next Steps Ok, so now that you can get some functions to work, next is [conditional execution](/guides/rhinopython/primer-101/5-conditional-execution/) to control how the script reacts to input. -------------------------------------------------------------------------------- # 4 Operators and Functions Source: https://developer.rhino3d.com/en/guides/rhinoscript/primer-101/4-operators-and-functions/ ## 4.1 What on Earth are they and why should I care? When we were discussing numeric variables in paragraph 2.3.1, there was an example about mathematical operations on numbers: ```python x = 15 + 26 * 2.33 x = math.sin(15 + 26) + math.sqrt(2.33) x = math.tan(15 + 26) / math.log(55) ``` The four lines of code above contain four kinds of code: 1. Numbers `15, 26, 2.33 and 55` 2. Variables `x` 3. Operators `=, +, * and /` 4. Functions `math.sin(), math.sqrt(), math.tan() and math.log()` Numbers and variables are well behind us now. Arithmetic operators should be familiar from everyday life, VBScript uses them in the same way as you used to during math classes. VBScript comes with a limited amount of arithmetic operators and they are always positioned between two variables or constants (a constant is a fixed number). Now, that looks really scary doesn't it? I am always amazed at how good people are in finding expensive words for simple things. There's nothing to be afraid about though, I'll talk you through the hardest bits and when were done with this chapter, you can impress the living daylights out of any non-programmer by throwing these terms into casual conversation. ## 4.2 Careful... As you take a closer look at the tables on the opposite page, you'll notice that the + and the = operator occur twice. The way they behave depends on where you put them, which I can't help but feel was a silly choice to make, even in 1963. Especially the assignment/equals operator can be confusing. If you want to assign a value to a variable called x, you use the following code: ``` x = SomethingOrOther ``` But if you want to check if x equals SomethingOrOther you use very identical syntax: ``` If x = SomethingOrOther Then… ``` In the second line, the value of x will not change. Java and C programmers are always scornful when they see such careless treatment of the equals operator, and for once they may be right. Another thing to watch out for is operator precedence. As you will remember from math classes, the addition and the multiplication operator have a different precedence. If you see an equation like this: ``` x = 4 + 5 * 2 x = (4 + 5) * 2 » wrong precedence x = 4 + (5 * 2) » correct precedence ``` x doesn't equal 18, even though many cheap calculators seem to disagree. The precedence of the multiplication is higher which means you first have to multiply 5 by 2, and then add the result to 4. Thus, x equals 14. VBScript is not a cheap calculator and it has no problems whatsoever with operator precedence. It is us, human beings, who are the confused ones. The example above is fairly straightforward, but how would you code the following? $$y =\frac{\sqrt{x^2+(x+1)}}{x-3} + \left|{\frac{2x}{x^{0.5x}}}\right|$$ Without extensive use of parenthesis, this would be very nasty indeed. By using parenthesis in equations we can force precedence, and we can easily group different bits of mathematics. All the individual bits in the mathematical notation have been grouped inside parenthesis and extra spaces have been inserted to accentuate transitions from one top level group to the next: ``` y = ( Sqr(x ^ 2 + (x - 1)) / (x - 3) ) + Abs( (2 * x) / (x ^ (0.5 * x)) ) ``` It is still not anywhere near as neat as the original notation, but I guess that is why the original notation was invented in the first place. Usually, one of the best things to do when lines of code are getting out of hand, is to break them up into smaller pieces. The equation becomes far more readable when spread out over multiple lines of code: ```vb Dim A, B, C, D A = x^2 + (x-1) B = x-3 C = 2*x D = x^(0.5* x) y = (Sqr(A) / B) + Abs(C / D) ``` ## 4.3 Logical Operators I realize the last thing you want right now is an in-depth tutorial on logical operators, but it is an absolute must if we want to start making smart code. I'll try to keep it as painless as possible. Logical operators mostly work on booleans and they are indeed very logical. As you will remember booleans can only have two values, so whatever logic deals with them cannot be too complicated. It isn't. The problem is, we are not used to booleans in every day life. This makes them a bit alien to our own logic systems and therefore perhaps somewhat hard to grasp. Boolean mathematics were developed by George Boole (1815-1864) and today they are at the very core of the entire digital industry. Boolean algebra provides us with tools to analyze, compare and describe sets of data. Although George originally defined six boolean operators we will only discuss three of them: 1. Not 2. And 3. Or The Not operator is a bit of an oddity among operators. It is odd because it doesn't require two values. Instead, it simply inverts the one on the right. Imagine we have a script which checks for the existence of a bunch of Block definitions in Rhino. If a block definition does not exist, we want to inform the user and abort the script. The English version of this process might look something like: ``` Ask Rhino if a certain Block definition exists If not, abort this sinking ship ``` The more observant among you will already have noticed that English version also requires a "not" in order to make this work. Of course you could circumvent it, but that means you need an extra line of code: ``` Ask Rhino if a certain Block definition exists If it does, continue unimpeded Otherwise, abort ``` When we translate this into VBScript code we get the following: ```vb If Not Rhino.IsBlock("SomeBlockName") Then Rhino.Print "Missing block definition: SomeBlockName" Exit Sub End If ``` And and Or at least behave like proper operators; they take two arguments on either side. The And operator requires both of them to be True in order for it to evaluate to True. The Or operator is more than happy with a single True value. Let's take a look at a typical 'one-beer-too-many' algorithm: ```vb Dim person person = GetPersonOverThere() colHair = GetHairColour(person) If IsGirl(person) And (colHair = Blond Or colHair = Brunette) And (Age(person) >= 18) Then Dim neighbour neighbour = GetAdjacentPerson(person) If Not IsGuy(neighbour) Or Not LooksStrong(neighbour) Then Rhino.Print "Hey baby, you like Heineken?" Else RotateAngleOfVision 5.0 End If End If ``` As you can see the problem with Logical operators is not the theory, it's what happens when you need a lot of them to evaluate something. Stringing them together, quickly results in convoluted code not to mention operator precedence problems. A good way to exercise your own boolean logic is to use Venn-diagrams. A Venn diagram is a graphical representation of boolean sets, where every region contains a (sub)set of values that share a common property. The most famous one is the three-circle diagram: Every circular region contains all values that belong to a set; the top circle for example marks off set {A}. Every value inside that circle evaluates True for {A} and every value not in that circle evaluates False for {A}. If you're uncomfortable with "A, B and C", you can substitute them with "Employed", "Single" and "HomeOwner". By colouring the regions we can mimic boolean evaluation in programming code: Try to color the four diagrams below so they match the boolean logic: Venn diagrams are useful for simple problems, but once you start dealing with more than three regions it becomes a bit opaque. The following image is an example of a 6-regional Venn diagram. Pretty, but not very practical: ## 4.4 Functions and Procedures In the end, all that a computer is good at is shifting little bits of memory back and forth. When you are drawing a cube in Rhino, you are not really drawing a cube, you are just setting some bits to zero and others to one. At the level of VBScript there are so many wrappers around those bits that we can't even access them anymore. A group of 32 bits over there happens to behave as a number, even though it isn't really. When we multiply two numbers in VBScript a very complicated operation is taking place in the memory of your PC and we may be very thankful that we are never confronted with the inner workings. As you can imagine, a lot of multiplications are taking place during any given second your computer is turned on and they are probably all calling the same low-level function that takes care of the nasty bits. That is what functions are about, they wrap up nasty bits of code so we don't have to bother with it. This is called encapsulation. A good example is the `Sin()` function, which takes a single numeric value and returns the sine of that value. If we want to know the sine of -say- 4.7, all we need to do is type in *x = Sin(4.7)*. Internally the computer might calculate the sine by using a digital implementation of the Taylor series: $$f(x) = \sum_{n=0}^\infty \frac{f^n(a)}{n!} {(x-a)^n}$$ In other words: you don't want to know. The good people who develop programming languages predicted you don't want to know, which is why they implemented a *Sin()* function. VBScript comes with a long list of predefined functions all of which are available to RhinoScripters. Some deal with mathematical computations such as *Sin()*, others perform String operations such as *Trim()* which removes all leading and trailing spaces from a block of text. When a function does not return a value we call it a 'subroutine' instead for no good reason whatsoever. Both functions and subroutines can be referred to as procedures. This is all just coding slang, in the end it all boils down to the same thing. My copy of the VBScript helpfile lists 89 native procedures. I won't discuss them here, unless when they are to be used in examples. Apart from implementing the native VBScript functions, Rhino adds a few extra ones for us to use. The current RhinoScript helpfile for Rhino4 claims a total number of about 800 additional functions, and new ones are added frequently. For a special reason which I will not be going into anytime soon, the procedures you get to use through Rhino are referred to as "methods". They behave exactly the same as VBScript procedures although you do need to look in a different helpfile to see what they do. So how do functions, subroutines and methods behave? Since the point of having procedures is to encapsulate code for frequent use, we should expect them to blend seamlessly into written code. In order to do this they must be able to both receive and return variables. *Sin()* is an example of a function which both requires and returns a single numeric variable. The *Now()* function on the other hand only returns a single value which contains the current date and time. It does not need any additional information from you, it is more than capable of finding out what time it is all by itself. An even more extreme example is the *Rhino.Exit()* method which does not accept any argument and does not return any value. There are two scenarios for calling procedures. We either use them to assign a value or we call them out of the blue: ```vb 1. strPointID = Rhino.AddPoint(Array(0.0, 0.0, 1.0)) » Correct 2. Call Rhino.AddPoint(Array(0.0, 0.0, 1.0)) » Correct 3. Rhino.AddPoint(Array(0.0, 0.0, 1.0)) » Wrong ``` Actually, this is not all there is to it but since the syntax rules for parenthesis and function calls are so exceptionally horrid, I will not discuss them here. All you need to know to be a successful VBScript programmer is that you always, always use parenthesis when calling functions and that you have to use the Call keyword if you're not assigning a value. If you look in the RhinoScript helpfile and search for `Rhino.AddLayer`, you'll see the following text: ```vb Rhino.AddLayer ([strLayer [, lngColor [, blnVisible [, blnLocked [, strParent]]]]]) ``` The combined information of procedure name and arguments is called the 'signature'. `Rhino.AddLayer()` is capable of taking five arguments, all of which are optional. We can tell they are optional by the fact that they are encapsulated in square brackets. Optional arguments have a default value which is used when we do not override it. If we omit to specify the lngColor argument for example the new layer will become black. ### 4.4.1 A Simple Function Example This concludes the boring portion of the primer. We now have enough information to actually start making useful scripts. I still haven't told you about arrays and loops, so the really awesome stuff will have to wait till Chapter 5 though. We're going to write a script which uses some VBScript functions and a few RhinoScript methods. Our objective for today is to write a script that applies a custom name to selected objects. First, I'll show you the script, then we'll analyze it line by line: ```vb Option Explicit 'This script will rename an object using the current system time Call RenameObject() Sub RenameObject() Dim strObjectID strObjectID = Rhino.GetObject("Select an object to rename", , , True) If IsNull(strObjectID) Then Exit Sub Dim strNewName strNewName = "Date tag: " & CStr(Now()) Call Rhino.ObjectName(strObjectID, strNewName) End Sub ``` This is a complete script file which can be run directly from the disk. It adheres to the basic script structure according to page 13, but it doesn't use any global variables or additional functions. There is a standard *Option Explicit* area which takes up the first two lines of the script. The Main Function Call can be found on line 4. The main function in this case is not called *Main()*, since that is rather nondescript and I prefer to use names that tell me something extra. The main 'function' incidentally is in fact a main subroutine, it does not return a value since there is nothing to return a value to. Line 5 contains a standard subroutine declaration. The *Sub* keyword is used to indicate that we are about to baptize a new subroutine. The word after *Sub* is always the name of the subroutine. Function and variable names have to adhere to VBScript naming conventions or the compiler will generate an error. Names cannot contain spaces or weird characters. The only non-alphanumeric character allowed is the underscore. Names cannot start with a number either. We'll be using two variables in this script, one to hold the ID of the object we're going to rename and one containing the new name. On line 6 we declare a new variable. Although the "str" prefix indicates that we'll be storing Strings in this variable, that is by no means a guarantee. You can still put numbers into something that starts with str, just as you can put malt liquor into a Listerine™ bottle. You're just not supposed to. The variable *strObjectID* has been initialized, but it does not contain any data yet. It is still set to vbEmpty. On line 7 we're assigning a different value to *strObjectID*. We're using the RhinoScript method *GetObject()* to ask the user to select an object. The help topic on *GetObject()* tells us the following: ```vb Rhino.GetObject ([strMessage [, intType [, blnPreSelect [, blnSelect [, arrObjects ]]]]]) Returns: String » The identifier of the picked object if successful. Null » If not successful, or on error. ``` This method accepts five arguments, all of which happen to be optional. In our script we're only specifying the first and fourth argument. The strMessage refers to the String which will be visible in the command-line during the picking operation. We're overriding the default, which is "Select object", with something a bit more specific. The second argument is an integer which allows us to set the selection filter. The default behaviour is to apply no filter; all objects can be selected whether they be points, textdots, polysurfaces, lights or whatever. We want the default behaviour. The same applies to the third argument which allows us to override the default behaviour of accepting preselected objects. The fourth argument is False by default, meaning that the object we pick will not be actually selected. This is not desired behaviour in our case. The fifth argument takes a bit more explaining so we'll leave it for now. Note that we can simply omit optional arguments and put a closing bracket after the last argument that we do specify. When the user is asked to pick an object -any object- on line 7, there exists a possibility he changed his mind and pressed the escape button instead. If this was the case then *strObjectID* will not contain a valid Object ID, it will be *Null* instead. If we do not check for variable validity here but simply press on, we will get an error on line 13 where we are trying to pass that *Null* value as an argument into the *Rhino.ObjectName()* method. We must always check our return values and act accordingly. In the case of this script the proper reaction to an Escape is to abort the whole thing. The *If…Then* structure on Line 8 will abort the current script if *strObjectID* turns out to be *Null*. The *Exit Sub* statement can be used anywhere within a Subroutine and it will immediately cancel the subroutine and return control to the line of code which was responsible for calling the sub in the first place. If *strObjectID* turns out to be an actual valid object identifier, our next job is to fabricate a new name and to assign it to the selected object. The first thing we need is a variable which contains this new name. We declare it on line 10 and assign it a value on line 11. The name we are constructing always has the same prefix but the suffix depends on the current system time. In order to get the current system time we use the *Now()* function which is native to VBScript. *Now()* returns a Date variable type which contains information about both the date and the time. Since a Date and a String are not the same thing, we cannot concatenate them with the ampersand operator. We must first convert the Date into a valid String representation. The *CStr()* function is another VBScript native function which is used to convert non-string variables into Strings.* CStr* stands for Convert to String and you can use it to turn booleans, numbers, dates and a whole lot of other things into proper Strings. When I tested this script, the value assigned to *strNewName* at line 11 was: ``` Time: (2011, 3, 10, 22, 17, 53, 3, 69, 0) ``` Finally, at line 13, we reach the end of our quest. We tell Rhino to assign the new name to the old object: Instead of using *strNewName* to store the name String, we could have gotten away with the following: ``` Call Rhino.ObjectName(strObjectID, "Date tag: " & CStr(Now())) ``` This one line replaces lines 10 through 13 of the original script. Sometimes brevity is a good thing, sometimes not. Especially in the beginning it might be smart not to cluster your code too much; it makes debugging a lot easier. On line 14 we tell the script interpreter that our subroutine has ended. ### 4.4.2 Advanced Function Syntax The previous example showed a very simple subroutine which did not take any arguments and did not return a value. In many cases you will need something a bit more advanced. For one, a complex function will usually require some information and it will often have a return value, if only to indicate whether the function completed successfully. Whenever you call a function it always returns a value, even if you do not specifically set it. By default, every function returns a vbEmpty value, since this is the default value for all variables and functions in VBScript. So if you want to write a function which returns you a String containing the alphabet, doing this is not enough: ```vb Function Alphabet() Dim strSeries strSeries = "abcdefghijklmnopqrstuvwxyz" End Function ``` Although the function actually assigns the alphabet to the variable called strSeries, this variable will go out of scope once the function ends on line #4 and its data will be lost. You have to assign the return value to the function name, like so: ```vb Function Alphabet() Alphabet = "abcdefghijklmnopqrstuvwxyz" End Function ``` Every function has a specific variable which shares its name with the function. You can treat this variable like any other but the value of this variable is passed back to the caller when the function ends. This is usually called the "return value". If your function is designed-to-fail (this doesn't mean it is poorly designed), it will crash when confronted with invalid input. If your function however is designed-not-to-fail, it should return a value which tells the caller whether or not it was able to perform its duty. Imagine you want to lock all curve objects in the document. Doing this by hand requires three steps and it will ruin your current selection set, so it pays to make a script for it. A function which performs this task might fail if there are no curve objects to be found. If the function is designed-not-to-fail you can always call it without thinking and it will sort itself out. If the function is designed-to-fail it will crash if you try to run it without making sure everything is set up correctly. The respective functions are: ```vb Sub LockCurves_Fail() Call Rhino.LockObjects(Rhino.ObjectsByType(4)) End Sub ``` ```vb Function LockCurves_NoFail() LockCurves_NoFail = False 'Set a default return value Dim arrCurves arrCurves = Rhino.ObjectsByType(4) 'Get all curve object IDs If IsNull(arrCurves) Then Exit Function 'At this point the return value is False Call Rhino.LockObjects(arrCurves) 'Lock the curves LockCurves_NoFail = True 'Set a new return value indicating success End Function ``` If you call the first subroutine when there are no curve objects in the document, the Rhino.ObjectsByType() method will return a Null variable. It returns null because it was designed-not-to-fail and the null variable is just its way of telling you; "tough luck". However, if you pass a null variable as an argument to the Rhino.LockObjects() method it will keel over and die, generating a fatal error: The second function, which is designed-not-to-fail, will detect this problem on line 6 and abort the operation. As you can see, it takes a lot more lines of code to make sure things run smoothly... A custom defined function can take any amount of arguments between nill and a gazillion. Unfortunately you cannot define optional arguments in your own functions, nor can you declare multiple functions with the same name but different signatures (what is called 'overloading' in languages that do support this). The VBScript helpfile provides the following syntax rules for functions: ``` [Public | Private] Function name [(arglist)] [statements] [Exit Function] [name = expression] End Function ``` To put it in regular English... Functions can be declared with either Public or Private scope. If you use the Private keyword you will limit the function to your script only, meaning no one else can call it. If you use the Public keyword all the scripts which run in Rhino can access your function. This keyword is optional, meaning that when you omit it, Public is assumed. You also have to provide a unique name which adheres to VBScript naming conventions, this is not optional. Finally you can declare a set of arguments. Anyone who calls this function must provide a matching signature or an error will occur. More on the argument list in a bit. The first line which contains the scope, name and the arguments is called the function declaration. The last line of a function is always, always an End Function statement, no two ways about it. Everything in between is called the function body. The function body could technically be empty, though that won't do anybody any good. In the function body you can declare variables, assign values, call other functions or place a call to Exit Function, which will terminate the function prematurely and return execution to the line of code which was responsible for calling this function. The argument list takes a bit more of explaining. Usually, you can simply comma separate a bunch of arguments and they will act as variables from there on end: ```vb Public Function MyBogusFunction(intNumber1, intNumber2) ``` This function declaration already provides three variables to be used inside the function body: ``` MyBogusFunction (the return value) intNumber1 (the first argument) intNumber2 (the second argument) ``` Let's assume this function determines whether intNumber1 plus 100 is larger than twice the value of intNumber2. The function could look like this: ```vb Function MyBogusFunction(intNumber1, intNumber2) intNumber1 = intNumber1 + 100 intNumber2 = intNumber2 * 2 MyBogusFunction = (intNumber1 > intNumber2) End Function ``` Note that we do not need to declare any additional variables, we can get away with the ones that are implied by the function declaration. Since we cannot specify what kind of variable intNumber1 has to be, there is no guarantee that it is in fact a number. It might just as well be a boolean or a null or an array or something even more scary. This function is thus designed-to-fail; it will crash if we call it with improper arguments: ```vb Sub Main() Dim blnResult blnResult = MyBogusFunction(45, "Fruitbat") End Sub ``` So what if we want our function to manipulate several variables? How do we get around the 'one return value only' limitation? There are essentially four solutions to this, two of which are far too difficult for you at this moment and one of which is just stupid... try to guess which is which: 1. Use global variables 2. Declare arguments by reference 3. Return an array 4. Return a class instance We'll focus on number two for the time being. (#1 was the stupid option in case you were wondering.) Arguments in the function declaration can be declared either by value or by reference. By value means that the variable data will be copied before it enters the function body. This means a function can do whatever it likes with an argument variable, it will not affect the caller in any way. If you declare an argument to be passed in by reference however, the function knows where the variable came from and it can change the original value. Observe what happens if we do this: ```vb Call Main() Sub Main() Dim intA, intB, dblC intA = 4 intB = 7 dblC = AnotherBogusFunction(intA, intB) Call Rhino.Print("A:" & intA & ", B:" & intB & ", C:" & dblC) End Sub Function AnotherBogusFunction(ByVal intNumber1, ByVal intNumber2) intNumber1 = intNumber1 + 1 intNumber2 = intNumber2 + 2 AnotherBogusFunction = intNumber1 * intNumber2 End Function ``` Since the arguments are passed in by value, `intA` and `intB` are left intact when AnotherBogusFunction is called. Although the function received the values 4 and 7 respectively, it does not know where they came from and thus when it increments them on lines 11 and 12, the operation only applies to the local variable copies `intNumber1` and `intNumber2`. However, if we replace the `ByVal` keywords with `ByRef` we get the following result: ```vb Function AnotherBogusFunction(ByRef intNumber1, ByRef intNumber2) intNumber1 = intNumber1 + 1 intNumber2 = intNumber2 + 2 AnotherBogusFunction = intNumber1 * intNumber2 End Function ``` `intNumber1` and `intA` now both point to the exact same section in the computer memory so when we change `intNumber1` we also change the value of `intA`. Passing arguments by reference is quite a tricky thing to wrap your head around, so don't feel bad if you don't get it at first. You can rest assured that in almost all cases we'll be using the `ByVal` approach which means our data is simply copied out of harms way. There is one other reason to use `ByRef` arguments which has to do with optimization. Whenever you pass an argument to a function it will be copied in the computer memory. With small stuff like integers, vectors and shorts strings this doesn't matter, but when you start copying huge arrays back and forth you're wasting memory and processor cycles. If it turns out your script is running slowly you could consider passing arguments by reference in order to avoid casual copying. You have to be careful not to change them, or very unpredictable things will start happening. It's a bit too early for optimizations though, more on this in Chapter 9. ## Next Steps Ok, so now that you can get some functions to work, next is [conditional execution](/guides/rhinoscript/primer-101/5-conditional-execution/) to control how the script reacts to input. -------------------------------------------------------------------------------- # 5 Conditional Statements Source: https://developer.rhino3d.com/en/guides/rhinopython/primer-101/5-conditional-execution/ ## 5.1 What if? What if I were to fling this rock at that bear? What if I were to alleviate that moose from its skin and wear it myself instead? It's questions like these that signify abstract thought, perhaps the most stunning of all human traits. As a programmer, you need to take abstract thought to the next level; the very-very-conscious level. A major part of programming is recovering from screw-ups. A piece of code does not always behave in a straightforward manner and we need to catch these aberrations before they propagate too far. Other times we design our code to deal with more than one situation. In any case, there's always a lot of conditional evaluation going on, a lot of 'what if' questions. Let's take a look at three conditionals of varying complexity: 1. If the object is a curve, delete it. 2. If the object is a short curve, delete it. 3. If the object is a short curve, delete it, otherwise move it to the "curves" layer. The first conditional statement evaluates a single boolean value; an object is either is a curve or it is not. There's no middle ground. The second conditional must also evaluate the constraint 'short'. Curves don't become short all of a sudden any more than people grow tall all of a sudden. We need to come up with a boolean way of talking about 'short' before we can evaluate it. The third conditional is identical to the second one, except it defines more behavioral patterns depending on the outcome of the evaluation. The translation from English into Python is not very difficult. We just need to learn how conditional syntax works. Problem 1: ```python if (rs.IsCurve(strObjectID)): rs.DeleteObject(strObjectID) ``` Problem 2: ```python if (rs.IsCurve(strObjectID)): if (rs.CurveLength(strObjectID) < 0.01): rs.DeleteObject(strObjectID) ``` Problem 3: ```python if (rs.IsCurve(strObjectID)): if (rs.CurveLength(strObjectID) < 0.01): rs.DeleteObject(strObjectID) else: rs.ObjectLayer(strObjectID, "Curves") ``` The most common conditional evaluation is the If…Then statement. If…Then allows you to bifurcate the flow of a program. The simplest If…Then structure can be used to shield certain lines of code. It always follows the same format: ```python if (SomethingOrOther): DoSomething() DoSomethingElseAsWell() ``` The bit of code that is indented after the *if():* is evaluated and when it turns out to be True, the block of code between the first and last line will be executed. If *SomethingOrOther* turns out to be False, lines 2 and 3 are skipped and the script goes on with whatever comes after line 3. In case of very simple If…Then structures, such as the first example, it is possible to use a shorthand notation which only takes up a single line instead of three. The shorthand for If…Then looks like: ```python if (SomethingOrOther): DoSomething() ``` Whenever you need an If…Then…Else structure, you can use the following syntax: ```python if (SomethingOrOther): DoSomething() else: DoSomethingElse() ``` If *SomethingOrOther* turns out to be True, then the bit of code between lines 1 and 3 are executed. This block can be as long as you like of course. However, if *SomethingOrOther* is False, then the code after else is executed. So in the case of If…Else, one -and only one- of the two blocks of code is put to work. You can nest If…Then structures as deep as you like, though code readability will suffer from too much indenting. The following example uses four nested If…Then structures to delete short, closed curves. ```python if (rs.IsCurve(strObjectID)): if (rs.CurveLength(strObjectID) < 1.0): if (rs.IsCurveClosed(strObjectID)): rs.DeleteObject(strObjectID) ``` When you feel you need to split up the code stream into more than two flows and you don't want to use nested structures, you can instead switch to something which goes by the name of the If…Elif…Else statement. As you may or may not know, the Make2D command in Rhino has a habit of creating some very tiny curve segments. We could write a script which deletes these segments automatically, but where would we draw the line between 'short' and 'long'? We could be reasonably sure that anything which is shorter than the document absolute tolerance value can be removed safely, but what about curves which are slightly longer? Rule #1 in programming: When in doubt, make the user decide. That way you can blame them when things go wrong. A good way of solving this would be to iterate through a predefined set of curves, delete those which are definitely short, and select those which are ambiguous. The user can then decide for himself whether those segments deserve to be deleted or retained. We won't discuss the iteration part here. The conditional bit of the algorithm looks like this: ```python dblCurveLength = rs.CurveLength(strObjectID) if (dblCurveLength != None): if (dblCurveLength < rs.UnitAbsoluteTolerance()): rs.DeleteObject(strObjectID) elif (dblCurveLength < (10 * rs.UnitAbsoluteTolerance())): rs.SelectObject(strObjectID) else: rs.UnselectObject(strObjectID) ``` In Python you can say the same thing in many different ways. The above snippet could have been written as a nested If…Then structure, but then it would not resemble the way we think about the problem. ## 5.2 Looping Executing certain lines of code more than once is called looping in programming slang. There are two types of loops; conditional and incremental which can be described respectively as: ``` Keep adding milk until the dough is kneadable Add five spoons of cinnamon ``` Conditional loops will keep repeating until some condition is met where as incremental loops will run a predefined number of times. Life isn't as simple as that though, and there are many different syntax specifications for loops in Python, we'll only discuss the two most important ones in depth. ## 5.3 Conditional Loops Sometimes we do not know how many iterations we will need in advance, so we need a loop which is potentially capable of running an infinite number of times. This type is called a Do…Loop. In the most basic form it looks like this: ```python while (something is true): DoSomething() if (condition is met): break ``` All the lines indented after the while keyword will be repeated until we abort the loop ourselves. If we do not abort the loop, I.e. if we omit the break statement or if our condition just never happens to be met, the loop will continue forever. This sounds like an easy problem to avoid but it is in fact a very common bug. In Python it does not signify the end of the world to have a truly infinite loop. The following example script contains an endless While...Loop which can only be cancelled by shutting down the application. ```python import rhinoscriptsyntax as rs import datetime as dt def viewportclock(): now = dt.datetime.now() textobject_id = rs.AddText(str(now), (0,0,0), 20) if textobject_id is None: return rs.ZoomExtents(None, True) while True: rs.Sleep(1000) now = dt.datetime.now() rs.TextObjectText(textobject_id, str(now)) if __name__=="__main__": viewportclock() ``` Here's how it works:
Line Description
1 & 2 Import calls referencing external code - in this case, Rhinoscriptsyntax and datetime. We assign each of them an alias using the 'as' keyword in order simplify function calls later.
4 Main Function declaration
5 We create a time object which contains a record the date and time of the function call datetime.now().
6 We create a new Rhino Text object to display the date and time from step 5. rs.AddText (Text, point_or_plane , Height=1.0 , Font="Arial" ,font_style=0 ) Five arguments, the last three of which have default assignments, and so are optional. When adding a text object to Rhino we must specify the text string and the location for the object. There are no defaults for this. The height of the text, font name and style do have default values. However, since we're not happy with the default height, we will override it to be much bigger: textobject_id = rs.AddText(str(now), (0,0,0), 20) The Text argument must contain a String description of the current system time. We will simply nest casting function to get it. Since a cast operation for a datetime object is a well known and solid operation, we do not have to check for a Null variable and we can put it 'inline'. This will give us the date and the time. we could have pared this down to just the time by calling the *dt.datetime.time(now)* function. Neither of these return a String type variable, so before we pass it into Rhino we have to cast it to a proper String using the *str()* function. This is analogous with our code on page 20. The *point_or_plane* argument requires a list of doubles. We haven't done lists yet, but it essentially means we have to supply the x, y and z coordinates of the text insertion point. *(0,0,0)* means the same as the world origin. The default height of text objects is 1.0 units, but we want our clock to look big since big things look expensive. Therefore we're overriding it to be 20 units instead.
7 I don't think there's anything here that could possibly go wrong, but it never hurts to be sure. Just in case the text object hasn't been created we need to abort the subroutine in order to prevent an error later on.
9 We start an infinite While... loop, lines 10, 11 and 12 will be repeated for all eternity.
10 There's no need to update our clock if the text remains the same, so we really only need to change the text once every second. The *Rhino.Sleep()* method will pause Rhino for the specified amount of milliseconds. We're forcing the loop to take it easy, by telling it to take some time off on every iteration. We could remove this line and the script will simply update the clock many times per second. This kind of reckless behaviour will quickly flood the undo buffer.
11 Here we update our now object. This will give us an updated datetime object.
12 This is the cool bit. Here we replace the text in the object with a new String representing the current system time.
14 & 15 This is where the viewport clock function is called. In IronPython, the main function call must be executed after the definition of the function. The if __name__ == "__main__": ... trick exists in Python so that our Python files can act as either reusable modules, or as standalone programs.
A simple example of a non-endless loop which will terminate itself would be an iterative scaling script. Imagine we need a tool which makes sure a curve does not exceed a certain length {L}. Whenever a curve does exceed this predefined value it must be scaled down by a factor {F} until it no longer exceeds {L}. This approach means that curves that turn out to be longer than {L} will probably end up being shorter than {L}, since we always scale with a fixed amount. There is no mechanism to prevent undershooting. Curves that start out by being shorter than {L} should remain untouched. A possible solution to this problem might look like this: ```python # Iteratively scale down a curve until it becomes shorter than a certain length import rhinoscriptsyntax as rs def fitcurvetolength(): curve_id = rs.GetObject("Select a curve to fit to length", 4, True, True) if curve_id is None: return length = rs.CurveLength(curve_id) length_limit = rs.GetReal("Length limit", 0.5 * length, 0.01 * length, length) if length_limit is None: return while True: if rs.CurveLength(curve_id)<=length_limit: break curve_id = rs.ScaleObject(curve_id, (0,0,0), (0.95, 0.95, 0.95)) if curve_id is None: print("Something went wrong...") return print("New curve length: ", rs.CurveLength(curve_id)) if __name__=="__main__": fitcurvetolength() ```
Line Description
1...4 This should be familiar by now
5 Prompt the user to pick a single curve object, we're allowing preselection.
6 Check that the user picked an object, and that its id was written to our curve_id variable. Exit if not.
8 Retrieve the current curve length. This function should not fail, no need to check for Null.
10 Prompt the user for a length limit value. The value must be chosen between the current curve length and 1% of the current curve length. We're setting the default to half the current curve length.
13 Start a While... loop
14 This is the break-away conditional. If the curve length no longer exceeds the preset limit, the break statement will take us directly to line 20.
15 If the length of the curve did exceed the preset limit, this line will be executed. The rs.ScaleObject() method takes four arguments, the last one of which is optional. We do not override it. We do need to specify which object we want rescaled (curve_id), what the center of the scaling operation will be ((0,0,0); the world origin) and the scaling factors along x, y and z (95% in all directions).
19 This line ends all indented code, which instructs the interpreter to go back to line 13
21 Eventually all curves will become shorter than the limit length and the While... loop will abort. We print out a message to the command line informing the user of the new curve length.
## 5.4 Incremental Loops When the number of iterations is known in advance, we could still use a While…Loop statement, but we'll have to do the bookkeeping ourselves. This is rather cumbersome since it involves us declaring, incrementing and evaluating variables. The For...statement is a loop which takes care of all this hassle. The underlying idea behind For... loops is to have a value incremented by a fixed amount every iteration until it exceeds a preset threshold: ```python group = 10 for item in group: AddSpoonOfCinnamon() ``` This loop will operate for each item in the group, adding a spoon of cinnamon and will exit when we come to the last item of the group. We can also use the range() function for more control: ```python for i in range(A,B,N): AddSpoonOfCinnamon() ``` The variable i starts out by being equal to A and it is incremented by N until it becomes 1 less than B. In other words, B is the total amount that you want to increment up to. Remember, that in programming, we always start with 0, therefore the total increment amount will be 1 more than we actually want! N signifies the "Step" value which is optional and if we do not override it the default stepsize of 1.0 will be used. If we have a stepsize of 2, it will increment every-other time. In the example above the variable i is not used in the loop itself, we're using it for counting purposes only. If we want to abort a For... loop ahead of time, we can use break in order to short-circuit the process. Creating mathematical graphs is a typical example of the usage of For…Loops: ```python # Draw a sine wave using points import rhinoscriptsyntax as rs import math def drawsinewave(): a = -8.0 b = 8.0 step = 0.25 x = a while x<=b: y = 2*math.sin(x) rs.AddPoint( (x,y,0) ) x += step if __name__=="__main__": drawsinewave() ``` The above example draws a sine wave graph in a certain numeric domain with a certain accuracy. There is no user input since that is not the focus of this paragraph, but you can change the values in the script. The numeric domain we're interested in ranges from -8.0 to +8.0 and with the current stepsize of 0.25 that means we'll be running this loop 64 times. 64 = Step-1 × (B - A)) The For…loop will increment the value of x automatically with the specified stepsize, so we don't have to worry about it when we use x on line 10. We should be careful not to change x inside the loop since that will play havoc with the logic of the iterations. Loop structures can be nested at will, there are no limitations, but you'll rarely encounter more than three. The following example shows how nested For…Loops can be used to compute distributions: ```python import rhinoscriptsyntax as rs import math def twistandshout(): twist_angle = 0.0 rs.EnableRedraw(False) z = 0 while z<=5: twist_angle += math.pi/30 a = 0 while a<2*math.pi: x = 5 * math.sin(a + twist_angle) y = 5 * math.cos(a + twist_angle) rs.AddSphere((x,y,z), 0.5) a += math.pi/15 z += 0.5 rs.EnableRedraw(True) if __name__=="__main__": twistandshout() ``` The master loop increments the z variable from 0.0 to 5.0 with a default step size of 0.5. The z variable is used directly as the z-coordinate for all the sphere centers. For every iteration of the master loop, we also want to increment the twist angle with a fixed amount. We can only use the For…Loop to automatically increment a single variable, so we have to do this one ourselves on line 8. The master loop will run a total of ten times and the nested loop is designed to run 30 times. But because the nested loop is started every time the master loop performs another iteration, the code between lines 11 and 14 will be executed 10×30 = 300 times. Whenever you start nesting loops, the total number of operations your script performs will grow exponentially. The *rs.EnableRedraw()* calls before and after the master loop are there to prevent the viewport from updating while the spheres are inserted. The script completes much faster if it doesn't have to redraw 330 times. If you comment out the *rs.EnableRedraw()* call you can see the order in which spheres are added, it may help you understand how the nested loops work together. ## Next Steps Now it should be coming together on how Python works. Just a few more details. Learn more about Python's advanced variables in [Tuples, Lists, and Dictionaries](/guides/rhinopython/primer-101/6-tuples-lists-dictionaries/). -------------------------------------------------------------------------------- # 6 Tuples, Lists, and Dictionaries Source: https://developer.rhino3d.com/en/guides/rhinopython/primer-101/6-tuples-lists-dictionaries/ ## 6.1 Tuples We've already been using tuples and lists in examples and I've always told you not to worry about it. Those days are officially over. Now is the time to panic. Perhaps it's best if we just get the obvious stuff out of the way first: **Tuples, Lists and Dictionaries are just a collection of things!** That's really all there is to it. Sometimes -in fact quite often- you want to store a large or unknown amount of things. You could of course declare 15,000 different variables by hand but that is generally considered to be bad practice. Remember, Tuples, Lists and Dictionaries always start counting at zero, while us humans are normally used to start counting at one. Try it by counting the number of fingers on your right hand. Chances are you are someone who has just counted to five. Code would disagree with you, it would only have counted to four: It helps to refer to numbers as 'indices' when you use the zero-based counting system just to avoid confusion. So when we talk about the 'first element' of a tuple we actually mean 'the element with index 0'. I know this all sounds strange, but zero-based counting systems habitually confuse even the most die-hard programmer. A tuple consists of a number of values separated by commas, for instance: ```python t = 12345, 5421, 'hello!' # Creating a Tuple with a variable name t print(t[0]) # print the first value of the Tuple t # This returns 12345 - the first value inside the Tuple print(t) # This returns (12345, 54321, 'hello!') - all of the values within the Tuple ``` Tuples can be used for coordinates (x,y) or any other time you need to store various elements. Tuples may contain multiple variables, nested Tuples, Lists or other objects. If you remember from section 4.5, Tuples are immutable, meaning that they cannot be changed! (Refer to section 4.5 on Mutability) That means that we cannot create a Tuple then remove an element, instead we need to create an entirely new Tuple that contains the desired change. A number of RhinoscriptSyntax methods return tuples rather than lists for their simplicity. When utilizing the RhinoscriptSyntax methods make sure to be particularly careful with the input and return types (numbers, strings, tuples, lists etc) and understand how to pass or use each of them. This is a common source of errors in people's code, so pay special attention to read the methods found in the RhinoscriptSyntax help file! ## 6.2 Lists Lists are just like Tuples, however they can be changed (mutable), they use brackets rather than parenthesis and have far more powerful built-in functionality! Lists can be added to, items can be removed, they can be sorted, sliced apart, nested with multiple levels of inner lists and packed with other objects. Lists are very powerful! ```python myList = [] #This creates an empty list with the variable name myList myList.append(5) myList.append(6) print myList[0] # This returns 5 - the first element (0th item) in the list print(myList) # This returns [5,6] - the entire contents of the list myList ``` Lists also can be sliced by using the following syntax: ```python myList = [1,2,3,4] #This creates a list with elements 1,2,3,4 print(myList[1:3]) #This returns [2,3] - the 1st and 2nd elements of the list ``` Similar to the range() function, the syntax for slicing list[start:end] - begins with the index of "start" (from the 0th element) and finishes at 1 less than the index "end." To create a copy of a list we can also use a similar syntax: ```python myList = [1,2,3,4] #This creates a list with elements 1,2,3,4 dupList = myList[:] ``` Some useful methods for lists: | Method | | | Description | | :--------------- | :--: | :--: | :--------------------------------------- | | list.append(x) | | | Adds an item to the end of a list. | | list.insert(i,x) | | | Inserts item i at location x. | | list.remove(x) | | | Removes the first item from the list who's value is x. | | list.count(s) | | | Counts how many times an item x is found within the list. | | list.append(x) | | | Adds an item to the end of a list. | | list.sort() | | | Sorts the elements of the list. | | list.reverse() | | | Reverses the elements of the list. | ### 6.2.1 List Comprehension List comprehensions are a way of utilizing the functionality of Lists and For...Loops with very concise syntax. The list comprehension begins with an expression then has a For...Loop - effectively executing the expression for the number of times specified in the For...Loop. This will create a List with the resultant values from the expression. For example: ```python myList = [2,4,6] #This creates a list with elements 2,4,6 print([3*x for x in myList]) # This returns [6,12,18] as the resultant calculation from the List Comprehension ``` List comprehensions can become far more complex and include more complicated expressions, loops and conditional statements. One last example: ```python myList = [2,4,6] #This creates a list with elements 2,4,6 print([3*x for x in myList if x>3]) # This returns [12,18] as the resultant calculation from the expression, loop and conditional ``` The following example should teach you almost all there is to know about lists, except nesting: ```python import rhinoscriptsyntax as rs def myfavoritethings(): things = [] while True: count = len(things) prompt = "What is your {}th most favorite thing?".format(count+1) if len(things)==0: prompt = "What is your most favorite thing?" elif count==1: prompt = "What is your second most favorite thing?" elif count==2: prompt = "What is your third most favourite thing?" answer = rs.GetString(prompt) if answer is None: break things.append(answer) if len(things)==0: return print("Your", len(things)+1, "favorite things are:") for i,thing in enumerate(things): print(i+1, ".", thing) if __name__=="__main__": myfavoritethings() ```
Line Description
4 We do not know how many favourite things the user has, so there's no way we can set the list to a certain size in advance. Luckily, we do not have to. Items can be appended to a list on an as needed basis!
8 The function len() returns the length of a list object. The very first time this line is run, count will be 0.
19 If the user does not enter an answer to our question regarding his/her Nth favorite thing, we will exit the loop and move into the last task of the script on lines 21 and 22.
20 We've just asked the user what his/her Nth favourite thing was, and he/she answered truthfully. This means that it is time to store the answer in a safe place. A list is a convenient place to store an arbitrarily long collection of data. The append function shown will add the entry to the end of the list.
21 It is possible the user has not entered any String. If this is the case then the result of len(things) will still have a its initial value of zero. There is nothing for us to do in this case and we should abort the subroutine.
23 After the loop has completed and we've made certain the array contains some actual data, we print all the gathered information to the command history. First we will tell the user how many favourite things he/she entered.
24 Using a For...loop, we can iterate through the items in the list. Note that this For...loop has two iteration variables - one to keep track of the index of the list item, and one to get the actual list item. This is convenient, as it is not necessary to explicitly retrieve the item in the list using the index. We then print the index of the user's nth favority thing, and the favorite thing.
## 6.3 Dictionaries Dictionaries go one step further than lists because they store a "key" and an associated value. Every dictionary contains a series of key : value associations. This is very similar to actual word-based dictionaries that contain words and their associated definition. Python Dictionaries can use any immutable type for the "key", such as; strings, numbers, Tuples (they can only contain strings, numbers or tuples and NOT lists). The value can be any mutable or immutable item that will become associated to the key (this includes, lists or other dictionaries). Lets see an example: ```python emptyDict = {} #This creates an empty Dictionary myDict = {'a':1,'b':2,'c':3} #This creates a Dictionary with its associated Key:Value pairs myDict['d'] = 4 #This Adds a key:value to the Dictionary myDict['a'] = 2 #This Changes the key "a"'s value to 2 rather than 1 print (myDict['a']) #This returns 2 - the associated value to the key "a" print (myDict) #This returns {'a': 2, 'c': 3, 'b': 2, 'd': 4} - the entire Dictionary print (myDict.keys()) #This returns ['a', 'c', 'b', 'd'] - a list containing all of the Keys del myDict['b'] # This deletes the Key "b" and its associated value of 2 ``` Because of the associated Key:Value relationship, Dictionaries are great for representing points and giving them an associated name based on the points relationship to their neighbors. Let's say we have a surface and we want to extract points across the surface with rows and columns. Each point has a 3D point coordinate [x,y,z] which is represented as a list who's first element 0 corresponds to x, element 1 corresponds to y and element 2 corresponds to z. If we wanted to store these points we have 2 options. We could create a list with internal lists that contain the point coordinates (one long linear organization of points) or we could use a Dictionary to add point coordinates as values and have a Key that is a name in relation to its Row/Column position. The second option then allows us to take any point on the surface and know its 4, 8 or however many neighbors because they are named accordingly. The first option would only allow us to know the neighbor direction in front or behind it in line. We will get into this more later, but this should wet your appetite for the exciting potential of lists and dictionaries for geometric information! ## 6.4 Points and Vectors As was just explained, points are represented by lists containing three values - [x,y,z]. This notation is used for both points and vectors. First, a point example: ```python import rhinoscriptsyntax as rs import math def pointspiral(): t = -5 while t<=5: point = t*math.sin(5*t), t*math.cos(5*t), t print(point) rs.AddPoint(point) t+=0.025 if __name__=="__main__": pointspiral() ``` The variable arrPoint is declared as an empty list, the elements are assigned different values on lines 7 to 9 inside the body of the loop. On line 10 the list is printed and on line 11 it is used as the point coordinates to create a 3D point in space. Vectors are a slightly new concept. Those of you who are familiar with the essentials of geometrical mathematics will have no problems with this concept... in fact you probably all are familiar with the essentials of geometrical mathematics or you wouldn't be learning how to program a 3D CAD platform. Vectors are indistinguishable from points. That is, they are both lists of three numbers so there's absolutely no way of telling whether a certain list represents a point or a vector. There is a practical difference though; points are absolute, vectors are relative. When we treat a list of three doubles as a point it represents a certain coordinate in space, when we treat it as a vector it represents a certain direction. You see, a vector is an arrow in space which always starts at the world origin (0.0, 0.0, 0.0) and ends at the specified coordinate. The picture on the right shows two vector definitions; a purple and a blue one. The blue one happens to have all positive components while the purple one has only negative components. Both vectors have a different direction and a different length. When I say vectors are relative, I mean that they only indicate the difference between the start and end points of the arrow, i.e. vectors are not actual geometrical entities, they are only information! The blue vector could represent the tangent direction of the black curve at parameter {t}. If we also know the point value of the curve at parameter {t}, we know what the tangent of the curve looks like; we know where in space the tangent belongs. The vector itself does not contain this information; the orange and the blue vector are identical in every respect. The addition of vector definitions in IronPython is accompanied by a whole group of point/vector related methods which perform the basic operations of 'vector mathematics'. Addition, subtraction, multiplication, dot and cross products, so on and so forth. The table on the following page is meant as a reference table, do not waste your time memorizing it. I will be using standard mathematical notation: - A lowercase letter represents a number - A lowercase letter with a dot above it represents a point - A lowercase letter with an arrow above it represents a vector - Vertical bars are used to denote vector length
Notation Implementation Description Example
$$d =|\dot{p}-\dot{r}|$$ Distance(Pt1, Pt2) Compute the distance between two points.
$$\dot{r} = a \times \dot{p}$$ PointScale(Pt1, dblA) Multiply the components of the point by the specified factor. This operation is the equivalent of a 3DScaling around the world origin.
$$\dot{r} = \frac{\dot{p}}{a}$$ PointDivide(Pt1, dblA) Divide the components of the point by the specified factor. This is the equivalent of PointScale(Pt1, a-1).
$$? \dot{r} = \dot{p} \pm t$$ PointCompare(Pt1, Pt2, dblT) Check to see if two points are more or less identical. Two points are identical if the length of the vector between them is less than the specified tolerance.
$$\dot{r} = \dot{p} \times \mathbb{M}$$ PointTransform(Pt1, arrM) Transform the point using a linear transformation matrix.
$$\overrightarrow{w} = \left(\frac{1}{|\overrightarrow{v}|} \right) \times \overrightarrow{v}$$ VectorUnitize(Vec1) Divide all components by the inverse of the length of the vector. The resulting vector has a length of 1.0 and is called the unit-vector. Unitizing is sometimes referred to as "normalizing".
$$l = |\overrightarrow{v}|$$ VectorLength(Vec1) Compute the square root of the sum of the squares of all the components. Standard Pythagorean distance equation.
$$\overrightarrow{w} = -\overrightarrow{v}$$ VectorReverse(Vec1) Negate all the components of a vector to invert the direction. The length of the vector is maintained.
$$? \overrightarrow{w} = \overrightarrow{v} \pm t$$ VectorCompare(Vec1, Vec2, dblT) Check to see if two vectors are more or less identical. This is the equivalent of PointCompare().
$$\overrightarrow{w} =\frac{\overrightarrow{v}}{a}$$ VectorDivide(Vec1, dblA) Divide the components of the vector by the specified factor. This is the equivalent of *PointDivide()*.
$$\dot{r} = \dot{p} + \overrightarrow{v}$$ PointAdd(Pt1, Vec1) Add the components of the vector to the components of the point. Point-Vector summation is the equivalent of moving the point along the vector.
$$\dot{r} = \dot{p} - \overrightarrow{v}$$ PointSubtract(Pt1, Vec1) Subtract the components of the vector from the components of the point. Point-Vector subtraction is the equivalent of moving the point along the reversed vector.
$$\overrightarrow{v} = \dot{p} - \dot{r}$$ PointSubtract(Pt1, Vec1) Subtract the components of the vector from the components of the point. Point-Vector subtraction is the equivalent of moving the point along the reversed vector.
$$\overrightarrow{u} = \overrightarrow{v} + \overrightarrow{w}$$ VectorAdd(Vec1, Vec2) Add the components of Vec1 to the components of Vec2. This is equivalent to standard vector summation.
$$\overrightarrow{u} = \overrightarrow{v} - \overrightarrow{w}$$ VectorSubtract(Vec1, Vec2) Subtract the components of Vec1 from the components of Vec2. This is equivalent of *VectorAdd(Vec1, -Vec2)*.
$$\alpha = \overrightarrow{v} \times \overrightarrow{w}$$ VectorDotProduct(Vec1, Vec2) -or- VectorMultiply(Vec1, Vec2) Calculate the sum of the products of the corresponding components. In practical, everyday-life the DotProduct can be used to compute the angle between vectors since the DotProduct of two vectors v and w equals: |v||w| cos(a)
$$\overrightarrow{u} = \overrightarrow{v} \times \overrightarrow{w}$$ VectorCrossProduct(Vec1, Vec2) The cross-product of two vectors v and w, is a third vector which is perpendicular to both v and w.
$$\overrightarrow{u} = \overrightarrow{v} \times (\sphericalangle\alpha)\overrightarrow{w}$$ VectorRotate(Vec1, dblA, VecA) Rotate a vector a specified number of degrees around an axis-vector.
IronPython has no method for displaying vectors, which is a pity since this would be very useful for visual feedback. I shall define a function here called *AddVector()* which we will use in examples to come. The function must be able to take two arguments; one vector definition and a point definition. If the point array is not defined the vector will be drawn starting at the world origin. ```python def AddVector(vecdir, base_point=[0,0,0]): tip_point = rs.PointAdd(base_point, vecdir) line = rs.AddLine(base_point, tip_point) if line: return rs.CurveArrows(line, 2) ```
Line Description
1 Standard function declaration. The function takes two arguments, if the first one does not represent a proper vector array the function will not do anything, if the second one does not represent a proper point array the function will draw the vector from the world origin.
2 Declare and compute the coordinate of the arrow tip. This will potentially fail if ptBase or vecDir are not proper arrays. However, the script will continue instead of crash due to the exception handling.
3 Here we are calling the RhinoScriptSyntax method rs.AddLine() and we're storing the return value directly into the line variable. There are three possible scenarios at this point:
  1. The method completed successfully
  2. The method failed, but it didn't crash
  3. The method crashed
In the case of scenario 1, the line variable now contains the object ID for a newly added line object. This is exactly what we want the function to return on success. In case of scenario #2, the line variable will be set to None. The last option means that there was no return value for AddLine() and hence line will also be None.
4 Check for scenario 2 and 3, and if they did not occur, go ahead and and add an arrow head using the CurveArrows method. If they did, this method will not be exectuted, and the script simply does not returns *None*.
## 6.5 An AddVector() example ```python # This script will compute a bunch of cross-product vector based on a pointcloud import rhinoscriptsyntax as rs def vectorfield(): cloud_id = rs.GetObject("Input pointcloud", 2, True, True) if cloud_id is None: return points = rs.PointCloudPoints(cloud_id) base_point = rs.GetPoint("Vector field base point") if base_point is None: return for point in points: vecbase = rs.VectorCreate(point, base_point) vecdir = rs.VectorCrossProduct(vecbase, (0,0,1)) if vecdir: vecdir = rs.VectorUnitize(vecdir) vecdir = rs.VectorScale(vecdir, 2.0) AddVector(vecdir, point) def AddVector(vecdir, base_point): tip_point = rs.PointAdd(base_point, vecdir) line = rs.AddLine(base_point, tip_point) if line: rs.CurveArrows(line, 2) if __name__=="__main__": vectorfield() ```
Line Description
9 The listpoints variable is a list which contains all the coordinates of a pointcloud object. This is an example of a nested list (see paragraph 6.6).
13 The variable point, which is taken from the listpoints variable, contains an array of three doubles; a standard Rhino point definition. We use that point to construct a new vector definition which points from the Base point to arrPoints(i).
14 The *rs.VectorCrossProduct()* method will return a vector which is perpendicular to vecBase and the world z-axis. If you feel like doing some homework, you can try to replace the hard-coded direction ([0,0,1]) with a second variable point a la *base_point*.
15 rs.VectorCrossProduct() will fail if one of the input vectors is zero-length or if both input vectors are parallel. In those cases we will not add a vector to the document.
17 & 18 Here we make sure the vecdir vector is two units long. First we unitize the vector, making it one unit long, then we double the length.
19 Finally, place a call to the AddVector() function we defined on page 40. If you intend to run this script, you must also include the AddVector() function in the same script.
## 6.6 Nested Lists > I wonder why, I wonder why. > I wonder why I wonder. > I wonder why I wonder why. > I wonder why I wonder. > -Richard P. Feynman- There's nothing to it. A list (or tuple or dictionary for that matter) becomes nested when it is stored inside another list The VectorField example on the previous page deals with a list of points (a list of lists, each with three doubles). The image on the right is a visualization of such a structure. The left most column represents the base list, the one containing all coordinates. It can be any size you like, there's no limit to the amount of points you can store in a single list. Every element of this base list is a standard Rhino point. In the case of point-lists all the nested lists are three elements long, but this is not a requisite, you can store anything you want in a list. Nesting can be done with tuples, lists or dictionaries. It simply means that you can put lists in lists, or tuples in tuples, dictionaries in dictionaries or even lists inside of dictionaries and so on. Nesting can be done infinitely, you can have a list that contains a list with a list inside of it, another list inside of that list and so on. Nesting can easily be done by utilizing a Loop that allows you to iterate and either extract or place other items inside of the lists. Accessing nested lists follows the same rules as accessing regular lists. Using the VectorField example: ```python arrSeventhPoint = arrPoints[6] #arrSeventhPoint now equals the 7th (starting from 0th) element arrLastPoint = arrPoints[len(arrPoints)] #arrLastPoint now equals the last point in the list ``` Len() can be used to get the length of a list. In this case we are saying that arrLastPoint equals the last element in the list called arrPoints because we have given it the numeric value that is the length of the list. This shows how to extract entire nested lists. Assuming the illustration on this page represents arrPoints, arrSeventhPoint will be identical to [0.3, -1.5, 4.9]. If we want to access individual coordinates directly we can use another bracket to explode out the z value: ```pyton dblSeventhPointHeight = arrPoints[6][2] #2 corresponds to the 3rd element (the Z coordinate) within that nested list. ``` The above code will store the third element of the nested list stored in the seventh element of the base list in *dblSeventhPointHeight*. This corresponds with the orange block. Nested lists can be parsed using nested loops like so: ```python for i in range(0,len(arrPoints)): for j in range(0,2): print("Coordinate(" + i + ", " + j + ") = " + arrPoints[i][j]) ``` Remember the scaling script from before? We're now going to take curve-length adjustment to the next level using nested lists. The logic of this script will be the same, but the algorithm for shortening a curve will be replaced with the following one (the illustration shows the first eight iterations of the algorithm): Every control-point or 'vertex' of the original curve (except the ones at the end) will be averaged with its neighbors in order to smooth the curve. With every iteration the curve will become shorter and we will abort as soon a certain threshold length has been reached. The curve can never become shorter than the distance between the first and last control-point, so we need to make sure our goals are actually feasible before we start a potentially endless loop. Note that the algorithm is approximating, it may not be endless but it could still take a long time to complete. We will not support closed or periodic curves. We're going to put the vector math bit in a separate function. This function will compute the {vM} vector given the control points {pN-1; p; pN+1} and a smoothing factor {s}. Since this function is not designed to fail, we will not be adding any error checking, if the thing crashes we'll have to fix the bug. Instead of using variable naming conventions, I'll use the same codes as in the diagram: ```python def smoothingvector(point, prev_point, next_point, s): pm = (prev_point+next_point)/2.0 va = rs.VectorCreate(pm, point) vm = rs.VectorScale(va, s) return vm ```
Line Description
2 The smoothingvector function definition takes input of Rhino.Point3d. This object type allows for explicit point addition. The act of adding two Point3d objects together results in vector addition of the two points. The act of dividing the resulting point by 2.0 simply divides the three components (x,y and z) by 2.
3 The VectorCreate() function is called in order to obtain a Rhino.Vector3d with information about the directional components of a vector between points Pm, and point, P being the origin. The math is effectively the same as Pm - Point = Va, but this operation would not have yielded a Rhion.Vector3d object. The VectorCreate() function creates this object efficiently.
4 Finally, we call the Rhino.VectorScale() function, which takes a Rhino.Vector3d object, and scales it according to a predetermined scaling factor 's'. When we use this algorithm, we must make sure to set 's' to be something sensible, or the loop might become endless: 0.0 1 {s} # 1.0
5 We return the vector vm.
We'll also put the entire curve-smoothing algorithm in a separate function. Since it's fairly hard to adjust existing objects in Rhino, we'll be adding a new curve and deleting the existing one: ```python def smoothcurve(curve_id, s): curve_points = rs.CurvePoints(curve_id) new_curve_points = [] new_curve_points.append(curve_points[0]) for i in range(1, len(curve_points)-1): vm = smoothingvector(curve_points[i], curve_points[i-1], curve_points[i+1], s) new_curve_points.append( rs.PointAdd(curve_points[i], vm) ) new_curve_points.append(curve_points[-1]) knots = rs.CurveKnots(curve_id) degree = rs.CurveDegree(curve_id) weights = rs.CurveWeights(curve_id,0) newcurve_id = rs.AddNurbsCurve(new_curve_points, knots, degree, weights) if newcurve_id: rs.DeleteObject(curve_id) return newcurve_id ```
Line Description
2 Retrieve the nested list of curve control points, and store it in curve_points.
3 We need a second list to contain the new points, while leaving the initial curve_points list intact.
5 This loop will start at one and stop one short of the length of the curve_points list. In other words, we're skipping the first and last items in the array.
7 Compute the smoothing vector using the current control point, the previous one (i-1) and the next one (i+1). Since we're omitting the first and last point in the array, every point we're dealing with has two neighbors.
8 Set the new control point position. The new coordinate equals the old coordinate plus the smoothing vector.
9...10 We'll be adding a new curve to the document which is identical to the existing one, but with different control point positions. A nurbs curve is defined by four different blocks of data: control points, knots, weights and degree (see paragraph 7.7 Nurbs Curves). We just need to copy the other bits from the old curve.
14 Create a new nurbs curve and store the object ID in the variable newcurve_id.
15 Delete the original curve.
The top-level subroutine doesn't contain anything you're not already familiar with: ```python def iterativeshortencurve(): curve_id = rs.GetObject("Open curve to smooth", 4, True) if curve_id is None or rs.IsCurveClosed(curve_id): return min = rs.Distance(rs.CurveStartPoint(curve_id), rs.CurveEndPoint(curve_id)) max = rs.CurveLength(curve_id) goal = rs.GetReal("Goal length", 0.5*(min+max) , min, max) if goal is None: return while rs.CurveLength(curve_id)>goal: rs.EnableRedraw(False) curve_id = smoothcurve(curve_id, 0.1) rs.EnableRedraw(True) if curve_id is None: break ``` ## Next Steps Tuples, Lists and Dictionaries are very powerful in Python. Let's make a quick stop to learn about [class syntax](/guides/rhinopython/primer-101/7-classes/#class-syntax). -------------------------------------------------------------------------------- # 7 Classes Source: https://developer.rhino3d.com/en/guides/rhinopython/primer-101/7-classes/ ## 7.1 Class Syntax Classes are useful mechanism for organization above what we have already mentioned: variables, flow control and functions. Classes give us another level of functionality and actually define a specific type of programming called Object-Oriented Programming. This means that we can create objects, rather than simply procedures or variables. Object-Oriented Programming gives us the ability to create a class with internal attributes, functions and any number of other characteristics and then create multiple instances. Classes offer you a possibility to organize your code based on objects, these objects can relate to one another with inheritance, add or remove information/characteristics through functions and actually exhibit "polymorphism"! (Sounds fancy!) Polymorphism, another exciting feature of Object-Orient Programming, is the ability to create one object or class that can exhibit multiple characteristics and commonly respond to similar functions. For example, if we have a function that asks for a "Person's" age and returns their birth year, we could pass in a "Professor" or we could pass in a "Person" although they are actually different objects they can respond to the same question (this is like acting as different people at different times, depending on the question)! Classes can be used describe geometry - i.e. a surface is an object that has multiple characteristics, curvature, centroid, number of U & V points etc. We can also ask for information about a surface based on functions embedded within the class; like returning the surface area or the bounding box etc! We can also create our own types of objects like "Connections" or "Apertures" etc - with functionality and specific attributes, while each one being slightly unique! Classes are very powerful, but at first glance are often difficult to wrap your head around!! Ok, enough talking - lets see some code (because that's much easier to understand...)! To create a class the syntax is: ```python class MyClass: """A simple example""" x = 10 def test(self): return 'hello' obj = MyClass() print(obj.x) print(obj.test()) ```
Line Description
1 Standard class declaration, this class is called "MyClass"
2 Standard class declaration, this class is called "MyClass"
3 Declare a variable x = 10.
4 Create a function within the class that returns 'hello'
7 Create an instance of MyClass() called obj
8 This print statement will return >> 10
9 This print statement will return >> 'hello'
Now, if we change the value of x and print the result: ```python obj.x = 5 print(obj.x) ``` ``` >> 5 ``` The result is 5, showing that we can change the attributes of an object and call functions outside of the class! Regarding the strange use of "self", the Python documentation explains, *" Often, the first argument of a method is called self. This is nothing more than a convention: the name self has absolutely no special meaning to Python. Note, however, that by not following the convention your code may be less readable to other Python programmers, and it is also conceivable that a class browser program might be written that relies upon such a convention."* Often, a class will have a function called __init__ - this forces the class to give certain attributes whenever it is created (rather than adding them later). For example: ```python class Harder: def __init__(self,m,n): self.i = m self.j = n newObj = Harder(10,20) print(newObj.i) print(newObj.j) ```
Line Description
1 Standard class declaration, this class is called "Harder"
2 This line initializes a number of variables that must be described when creating an instance of the class
3-4 Sets internal variables i & j to the input variables m & n
5 Creates an instance of the class Harder() called newObj
6 The print call returns the value of i (which was initially m) = 10
7 The print call returns the value of j (which was initially n) = 20
Now for an example of inheritance, or the ability for a class to take on the qualities of another class, yet have its own differences (Polymorphism!): ```python class Weird(MyClass): k = 17 newerObj = Weird() print(newerObj.test()) ```
Line Description
1 Standard class declaration, this class is called "Weird." However, this time we put another class in parenthesis - this means that it inherits all of the properties of the class, MyClass()!
2 Standard variable k = 17
4 Creates an instance of the class Weird() called newerObj
5 The print call returns 'hello'! (Weird...right!?) This means that it referenced the previous class and returned 'hello', because Weird is an child of MyClass()
You can use isinstance(newerObj,MyClass) to check if one object is an instance of another object. Classes can be nested, they can have multiple functions, create powerful systems with polymorphism, privacy and modularity of your code! Like I said, classes are very powerful and sometimes difficult to wrap your head around at first (don't get hung up on them....work your way into it)! We are certainly not doing them justice here by explaining them in just 2 short pages! However, their complete depths are certainly out of the scope of this primer and you can find more information on Python classes from the resources at the beginning of this primer or at: [http://docs.python.org/tutorial/classes.html](http://docs.python.org/tutorial/classes.html) ## Next Steps Classes are the last of the pure Python units. The last chapter is all about [geometry](/guides/rhinopython/primer-101/8-geometry/) in Rhino and Python. -------------------------------------------------------------------------------- # 8 Geometry Source: https://developer.rhino3d.com/en/guides/rhinopython/primer-101/8-geometry/ ## 8.1 The openNURBS™ Kernel Now that you are familiar with the basics of scripting, it is time to start with the actual geometry part of Rhino. To keep things interesting we've used plenty of Rhino methods in examples before now, but that was all peanuts. Now you will embark upon that great journey which, if you survive, will turn you into a real 3D geek. As already mentioned in Chapter 3, Rhinoceros is built upon the openNURBS™ kernel which supplies the bulk of the geometry and file I/O functions. All plugins that deal with geometry tap into this rich resource and the RhinoScriptSytnax plugin is no exception. Although Rhino is marketed as a "NURBS modeler", it does have a basic understanding of other types of geometry as well. Some of these are available to the general Rhino user, others are only available to programmers. When writting in Python you will not be dealing directly with any openNURBS™ code since RhinoScriptSyntax wraps it all up into an easy-to-swallow package. However, programmers need to have a much higher level of comprehension than users which is why we'll dig fairly deep. ## 8.2 Objects in Rhino All objects in Rhino are composed of a geometry part and an attribute part. There are quite a few different geometry types but the attributes always follow the same format. The attributes store information such as object name, color, layer, isocurve density, linetype and so on. Not all attributes make sense for all geometry types, points for example do not use linetypes or materials but they are capable of storing this information nevertheless. Most attributes and properties are fairly straightforward and can be read and assigned to objects at will. This table lists most of the attributes and properties which are available to plugin developers. Most of these have been wrapped in the RhinoScriptSyntax module, others are missing at this point in time and the custom user data element is special. We'll get to user data after we're done with the basic geometry chapters. The following procedure displays some attributes of a single object in a dialog box. There is nothing exciting going on here so I'll refrain from providing a step-by-step explanation. ```python import rhinoscriptsyntax as rs def displayobjectattributes(object_id): source = "By Layer", "By Object", "By Parent" data = [] data.append( "Object attributes for :"+str(object_id) ) data.append( "Description: " + rs.ObjectDescription(object_id)) data.append( "Layer: " + rs.ObjectLayer(object_id)) #data.append( "LineType: " + rs.ObjectLineType(object_id)) #data.append( "LineTypeSource: " + rs.ObjectLineTypeSource(object_id)) data.append( "MaterialSource: " + str(rs.ObjectMaterialSource(object_id))) name = rs.ObjectName(object_id) if not name: data.append("") else: data.append("Name: " + name) groups = rs.ObjectGroups(object_id) if groups: for i,group in enumerate(groups): data.append( "Group(%d): %s" % i+1, group ) else: data.append("") s = "" for line in data: s += line + "\n" rs.EditBox(s, "Object attributes", "RhinoPython") if __name__=="__main__": id = rs.GetObject() displayobjectattributes(id) ``` ## 8.3 Points and Pointclouds Everything begins with points. A point is nothing more than a list of values called a coordinate. The number of values in the list corresponds with the number of dimensions of the space it resides in. Space is usually denoted with an R and a superscript value indicating the number of dimensions. (The 'R' stems from the world 'real' which means the space is continuous. We should keep in mind that a digital representation always has gaps, even though we are rarely confronted with them.) Points in 3D space, or R3 thus have three coordinates, usually referred to as [x,y,z]. Points in R2 have only two coordinates which are either called [x,y] or [u,v] depending on what kind of two dimensional space we're talking about. Points in R1 are denoted with a single value. Although we tend not to think of one-dimensional points as 'points', there is no mathematical difference; the same rules apply. One-dimensional points are often referred to as 'parameters' and we denote them with [t] or [p]. The image on the left shows the R3 world space, it is continuous and infinite. The x-coordinate of a point in this space is the projection (the red dotted line) of that point onto the x-axis (the red solid line). Points are always specified in world coordinates in Rhino. R2 world space (not drawn) is the same as R3 world space, except that it lacks a z-component. It is still continuous and infinite. R2 parameter space however is bound to a finite surface as shown in the center image. It is still continuous, I.e. hypothetically there is an infinite amount of points on the surface, but the maximum distance between any of these points is very much limited. R2 parameter coordinates are only valid if they do not exceed a certain range. In the example drawing the range has been set between 0.0 and 1.0 for both [u] and [v]directions, but it could be any finite domain. A point with coordinates [1.5, 0.6] would be somewhere outside the surface and thus invalid. Since the surface which defines this particular parameter space resides in regular R3 world space, we can always translate a parametric coordinate into a 3d world coordinate. The point [0.2, 0.4] on the surface for example is the same as point [1.8, 2.0, 4.1] in world coordinates. Once we transform or deform the surface, the R3 coordinates which correspond with [0.2, 0.4] will change. Note that the opposite is not true, we can translate any R2 parameter coordinate into a 3D world coordinate, but there are many 3D world coordinates that are not on the surface and which can therefore not be written as an R2 parameter coordinate. However, we can always project a 3D world coordinate onto the surface using the closest-point relationship. We'll discuss this in more detail later on. If the above is a hard concept to swallow, it might help you to think of yourself and your position in space. We usually tend to use local coordinate systems to describe our whereabouts; "I'm sitting in the third seat on the seventh row in the movie theatre", "I live in apartment 24 on the fifth floor", "I'm in the back seat". Some of these are variations to the global coordinate system (latitude, longitude, elevation), while others use a different anchor point. If the car you're in is on the road, your position in global coordinates is changing all the time, even though you remain in the same back seat 'coordinate'. Let's start with conversion from R1 to R3 space. The following script will add 500 colored points to the document, all of which are sampled at regular intervals across the R1 parameter space of a curve object: ```python import rhinoscriptsyntax as rs def main(): curve_id = rs.GetObject("Select a curve to sample", 4, True, True) if not curve_id: return rs.EnableRedraw(False) t = 0 while t<=1.0: addpointat_r1_parameter(curve_id,t) t+=0.002 rs.EnableRedraw(True) def addpointat_r1_parameter(curve_id, parameter): domain = rs.CurveDomain(curve_id) r1_param = domain[0] + parameter*(domain[1]-domain[0]) r3point = rs.EvaluateCurve(curve_id, r1_param) if r3point: point_id = rs.AddPoint(r3point) rs.ObjectColor(point_id, parametercolor(parameter)) def parametercolor(parameter): red = 255 * parameter if red<0: red=0 if red>255: red=255 return (red,0,255-red) if __name__=="__main__": main() ``` For no good reason whatsoever, we'll start with the bottom most function:
Line Description
24 Standard out-of-the-box function declaration which takes a single double value. This function is supposed to return a colour which changes gradually from blue to red as parameter changes from zero to one. Values outside of the range {0.0~1.0} will be clipped.
25 The red component of the colour we're going to return is declared here and assigned the naive value of 255 times the parameter. Colour components must have a value between and including 0 and 255. If we attempt to construct a colour with lower or higher values a run-time error will spoil the party.
26...27 Here's where we make sure the party can continue unimpeded.
28 Compute the colour gradient value. If parameter equals zero we want blue (0,0,255) and if it equals one we want red (255,0,0). So the green component is always zero while blue and red see-saw between 0 and 255.
Now, on to function *AddPointAtR1Parameter()*. As the name implies, this function will add a single point in 3D world space based on the parameter coordinate of a curve object. In order to work correctly this function must know what curve we're talking about and what parameter we want to sample. Instead of passing the actual parameter which is bound to the curve domain (and could be anything) we're passing a unitized one. I.e. we pretend the curve domain is between zero and one. This function will have to wrap the required math for translating unitized parameters into actual parameters. Since we're calling this function a lot (once for every point we want to add), it is actually a bit odd to put all the heavy-duty stuff inside it. We only really need to perform the overhead costs of 'unitized parameter + actual parameter' calculation once, so it makes more sense to put it in a higher level function. Still, it will be very quick so there's no need to optimize it yet.
Line Description
14 Function declaration.
15...16 Get the curve domain and check for Null. It will be Null if the ID does not represent a proper curve object. The Rhino.CurveDomain() method will return an array of two doubles which indicate the minimum and maximum t-parameters which lie on the curve.
18 Translate the unitized R1 coordinate into actual domain coordinates.
19 Evaluate the curve at the specified parameter. Rhino.EvaluateCurve() takes an R1 coordinate and returns an R3 coordinate.
21 Add the point, it will have default attributes.
22 Set the custom colour. This will automatically change the color-source attribute to By Object.
The distribution of R1 points on a spiral is not very enticing since it approximates a division by equal length segments in R3 space. When we run the same script on less regular curves it becomes easier to grasp what parameter space is all about: Let's take a look at an example which uses all parameter spaces we've discussed so far: ```python import rhinoscriptsyntax as rs def main(): surface_id = rs.GetObject("Select a surface to sample", 8, True) if not surface_id: return curve_id = rs.GetObject("Select a curve to measure", 4, True, True) if not curve_id: return points = rs.DivideCurve(curve_id, 500) rs.EnableRedraw(False) for point in points: evaluatedeviation(surface_id, 1.0, point) rs.EnableRedraw(True) def evaluatedeviation( surface_id, threshold, sample ): r2point = rs.SurfaceClosestPoint(surface_id, sample) if not r2point: return r3point = rs.EvaluateSurface(surface_id, r2point[0], r2point[1]) if not r3point: return deviation = rs.Distance(r3point, sample) if deviation<=threshold: return rs.AddPoint(sample) rs.AddLine(sample, r3point) if __name__=="__main__": main() ``` This script will compare a bunch of points on a curve to their projection on a surface. If the distance exceeds one unit, a line and a point will be added. First, the R1 points are translated into R3 coordinates so we can project them onto the surface, getting the R2 coordinate [u,v] in return. This R2 point has to be translated into R3 space as well, since we need to know the distance between the R1 point on the curve and the R2 point on the surface. Distances can only be measured if both points reside in the same number of dimensions, so we need to translate them into R3 as well. Told you it was a piece of cake...
Line Description
10 We're using the Rhino.DivideCurve() method to get all the R3 coordinates on the curve in one go. This saves us a lot of looping and evaluating.
24 Rhino.SurfaceClosestPoint() returns an array of two doubles representing the R2 point on the surface (in {u,v} coordinates) which is closest to the sample point.
27 Rhino.EvaluateSurface() in turn translates the R2 parameter coordinate into R3 world coordinates
30...38 Compute the distance between the two points and add geometry if necessary. This function returns True if the deviation is less than one unit, False if it is more than one unit and Null if something went wrong.
One more time just for kicks. We project the R1 parameter coordinate on the curve into 3D space (Step A), then we project that R3 coordinate onto the surface getting the R2 coordinate of the closest point (Step B). We evaluate the surface at R2, getting the R3 coordinate in 3D world space (Step C), and we finally measure the distance between the two R3 points to determine the deviation: ## 8.4 Lines and Polylines You'll be glad to learn that (poly)lines are essentially the same as point-lists. The only difference is that we treat the points as a series rather than an anonymous collection, which enables us to draw lines between them. There is some nasty stuff going on which might cause problems down the road so perhaps it's best to get it over with quick. There are several ways in which polylines can be manifested in openNURBS™ and thus in Rhino. There is a special polyline class which is simply a list of ordered points. It has no overhead data so this is the simplest case. It's also possible for regular nurbs curves to behave as polylines when they have their degree set to 1. In addition, a polyline could also be a polycurve made up of line segments, polyline segments, degree=1 nurbs curves or a combination of the above. If you create a polyline using the _Polyline command, you will get a proper polyline object as the Object Properties Details dialog on the left shows: The dialog claims an "Open polyline with 8 points". However, when we drag a control-point Rhino will automatically convert any curve to a Nurbs curve, as the image on the right shows. It is now an open nurbs curve of degree=1. From a geometric point of view, these two curves are identical. From a programmatic point of view, they are anything but. For the time being we will only deal with 'proper' polylines though; lists of sequential coordinates. For purposes of clarification I've added two example functions which perform basic operations on polyline point-lists. Compute the length of a polyline point-array: ```python def PolylineLength(arrVertices): PolylineLength = 0.0 for i in range(0,len(arrVertices)-1): PolylineLength = PolylineLength + rs.Distance(arrVertices[i], arrVertices[i+1]) ``` Subdivide a polyline by adding extra vertices halfway between all existing vertices: ```python def SubDividePolyline(arrV) arrSubD = [] for i in range(0, len(arrV)-1): 'copy the original vertex location arrSubD.append(arrV[i]) 'compute the average of the current vertex and the next one arrSubD.append([arrV[i][0] + arrV[i+1][0]] / 2.0, _ [arrV(i][1] + arrV[i+1][1]] / 2.0, _ [arrV[i][2] + arrV[i+1][2]] / 2.0]) 'copy the last vertex (this is skipped by the loop) arrSubD.append(arrV[len(arrV)]) return arrSubD ``` No rocket science yet, but brace yourself for the next bit... As you know, the shortest path between two points is a straight line. This is true for all our space definitions, from R1 to RN. However, the shortest path in R2 space is not necessarily the same shortest path in R3 space. If we want to connect two points on a surface with a straight line in R2, all we need to do is plot a linear course through the surface [u,v] space. (Since we can only add curves to Rhino which use 3D world coordinates, we'll need a fair amount of samples to give the impression of smoothness.) The thick red curve in the adjacent illustration is the shortest path in R2 parameter space connecting [A] and [B]. We can clearly see that this is definitely not the shortest path in R3 space. We can clearly see this because we're used to things happening in R3 space, which is why this whole R2/R3 thing is so thoroughly counter intuitive to begin with. The green, dotted curve is the actual shortest path in R3 space which still respects the limitation of the surface (I.e. it can be projected onto the surface without any loss of information). The following function was used to create the red curve; it creates a polyline which represents the shortest path from [A] to [B] in surface parameter space: ```python def getr2pathonsurface(surface_id, segments, prompt1, prompt2): start_point = rs.GetPointOnSurface(surface_id, prompt1) if not start_point: return end_point = rs.GetPointOnSurface(surface_id, prompt2) if not end_point: return if rs.Distance(start_point, end_point)==0.0: return uva = rs.SurfaceClosestPoint(surface_id, start_point) uvb = rs.SurfaceClosestPoint(surface_id, end_point) path = [] for i in range(segments): t = i / segments u = uva[0] + t*(uvb[0] - uva[0]) v = uva[1] + t*(uvb[1] - uva[1]) pt = rs.EvaluateSurface(surface_id, u, v) path.append(pt) return path ```
Line Description
1 This function takes four arguments; the ID of the surface onto which to plot the shortest route, the number of segments for the path polyline and the prompts to use for picking the A and B point.
1...3 Prompt the user for the {A} point on the surface. Return if the user does not enter a point.
5...6 Prompt the user for the {B} point on the surface. Return if the user does not enter a point.
10...11 Project {A} and {B} onto the surface to get the respective R2 coordinates uva and uvb.
13 Declare the list which is going to store all the polyline vertices.
14 Since this algorithm is segment-based, we know in advance how many vertices the polyline will have and thus how often we will have to sample the surface.
15 t is a value which ranges from 0.0 to 1.0 over the course of our loop
16...17 Use the current value of t to sample the surface somewhere in between uvA and uvB.
18 rs.EvaluateSurface() takes a {u} and a {v} value and spits out a 3D-world coordinate. This is just a friendly way of saying that it converts from R2 to R3.
We're going to combine the previous examples in order to make a real geodesic path routine in Rhino. This is a fairly complex algorithm and I'll do my best to explain to you how it works before we get into any actual code. First we'll create a polyline which describes the shortest path between [A] and [B] in R2 space. This is our base curve. It will be a very coarse approximation, only ten segments in total. We'll create it using the function on page 54. Unfortunately that function does not take closed surfaces into account. In the paragraph on nurbs surfaces we'll elaborate on this. Once we've got our base shape we'll enter the iterative part. The iteration consists of two nested loops, which we will put in two different functions in order to avoid too much nesting and indenting. We're going to write four functions in addition to the ones already discussed in this paragraph: - The main geodesic routine - ProjectPolyline() - SmoothPolyline() - GeodesicFit() The purpose of the main routine is the same as always; to collect the initial data and make sure the script completes as successfully as possible. Since we're going to calculate the geodesic curve between two points on a surface, the initial data consists only of a surface ID and two points in surface parameter space. The algorithm for finding the geodesic curve is a relatively slow one and it is not very good at making major changes to dense polylines. That is why we will be feeding it the problem in bite-size chunks. It is because of this reason that our initial base curve (the first bite) will only have ten segments. We'll compute the geodesic path for these ten segments, then subdivide the curve into twenty segments and recompute the geodesic, then subdivide into 40 and so on and so forth until further subdivision no longer results in a shorter overall curve. The *ProjectPolyline()* function will be responsible for making sure all the vertices of a polyline point-array are in fact coincident with a certain surface. In order to do this it must project the R3 coordinates of the polyline onto the surface, and then again evaluate that projection back into R3 space. This is called 'pulling'. The purpose of *SmoothPolyline()* will be to average all polyline vertices with their neighbours. This function will be very similar to our previous example, except it will be much simpler since we know for a fact we're not dealing with nurbs curves here. We do not need to worry about knots, weights, degrees and domains. *GeodesicFit()* is the essential geodesic routine. We expect it to deform any given polyline into the best possible geodesic curve, no matter how coarse and wrong the input is. The algorithm in question is a very naive solution to the geodesic problem and it will run much slower than Rhinos native _ShortPath command. The upside is that our script, once finished, will be able to deal with self-intersecting surfaces. The underlying theory of this algorithm is synonymous with the simulation of a contracting rubber band, with the one difference that our rubber band is not allowed to leave the surface. The process is iterative and though we expect every iteration to yield a certain improvement over the last one, the amount of improvement will diminish as we near the ideal solution. Once we feel the improvement has become negligible we'll abort the function. In order to simulate a rubber band we require two steps; smoothing and projecting. First we allow the rubber band to contract (it always wants to contract into a straight line between [A] and [B]). This contraction happens in R3 space which means the vertices of the polyline will probably end up away from the surface. We must then re-impose these surface constraints. These two operations have been hoisted into functions #2 and #3. The illustration depicts the two steps which compose a single iteration of the geodesic routine. The black polyline is projected onto the surface giving the red polyline. The red curve in turn is smoothed into the green curve. Note that the actual algorithm performs these two steps in the reverse order; smoothing first, projection second. We'll start with the simplest function: ```python def projectpolyline(vertices, surface_id): polyline = [] for vertex in vertices: pt = rs.BrepClosestPoint(surface_id, vertex) if pt: polyline.append(pt[0]) return polyline ```
Line Description
1...3 Since this is a specialized def which we will only be using inside this script, we can skip projecting the first and last point. We can safely assume the polyline is open and that both endpoints will already be on the curve.
4 We ask Rhino for the closest point on the surface object given our polyline vertex coordinate. The reason why we do not use rs.SurfaceClosestPoint() is because BRepClosestPoint() takes trims into account. This is a nice bonus we can get for free. The native _ShortPath command does not deal with trims at all. We are of course not interested in aping something which already exists, we want to make something better.
5 If BRepClosestPoint() returned Null something went wrong after all. We cannot project the vertex in this case so we'll simply ignore it. We could of course short-circuit the whole operation after a failure like this, but I prefer to press on and see what comes out the other end.
6 The BRepClosestPoint() method returns a lot of information, not just the R2 coordinate. In fact it returns a tuple of data, the first element of which is the R3 closest point. This means we do not have to translate the uv coordinate into xyz ourselves. Huzzah! Assign it to the vertex and move on.
```python def smoothpolyline(vertices): smooth = [] smooth.append(vertices[0]) for i in range(1, len(vertices)-1): prev = vertices[i-1] this = vertices[i] next = vertices[i+1] pt = (prev+this+next) / 3.0 smooth.append(pt) smooth.append(vertices[len(vertices)-1]) return smooth ```
Line Description
1...3 6...8 Since we need the original coordinates throughout the smoothing operation we cannot deform it directly. That is why we need to make a copy of each vertex point before we start messing about with coordinates.
9 What we do here is average the x, y and z coordinates of the current vertex ('current' as defined by i) using both itself and its neighbours. We iterate through all the internal vertices and add the Point3d objects together, rather than explicitly adding their x, y and z components together. Writing smaller functions will not make the code go faster, but it does mean we just get to write less junk. Also, it means adjustments are easier to make afterwards since less code-rewriting is required.
Time for the bit that sounded so difficult on the previous page, the actual geodesic curve fitter routine: ```python def geodesicfit(vertices, surface_id, tolerance): length = polylinelength(vertices) while True: vertices = smoothpolyline(vertices) vertices = projectpolyline(vertices, surface_id) newlength = polylinelength(vertices) if abs(newlength-length) Line Description 1 Hah... that doesn't look so bad after all, does it? You'll notice that it's often the stuff which is easy to explain that ends up taking a lot of lines of code. Rigid mathematical and logical structures can typically be coded very efficiently. 2 We'll be monitoring the progress of each iteration and once the curve no longer becomes noticeably shorter (where 'noticeable' is defined by the tolerance argument), we'll call the 'intermediate result' the 'final result' and return execution to the caller. In order to monitor this progress, we need to remember how long the curve was before we started; length is created for this purpose. 3 Whenever you see a while True: without any standard escape clause you should be on your toes. This is potentially an infinite loop. I have tested it rather thoroughly and have been unable to make it run more than 120 times. Experimental data is never watertight proof, the routine could theoretically fall into a stable state where it jumps between two solutions. If this happens, the loop will run forever. You are of course welcome to add additional escape clauses if you deem that necessary. 4...5 Place the calls to the functions on page 56. These are the bones of the algorithm. 6 Compute the new length of the polyline. 7 Check to see whether or not it is worth carrying on. 8 Apparently it was, we need now to remember this new length as our frame of reference. The main subroutine takes some explaining. It performs a lot of different tasks which always makes a block of code harder to read. It would have been better to split it up into more discrete chunks, but we're already using seven different functions for this script and I feel we are nearing the ceiling. Remember that splitting problems into smaller parts is a good way to organize your thoughts, but it doesn't actually solve anything. You'll need to find a good balance between splitting and lumping. ```python def geodesiccurve(): surface_id = rs.GetObject("Select surface for geodesic curve solution", 8, True, True) if not surface_id: return vertices = getr2pathonsurface(surface_id, 10, "Start of geodesic curve", "End of geodesic curve") if not vertices: return tolerance = rs.UnitAbsoluteTolerance() / 10 length = 1e300 newlength = 0.0 while True: print("Solving geodesic fit for %d samples" % len(vertices)) vertices = geodesicfit(vertices, surface_id, tolerance) newlength = polylinelength(vertices) if abs(newlength-length)1000: break vertices = subdividepolyline(vertices) length = newlength rs.AddPolyline(vertices) print("Geodesic curve added with length: ", newlength) ```
Line Description
2...3 Get the surface to be used in the geodesic routine.
5...6 Declare a variable which will store the polyline vertices. Since the return value of getr2pathonsurface() is already a list, we do not need to declare it with empty brackets - '[ ]' - as we have done elsewhere.
8 The tolerance used in our script will be 10% of the absolute tolerance of the document.
9...12 This loop also uses a length comparison in order to determine whether or not to continue. But instead of evaluating the length of a polyline before and after a smooth/project iteration, it measures the difference before and after a subdivide/geodesicfit iteration. The goal of this evaluation is to decide whether or not further elaboration will pay off. The variables length and newlength are used in the same context as on the previous page.
13 Display a message in the command-line informing the user about the progress we're making. This script may run for quite some time so it's important not to let the user think the damn thing has crashed.
14 Place a call to the GeodesicFit() subroutine.
16...17 Compare the improvement in length, exit the loop when there's no progress of any value.
18 A safety-switch. We don't want our curve to become too dense.
19 A call to subdividepolyline() will double the amount of vertices in the polyline. The newly added vertices will not be on the surface, so we must make sure to call geodesicfit() at least once before we add this new polyline to the document.
22...23 Add the curve and print a message about the length.
## 8.5 Planes Planes are not genuine objects in Rhino, they are used to define a coordinate system in 3D world space. In fact, it's best to think of planes as vectors, they are merely mathematical constructs. Although planes are internally defined by a parametric equation, I find it easiest to think of them as a set of axes: A plane definition is an array of one point and three vectors, the point marks the origin of the plane and the vectors represent the three axes. There are some rules to plane definitions, I.e. not every combination of points and vectors is a valid plane. If you create a plane using one of the RhinoScriptSyntax plane methods you don't have to worry about this, since all the bookkeeping will be done for you. The rules are as follows: - The axis vectors must be unitized (have a length of 1.0). - All axis vectors must be perpendicular to each other. - The x and y axis are ordered anti-clockwise. The illustration shows how rules #2 and #3 work in practice. ```python ptOrigin = rs.GetPoint("Plane origin") ptX = rs.GetPoint("Plane X-axis", ptOrigin) ptY = rs.GetPoint("Plane Y-axis", ptOrigin) dX = rs.Distance(ptOrigin, ptX) dY = rs.Distance(ptOrigin, ptY) arrPlane = rs.PlaneFromPoints(ptOrigin, ptX, ptY) rs.AddPlaneSurface(arrPlane, 1.0, 1.0) rs.AddPlaneSurface(arrPlane, dX, dY) ``` You will notice that all RhinoScriptSyntax methods that require plane definitions make sure these demands are met, no matter how poorly you defined the input. The adjacent illustration shows how the rs.AddPlaneSurface() call on line 11 results in the red plane, while the rs.AddPlaneSurface() call on line 12 creates the yellow surface which has dimensions equal to the distance between the picked origin and axis points. We'll only pause briefly at plane definitions since planes, like vectors, are usually only constructive elements. In examples to come they will be used extensively so don't worry about getting the hours in. A more interesting script which uses the *rs.AddPlaneSurface()* method is the one below which populates a surface with so-called surface frames: ```python surface_id = rs.GetObject("Surface to frame", 8, True, True) if not surface_id: return count = rs.GetInteger("Number of iterations per direction", 20, 2) if not count: return udomain = rs.SurfaceDomain(surface_id, 0) vdomain = rs.SurfaceDomain(surface_id, 1) ustep = (udomain[1] - udomain[0]) / count vstep = (vdomain[1] - vdomain[0]) / count rs.EnableRedraw(False) u = udomain[0] while u<=udomain[1]: v = vdomain[0] while v<=vdomain[1]: pt = rs.EvaluateSurface(surface_id, u, v) if rs.Distance(pt, rs.BrepClosestPoint(surface_id, pt)[0])<0.1: srf_frame = rs.SurfaceFrame(surface_id, [u, v]) rs.AddPlaneSurface(srf_frame, 1.0, 1.0) v+=vstep u+=ustep rs.EnableRedraw(True) ``` Frames are planes which are used to indicate geometrical directions. Both curves, surfaces and textured meshes have frames which identify tangency and curvature in the case of curves and [u] and [v] directions in the case of surfaces and meshes. The script above simply iterates over the [u] and [v] directions of any given surface and adds surface frame objects at all uv coordinates it passes. On lines 9 and 10 we determine the domain of the surface in u and v directions and we derive the required stepsize from those limits. Line 14 and 16 form the main structure of the two-dimensional iteration. You can read such nested For loops as "Iterate through all columns and inside every column iterate through all rows". Line 18 does something interesting which is not apparent in the adjacent illustration. When we are dealing with trimmed surfaces, those two lines prevent the script from adding planes in cut-away areas. By comparing the point on the (untrimmed) surface to it's projection onto the trimmed surface, we know whether or not the [uv] coordinate in question represents an actual point on the trimmed surface. The *rs.SurfaceFrame()* method returns a unitized frame whose axes point in the [u] and [v] directions of the surface. Note that the [u] and [v] directions are not necessarily perpendicular to each other, but we only add valid planes whose x and y axis are always at 90º, thus we ignore the direction of the v-component. ## 8.6 Circles, Ellipses, and Arcs Although the user is never confronted with parametric objects in Rhino, the openNURBS™ kernel has a certain set of mathematical primitives which are stored parametrically. Examples of these are cylinders, spheres, circles, revolutions and sum-surfaces. To highlight the difference between explicit (parametric) and implicit circles: When adding circles to Rhino through scripting, we can either use the Plane+Radius approach or we can use a 3-Point approach (which is internally translated into Plane+Radius). You may remember that circles are tightly linked with sines and cosines; those lovable, undulating waves. We're going to create a script which packs circles with a predefined radius onto a sphere with another predefined radius. Now, before we start and I give away the answer, I'd like you to take a minute and think about this problem. The most obvious solution is to start stacking circles in horizontal bands and simply to ignore any vertical nesting which might take place. If you reached a similar solution and you want to keep feeling good about yourself I recommend you skip the following two sentences. This very solution has been found over and over again but for some reason Dave Rusin is usually given as the inventor. Even though Rusin's algorithm isn't exactly rocket science, it is worth discussing the mathematics in advance to prevent -or at least reduce- any confusion when I finally confront you with the code. Rusin's algorithm works as follows: - Solve how many circles you can evenly stack from north pole to south pole on the sphere. - For each of those bands, solve how many circles you can stack evenly around the sphere. - Do it. No wait, back up. The first thing to realize is how a sphere actually works. Only once we master spheres can we start packing them with circles. In Rhino, a sphere is a surface of revolution, which has two singularities and a single seam: The north pole (the black dot in the left most image) and the south pole (the white dot in the same image) are both on the main axis of the sphere and the seam (the thick edge) connects the two. In essence, a sphere is a rectangular plane bent in two directions, where the left and right side meet up to form the seam and the top and bottom edge are compressed into a single point each (a singularity). This coordinate system should be familiar since we use the same one for our own planet. However, our planet is divided into latitude and longitude degrees, whereas spheres are defined by latitude and longitude radians. The numeric domain of the latitude of the sphere starts in the south pole with -½π, reaches 0.0 at the equator and finally terminates with ½π at the north pole. The longitudinal domain starts and stops at the seam and travels around the sphere from 0.0 to 2π. Now you also know why it is called a 'seam' in the first place; it's where the domain suddenly jumps from one value to another, distant one. We cannot pack circles in the same way as we pack squares in the image above since that would deform them heavily near the poles, as indeed the squares are deformed. We want our circles to remain perfectly circular which means we have to fight the converging nature of the sphere Assuming the radius of the circles we are about to stack is sufficiently smaller than the radius of the sphere, we can at least place two circles without thinking; one on the north- and one on the south pole. The additional benefit is that these two circles now handsomely cover up the singularities so we are only left with the annoying seam. The next order of business then, is to determine how many circles we need in order to cover up the seam in a straightforward fashion. The length of the seam is half of the circumference of the sphere (see yellow arrow in adjacent illustration). Home stretch time, we've collected all the information we need in order to populate this sphere. The last step of the algorithm is to stack circles around the sphere, starting at every seam-circle. We need to calculate the circumference of the sphere at that particular latitude, divide that number by the diameter of the circles and once again find the largest integer value which is smaller than or equal to that result. The equivalent mathematical notation for this is: $$N_{count} = \left[\frac{2 \cdot R_{sphere} \cdot \cos{\phi}}{2 \cdot R_{circle}} \right]$$ in case you need to impress anyone… ```python def DistributeCirclesOnSphere(): sphere_radius = rs.GetReal("Radius of sphere", 10.0, 0.01) if not sphere_radius: return circle_radius = rs.GetReal("Radius of circles", 0.05*sphere_radius, 0.001, 0.5*sphere_radius) if not circle_radius: return vertical_count = int( (math.pi*sphere_radius)/(2*circle_radius) ) rs.EnableRedraw(False) phi = -0.5*math.pi phi_step = math.pi/vertical_count while phi<0.5*math.pi: horizontal_count = int( (2*math.pi*math.cos(phi)*sphere_radius)/(2*circle_radius) ) if horizontal_count==0: horizontal_count=1 theta = 0 theta_step = 2*math.pi/horizontal_count while theta<2*math.pi-1e-8: circle_center = (sphere_radius*math.cos(theta)*math.cos(phi), sphere_radius*math.sin(theta)*math.cos(phi), sphere_radius*math.sin(phi)) circle_normal = rs.PointSubtract(circle_center, (0,0,0)) circle_plane = rs.PlaneFromNormal(circle_center, circle_normal) rs.AddCircle(circle_plane, circle_radius) theta += theta_step phi += phi_step rs.EnableRedraw(True) ```
Line Description
1...6 Collect all custom variables and make sure they make sense. We don't want spheres smaller than 0.01 units and we don't want circle radii larger than half the sphere radius.
8 Compute the number of circles from pole to pole. The int() function in VBScript takes a double and returns only the integer part of that number. Hence it always rounds downwards.
11...12 16...17 phi and theta (Φ and Θ) are typically used to denote angles in spherical space and it's not hard to see why. I could have called them latitude and longitude respectively as well.
13 The phi loop runs from -½π to ½π and we need to run it VerticalCount times.
14 This is where we calculate how many circles we can fit around the sphere on the current latitude. The math is the same as before, except we also need to calculate the length of the path around the sphere: 2π·R·Cos(Φ)
15 If it turns out that we can fit no circles at all at a certain latitude, we're going to get into trouble since we use the HorizontalCount variable as a denominator in the stepsize calculation on line 24. And even my mother knows you cannot divide by zero. However, we know we can always fit at least one circle.
18 This loop is essentially the same as the one on line 20, except it uses a different stepsize and a different numeric range ({0.0 <= theta < 2π} instead of {-½π <= phi <= +½π}). The more observant among you will have noticed that the domain of theta reaches from nought up to but not including two pi. If theta would go all the way up to 2π then there would be a duplicate circle on the seam. The best way of preventing a loop to reach a certain value is to subtract a fraction of the stepsize from that value, in this case I have simply subtracted a ludicrously small number (1e-8 = 0.00000001).
19...21 circle_center will be used to store the center point of the circles we're going to add. circle_normal will be used to store the normal of the plane in which these circles reside. circle_plane will be used to store the resulting plane definition.
19 This is mathematically the most demanding line, and I'm not going to provide a full proof of why and how it works. This is the standard way of translating the spherical coordinates Φ and Θ into Cartesian coordinates x, y and z. Further information can be found on [MathWorld.com](http://mathworld.wolfram.com/)
20 Once we found the point on the sphere which corresponds to the current values of phi and theta, it's a piece of proverbial cake to find the normal of the sphere at that location. The normal of a sphere at any point on its surface is the inverted vector from that point to the center of the sphere. And that's what we do on line 29, we subtract the sphere origin (always (0,0,0) in this script) from the newly found {x,y,z} coordinate.
21...22 We can construct a plane definition from a single point on that plane and a normal vector and we can construct a circle from a plane definition and a radius value. Voila.
### 8.6.1 Ellipses Ellipses essentially work the same as circles, with the difference that you have to supply two radii instead of just one. Because ellipses only have two mirror symmetry planes and circles possess rotational symmetry (I.e. an infinite number of mirror symmetry planes), it actually does matter a great deal how the base-plane is oriented in the case of ellipses. A plane specified merely by origin and normal vector is free to rotate around that vector without breaking any of the initial constraints. The following example script demonstrates very clearly how the orientation of the base plane and the ellipse correspond. Consider the standard curvature analysis graph as shown on the left: It gives a clear impression of the range of different curvatures in the spline, but it doesn't communicate the helical twisting of the curvature very well. Parts of the spline that are near-linear tend to have a garbled curvature since they are the transition from one well defined bend to another. The arrows in the left image indicate these areas of twisting but it is hard to deduce this from the curvature graph alone. The upcoming script will use the curvature information to loft a surface through a set of ellipses which have been oriented into the curvature plane of the local spline geometry. The ellipses have a small radius in the bending plane of the curve and a large one perpendicular to the bending plane. Since we will not be using the strength of the curvature but only its orientation, small details will become very apparent. ```python def FlatWorm(): curve_object = rs.GetObject("Pick a backbone curve", 4, True, False) if not curve_object: return samples = rs.GetInteger("Number of cross sections", 100, 5) if not samples: return bend_radius = rs.GetReal("Bend plane radius", 0.5, 0.001) if not bend_radius: return perp_radius = rs.GetReal("Ribbon plane radius", 2.0, 0.001) if not perp_radius: return crvdomain = rs.CurveDomain(curve_object) crosssections = [] t_step = (crvdomain[1]-crvdomain[0])/samples t = crvdomain[0] while t<=crvdomain[1]: crvcurvature = rs.CurveCurvature(curve_object, t) crosssectionplane = None if not crvcurvature: crvPoint = rs.EvaluateCurve(curve_object, t) crvTangent = rs.CurveTangent(curve_object, t) crvPerp = (0,0,1) crvNormal = rs.VectorCrossProduct(crvTangent, crvPerp) crosssectionplane = rs.PlaneFromFrame(crvPoint, crvPerp, crvNormal) else: crvPoint = crvcurvature[0] crvTangent = crvcurvature[1] crvPerp = rs.VectorUnitize(crvcurvature[4]) crvNormal = rs.VectorCrossProduct(crvTangent, crvPerp) crosssectionplane = rs.PlaneFromFrame(crvPoint, crvPerp, crvNormal) if crosssectionplane: csec = rs.AddEllipse(crosssectionplane, bend_radius, perp_radius) crosssections.append(csec) t += t_step if not crosssections: return rs.AddLoftSrf(crosssections) rs.DeleteObjects(crosssections)
Line Description
16 crosssections is a list where we will store all our ellipse IDs. We need to remember all the ellipses we add since they have to be fed to the rs.AddLoftSrf() method. crosssectionplane will contain the base plane data for every individual ellipse, we do not need to remember these planes so we can afford to overwrite the old value with any new one. You'll notice I'm violating a lot of naming conventions from paragraph [2.3.5 Using Variables]. If you want to make something of it we can take it outside.
19 We'll be walking along the curve with equal parameter steps. This is arguably not the best way, since we might be dealing with a polycurve which has wildly different parameterizations among its subcurves. This is only an example script though so I wanted to keep the code to a minimum. We're using the same trick as before in the header of the loop to ensure that the final value in the domain is included in the calculation. By extending the range of the loop by one billionth of a parameter we circumvent the 'double noise problem' which might result from multiple additions of doubles.
20 The rs.CurveCurvature() method returns a whole set of data to do with curvature analysis. However, it will fail on any linear segment (the radius of curvature is infinite on linear segments).
22...27 Hence, if it fails we have to collect the standard information in the old fashioned way. We also have to pick a crvPerp vector since none is available. We could perhaps use the last known one, or look at the local plane of the curve beyond the current -unsolvable- segment, but I've chosen to simply use a z-axis vector by default.
28...32 If the curve does have curvature at t, then we extract the required information directly from the curvature data.
33 Construct the plane for the ellipse.
35...37 Add the ellipse to the file and append the new ellipse curve ID csec to the list crosssections.
40...42 Create a lofted surface through all ellipses and delete the curves afterwards.
### 8.6.2 Arcs Since the topic of Arcs isn't much different from the topic of Circles, I thought it would be a nice idea to drag in something extra. This something extra is what we programmers call "recursion" and it is without doubt the most exciting thing in our lives (we don't get out much). Recursion is the process of self-repetition. Like loops which are iterative and execute the same code over and over again, recursive functions call themselves and thus also execute the same code over and over again, but this process is hierarchical. It actually sounds harder than it is. One of the success stories of recursive functions is their implementation in binary trees which are the foundation for many search and classification algorithms in the world today. I'll allow myself a small detour on the subject of recursion because I would very much like you to appreciate the power that flows from the simplicity of the technique. Recursion is unfortunately one of those things which only become horribly obvious once you understand how it works. Imagine a box in 3D space which contains a number of points within its volume. This box exhibits a single behavioral pattern which is recursive. The recursive function evaluates a single conditional statement: {when the number of contained points exceeds a certain threshold value then subdivide into 8 smaller boxes, otherwise add yourself to the document}. It would be hard to come up with an easier If…Else statement. Yet, because this behavior is also exhibited by all newly created boxes, it bursts into a chain of recursion, resulting in the voxel spaces in the images below: The input in these cases was a large pointcloud shaped like the upper half of a sphere. There was also a dense spot with a higher than average concentration of points. Because of the approximating pattern of the subdivision, the recursive cascade results in these beautiful stacks. Trying to achieve this result without the use of recursion would entail a humongous amount of bookkeeping and many, many lines of code. Before we can get to the cool bit we have to write some of the supporting functions, which -I hate to say it- once again involve goniometry (the mathematics of angles). The problem: adding an arc using the start point, end point and start direction. As you will be aware there is a way to do this directly in Rhino using the mouse. In fact a brief inspection yields 14 different ways in which arcs can be drawn in Rhino manually and yet there are only two ways to add arcs through scripting: - rs.AddArc(Plane, Radius, Angle) - rs.AddArc3Pt(Point, Point, Point) The first way is very similar to adding circles using plane and radius values, with the added argument for sweep angle. The second way is also similar to adding circles using a 3-point system, with the difference that the arc terminates at the first and second point. There is no direct way to add arcs from point A to point B while constrained to a start tangent vector. We're going to have to write a function which translates the desired Start-End-Direction approach into a 3-Point approach. Before we tackle the math, let's review how it works: We start with two points {A} & {B} and a vector definition {D}. The arc we're after is the red curve, but at this point we don't know how to get there yet. Note that this problem might not have a solution if {D} is parallel or anti-parallel to the line from {A} to {B}. If you try to draw an arc like that in Rhino it will not work. Thus, we need to add some code to our function that aborts when we're confronted with unsolvable input. We're going to find the coordinates of the point in the middle of the desired arc {M}, so we can use the 3Point approach with {A}, {B} and {M}. As the illustration on the left indicates, the point in the middle of the arc is also on the line perpendicular from the middle {C} of the baseline. The halfway point on the arc also happens to lie on the bisector between {D} and the baseline vector. We can easily construct the bisector of two vectors in 3D space by process of unitizing and adding both vectors. In the illustration on the left the bisector is already pointing in the right direction, but it still hasn't got the correct length. We can compute the correct length using the standard "Sin-Cos-Tan right triangle rules": The triangle we have to solve has a 90º angle in the lower right corner, a is the angle between the baseline and the bisector, the length of the bottom edge of the triangle is half the distance between {A} and {B} and we need to compute the length of the slant edge (between {A} and {M}). The relationship between a and the lengths of the sides of the triangle is: $$\cos({\alpha})=\frac{0.5D}{?} \gg \frac{1}{\cos({\alpha})}=\frac{?}{0.5D} \gg \frac{0.5D}{\cos({\alpha})} = ?$$ We now have the equation we need in order to solve the length of the slant edge. The only remaining problem is cos(a). In the paragraph on vector mathematics (6.2 Points and Vectors) the vector dotproduct is briefly introduced as a way to compute the angle between two vectors. When we use unitized vectors, the arccosine of the dotproduct gives us the angle between them. This means the dotproduct returns the cosine of the angle between these vectors. This is a very fortunate turn of events since the cosine of the angle is exactly the thing we're looking for. In other words, the dotproduct saves us from having to use the cosine and arccosine functions altogether. Thus, the distance between {A} and {M} is the result of: ```python (0.5 * rs.Distance(A, B)) / rs.VectorDotProduct(D, Bisector) ``` ```python def AddArcDir(ptStart, ptEnd, vecDir): vecBase = rs.PointSubtract(ptEnd, ptStart) if rs.VectorLength(vecBase)==0.0: return if rs.IsVectorParallelTo(vecBase, vecDir): return rs.AddLine(ptStart, ptEnd) vecBase = rs.VectorUnitize(vecBase) vecDir = rs.VectorUnitize(vecDir) vecBisector = rs.VectorAdd(vecDir, vecBase) vecBisector = rs.VectorUnitize(vecBisector) dotProd = rs.VectorDotProduct(vecBisector, vecDir) midLength = (0.5*rs.Distance(ptStart, ptEnd))/dotProd vecBisector = rs.VectorScale(vecBisector, midLength) return rs.AddArc3Pt(ptStart, rs.PointAdd(ptStart, vecBisector), ptEnd) ```
Line Description
1 The ptStart argument indicates the start of the arc, ptEnd the end and vecDir the direction at ptStart. This function will behave just like the rs.AddArc3Pt() method. It takes a set of arguments and returns the identifier of the created curve object if successful. If no curve was added the function does not return anything - that is, the resulting assignment will be None.
2 Create the baseline vector (from {A} to {B}), by subtracting {A} from {B}.
3 If {A} and {B} are coincident, then the subtraction from line 2 will result in a vector with a length of 0 and no solution is possible. Actually, there is an infinite number of solutions so we wouldn't know which one to pick.
5 If vecDir is parallel (or anti-parallel) to the baseline vector, then no solution is possible at all.
8...9 Make sure all vector definitions so far are unitized - that is, they all have a vector length value of one
11...12 Create the bisector vector and unitize it.
14 Compute the dotproduct between the bisector and the direction vector. Since the bisector is exactly halfway between the direction vector and baseline vector (indeed, that is the point to its existence), we could just as well have calculated the dotproduct between it and the baseline vector.
15 Compute the distance between ptStart and the center point of the desired arc.
17 Resize the (unitized) bisector vector to match this length.
18 Create an arc using the start, end and midpoint arguments, return the ID.
We need this function in order to build a recursive tree-generator which outputs trees made of arcs. Our trees will be governed by a set of five variables but -due to the flexible nature of the recursive paradigm- it will be very easy to add more behavioral patterns. The growing algorithm as implemented in this example is very simple and doesn't allow a great deal of variation. The five base parameters are: 1. Propagation factor 2. Twig length 3. Twig length mutation 4. Twig angle 5. Twig angle mutation The propagation-factor is a numeric range which indicates the minimum and maximum number of twigs that grow at the end of every branch. This is a totally random affair, which is why it is called a "factor" rather than a "number". More on random numbers in a minute. The twig-length and twig-length-mutation variables control the -as you probably guessed- length of the twigs and how the length changes with every twig generation. The twig-angle and twig-angle-mutation work in a similar fashion. The actual recursive bit of this algorithm will not concern itself with the addition and shape of the twig-arcs. This is done by a supporting function which we have to write before we can start growing trees. The problem we have when adding new twigs, is that we want them to connect smoothly to their parent branch. We've already got the plumbing in place to make tangency continuous arcs, but we have no mechanism yet for picking the end-point. In our current plant-scheme, twig growth is controlled by two factors; length and angle. However, since more than one twig might be growing at the end of a branch there needs to be a certain amount of random variation to keep all the twigs from looking the same. The adjacent illustration shows the algorithm we'll be using for twig propagation. The red curve is the branch-arc and we need to populate the end with any number of twig-arcs. Point {A} and Vector {D} are dictated by the shape of the branch but we are free to pick point {B} at random provided we remain within the limits set by the length and angle constraints. The complete set of possible end-points is drawn as the yellow cone. We're going to use a sequence of Vector methods to get a random point {B} in this shape: 1. Create a new vector {T} parallel to {D} 2. Resize {T} to have a length between {Lmin} and {Lmax} 3. Mutate {T} to deviate a bit from {D} 4. Rotate {T} around {D} to randomize the orientation ```python def RandomPointInCone( origin, direction, minDistance, maxDistance, maxAngle): vecTwig = rs.VectorUnitize(direction) vecTwig = rs.VectorScale(vecTwig, minDistance + random.random()*(maxDistance-minDistance)) MutationPlane = rs.PlaneFromNormal((0,0,0), vecTwig) vecTwig = rs.VectorRotate(vecTwig, random.random()*maxAngle, MutationPlane[1]) vecTwig = rs.VectorRotate(vecTwig, random.random()*360, direction) return rs.PointAdd(origin, vecTwig) ```
Line Description
1 origin is synonymous with point {A}. direction is synonymous with vector {D}. minDistance and MaxDistance indicate the length-wise domain of the cone. maxAngle is a value which specifies the angle of the cone (in degrees, not radians).
2...3 Create a new vector parallel to Direction and resize it to be somewhere between MinDistance and MaxDistance. I'm using the random() function here which is a Python pseudo-random-number frontend. It always returns a random value between zero and one.
4 In order to mutate vecTwig, we need to find a parallel vector. since we only have one vector here we cannot directly use the Rhino.VectorCrossProduct() method, so we'll construct a plane and use its x-axis. This vector could be pointing anywhere, but always perpendicular to vecTwig.
5 Mutate vecTwig by rotating a random amount of degrees around the plane x-axis.
6 Mutate vecTwig again by rotating it around the Direction vector. This time the random angle is between 0 and 360 degrees.
7 Create the new point as inferred by Origin and vecTwig.
One of the definitions Wikipedia has to offer on the subject of recursion is: "In order to understand recursion, one must first understand recursion." Although this is obviously just meant to be funny, there is an unmistakable truth as well. The upcoming script is recursive in every definition of the word, it is also quite short, it produces visually interesting effects and it is quite clearly a very poor realistic plant generator. The perfect characteristics for exploration by trial-and-error. Probably more than any other example script in this primer this one is a lot of fun to play around with. Modify, alter, change, mangle and bend it as you see fit. There is a set of rules to which any working recursive function must adhere. It must place at least one call to itself somewhere before the end and must have a way of exiting without placing any calls to itself. If the first condition is not met the function cannot be called recursive and if the second condition is not met it will call itself until time stops (or rather until the call-stack memory in your computer runs dry). Lo and behold! A mere 21 lines of code to describe the growth of an entire tree. ```python def RecursiveGrowth( ptStart, vecDir, props, generation): minTwigCount, maxTwigCount, maxGenerations, maxTwigLength, lengthMutation, maxTwigAngle,... angleMutation = props if generation>maxGenerations: return #Copy and mutate the growth-properties newProps = props maxTwigLength *= lengthMutation maxTwigAngle *= angleMutation if maxTwigAngle>90: maxTwigAngle=90 #Determine the number of twigs (could be less than zero) newprops = minTwigCount, maxTwigCount, maxGenerations, maxTwigLength, lengthMutation,... maxTwigAngle, angleMutation maxN = int( minTwigCount+random.random()*(maxTwigCount-minTwigCount) ) for n in range(1,maxN): ptGrow = RandomPointInCone(ptStart, vecDir, 0.25*maxTwigLength, maxTwigLength,... maxTwigAngle) newTwig = AddArcDir(ptStart, ptGrow, vecDir) if newTwig: vecGrow = rs.CurveTangent(newTwig, rs.CurveDomain(newTwig)[1]) RecursiveGrowth(ptGrow, vecGrow, newProps, generation+1) ```
Line Description
1 A word on the function signature. Apart from the obvious arguments ptStart and vecDir, this function takes an tuple and a generation counter. The tuple contains all our growth variables. Since there are seven of them in total I didn't want to add them all as individual arguments. Also, this way it is easier to add parameters without changing function calls. The generation argument is an integer telling the function which twig generation it is in. Normally a recursive function does not need to know its depth in the grand scheme of things, but in our case we're making an exception since the number of generations is an exit threshold.
2 For readability, we will break our tuple into individual variables. On the assignment side, the variables are listed in the order that they appear in the tuple. The properties tuple consists of the following items:
3 If the current generation exceeds the generation limit (which is stored at the third element in the properties tuple, and broken out to the variable maxGenerations) this function will abort without calling itself. Hence, it will take a step back on the recursive hierarchy.
6 This is where we make a copy of the properties. You see, when we are going to grow new twigs, those twigs will be called with mutated properties, however we require the unmutated properties inside this function instance.
7...9 Mutate the copied properties. I.e. multiply the maximum-twig-length by the twig-length-mutation factor and do the same for the angle. We must take additional steps to ensure the angle doesn't go berserk so we're limiting the mutation to within the 90 degree realm.
13 maxN is an integer which indicated the number of twigs we are about to grow. maxN is randomly picked between the two allowed extremes (Props(0) and Props(1)). The random() function generates a number between zero and one which means that maxN can become any value between and including the limits.
15 This is where we pick a point at random using the unmutated properties. The length constraints we're using is hard coded to be between the maximum allowed length and a quarter of the maximum allowed length. There is nothing in the universe which suggests a factor of 0.25, it is purely arbitrary. It does however have a strong effect on the shape of the trees we're growing. It means it is impossible to accurately specify a twig length. There is a lot of room for experimentation and change here.
16 We create the arc that belongs to this twig.
17 If the distance between ptStart and ptGrow was 0.0 or if vecDir was parallel to ptStart » ptGrow then the arc could not be added. We need to catch this problem in time.
18 We need to know the tangent at the end of the newly created arc curve. The domain of a curve consists of two values (a lower and an upper bound). Rhino.CurveDomain(newTwig)(1) will return the upper bound of the domain. This is the same as calling: crvDomain = rs.CurveDomain(newTwig) vecGrow = rs.CurveTangent(newTwig, crvDomain[1])
19 Awooga! Awooga! A function calling itself! This is it! We made it! The thing to realize is that the call is now different. We're putting in different arguments which means this new function instance behaves differently than the current function instance.
It would have been possible to code this tree-generator in an iterative (For loops) fashion. The tree would look the same even though the code would be very different (probably a lot more lines). The order in which the branches are added would very probably also have differed. The trees below are archetypal, digital trees, the one on the left generated using iteration, the one on the right generated using recursion. Note the difference in branch order. If you look carefully at the recursive function on the previous page you'll probably be able to work out where this difference comes from... A small comparison table for different setting combinations. Please note that the trees have a very high random component. ## 8.7 NURBS Curves Circles and arcs are all fine and dandy, but they cannot be used to draw freeform shapes. For that you need splines. The worlds most famous spline is probably the Bézier curve, which was developed in 1962 by the French engineer *Pierre Bézier* while he was working for Renault. Most splines used in computer graphics these days are variations on the Bézier spline, and they are thus a surprisingly recent arrival on the mathematical scene. Other ground-breaking work on splines was done by *Paul de Casteljau* at Citroën and *Carl de Boor* at General Motors. The thing that jumps out here is the fact that all these people worked for car manufacturers. With the increase in engine power and road quality, the automobile industry started to face new problems halfway through the twentieth century, one of which was aerodynamics. New methods were needed to design mass-production cars that had smooth, fluent curves as opposed to the tangency and curvature fractured shapes of old. They needed mathematically accurate, freely adjustable geometry. Enter splines. Before we start with NURBS curves (the mathematics of which are a bit too complex for a scripting primer) I'd like to give you a sense of how splines work in general and Béziers work in particular. I'll explain the de Casteljau algorithm which is a very straightforward way of evaluating properties of simple splines. In practice, this algorithm will rarely be used since its performance is worse than alternate approaches, but due to its visual appeal it is easier to 'get a feel' for it. Splines limited to four control points were not the end of the revolution of course. Soon, more advanced spline definitions were formulated one of which is the NURBS curve. (Just to set the record straight; NURBS stands for Non-Uniform Rational [Basic/Basis] Spline and not Bézier-Spline as some people think. In fact, the Rhino help file gets it right, but I doubt many of you have read the glossary section, I only found out just now.) Bézier splines are a subset of NURBS curves, meaning that every Bézier spline can be represented by a NURBS curve, but not the other way around. Other curve types still in use today (but not available in Rhino) are Hermite, Cardinal, Catmull-Rom, Beta and Akima splines, but this is not a complete list. Hermite curves for example are used by the Bongo animation plug-in to smoothly transform objects through a number of keyframes. In addition to control point locations, NURBS curves have additional properties such as the degree, knot-vectors and weights. I'm going to assume that you already know how weight factors work (if you don't, it's in the Rhino help file under [NURBS About]) so I won't discuss them here. Instead, we'll continue with the correlation between degrees and knot-vectors. Every NURBS curve has a number associated with it which represents the degree. The degree of a curve is always a positive integer between and including 1 and 11. The degree of a curve is written as DN. Thus D1 is a degree one curve and D3 is a degree three curve. The table on the next page shows a number of curves with the exact same control-polygon but with different degrees. In short, the degree of a curve determines the range of influence of control points. The higher the degree, the larger the range. As you will recall from the beginning of this section, a quadratic Bézier curve is defined by four control points. A quadratic NURBS curve however can be defined by any number of control points (any number larger than three that is), which in turn means that the entire curve consists of a number of connected pieces. The illustration below shows a D3 curve with 10 control points. All the individual pieces have been given a different color. As you can see each piece has a rather simple shape; a shape you could approximate with a traditional, four-point Bézier curve. Now you know why NURBS curves and other splines are often described as "piece-wise curves". The shape of the red piece is entirely dictated by the first four control points. In fact, since this is a D3 curve, every piece is defined by four control points. So the second (orange) piece is defined by points {A; B; C; D}. The big difference between these pieces and a traditional Bézier curve is that the pieces stop short of the local control polygon. Instead of going all the way to {D}, the orange piece terminates somewhere in the vicinity of {C} and gives way to the green piece. Due to the mathematical magic of spline curves, the orange and green pieces fit perfectly, they have an identical position, tangency and curvature at point 4. As you may or may not have guessed at this point, the little circles between pieces represent the knot-vector of this curve. This D3 curve has ten control points and twelve knots (0~11). This is not a coincidence, the number of knots follows directly from the number of points and the degree: $$K_N = P_N + (D-1)$$ Where $${K_N}$$ is the knot count, $${P_N}$$ is the point count and $${D}$$ is the degree. In the image on the previous page, the red and purple pieces do in fact touch the control polygon at the beginning and end, but we have to make some effort to stretch them this far. This effort is called "clamping", and it is achieved by stacking a lot of knots together. You can see that the number of knots we need to collapse in order to get the curve to touch a control-point is the same as the degree of the curve: A clamped curve always has a bunch of knots at the beginning and end (periodic curves do not, but we'll get to that later). If a curve has knot clusters on the interior as well, then it will touch one of the interior control points and we have a kinked curve. There is a lot more to know about knots, but I suggest we continue with some simple nurbs curves and let Rhino worry about the knot vector for the time being. ### 8.7.1 Control-Point Curves The _FilletCorners command in Rhino puts filleting arcs across all sharp kinks in a polycurve. Since fillet curves are tangent arcs, the corners have to be planar. All flat curves though can always be filleted as the image to the right shows. The input curve {A} has nine G0 corners (filled circles) which qualify for a filleting operation and three G1 corners (empty circles) which do not. Since each segment of the polycurve has a length larger than twice the fillet radius, none of the fillets overlap and the result is a predictable curve {B}. Since blend curves are freeform they are allowed to twist and curl as much as they please. They have no problem with non-planar segments. Our assignment for today is to make a script which inserts blend corners into polylines. We're not going to handle polycurves (with freeform curved segments) since that would involve quite a lot of math and logic which goes beyond this simple curve introduction. This unfortunately means we won't actually be making non-planar blend corners. The logic of our BlendCorners script is simple: - Iterate though all segments of the polyline. - From the beginning of the segment $${A}$$, place an extra control point $${W_1}$$ at distance $${R}$$. - From the end of the segment $${B}$$, place an extra control point $${W_2}$$ at distance $${R}$$. - Put extra control-points halfway between $${A; W_1; W_2; B}$$. - Insert a $$D^5$$ nurbs curve using those new control points. Or, in graphic form: The first image shows our input curve positioned on a unit grid. The shortest segment has a length of 1.0, the longest segment a length of 6.0. If we're going to blend all corners with a radius of 0.75 (the circles in the second image) we can see that one of the edges has a conflict of overlapping blend radii. The third image shows the original control points (the filled circles) and all blend radius control points (the empty circles), positioned along every segment with a distance of {R} from its nearest neighbor The two red control points have been positioned 0.5 units away (half the segment length) from their respective neighbours. Finally, the last image shows all the control points that will be added in between the existing control points. Once we have an ordered array of all control points (ordered as they appear along the original polyline) we can create a $$D^5$$ curve using *rs.AddCurve()*. ```python def blendcorners(): polyline_id = rs.GetObject("Polyline to blend", 4, True, True) if not polyline_id: return vertices = rs.PolylineVertices(polyline_id) if not vertices: return radius = rs.GetReal("Blend radius", 1.0, 0.0) if radius is None: return between = lambda a,b: (a+b)/2.0 newverts = [] for i in range(len(vertices)-1): a = vertices[i] b = vertices[i+1] segmentlength = rs.Distance(a, b) vec_segment = rs.PointSubtract(b, a) vec_segment = rs.VectorUnitize(vec_segment) if radius<(0.5*segmentlength): vec_segment = rs.VectorScale(vec_segment, radius) else: vec_segment = rs.VectorScale(vec_segment, 0.5*segment_length) w1 = rs.VectorAdd(a, vec_segment) w2 = rs.VectorSubtract(b, vec_segment) newverts.append(a) newverts.append(between(a,w1)) newverts.append(w1) newverts.append(between(w1,w2)) newverts.append(w2) newverts.append(between(w2,b)) newverts.append(vertices[len(vertices)-1]) rs.AddCurve(newverts, 5) rs.DeleteObject(polyline_id) ```
Line Description
2...7 These calls prompt the user for a polyline, get the polyline's vertices, and then promps the user for the radius of blending. Since David Rutten is the only one allowed to be so careless as to not check for None values (and we are not David Rutten) each of these operations is followed by a failure check.
9...10 Sometimes, an operation that is needed within a loop results in the same values in each loop iteration. In cases like this, programs can be made much more efficient, by performing these operations before entering the loop. The variables between and newverts will not be used until line 25, but obtaining them here at lines 9 and 10 will make the script much more efficient.
11 Begin a loop for each segment in the polyline.
12...13 Store A and B coordinates for easy reference.
15...21 vec_segment is a scaled vector that points from A to B with a length of radius. Calculate the vec_segment vector. Typically this vector has length radius, but if the current polyline segment is too short to contain two complete radii, then adjust the vec_segment accordingly.
23...24 Calculate W1 and W2.
25...30 Store all points (except B) in the newverts list.
31 Append the last point of the polyline to the newverts list. We've omitted B everywhere because the A of the next segment has the same location and we do not want coincident control-points. The last segment has no next segment, so we need to make sure B is included this time.
32...33 Create a new D5 nurbs curve and delete the original.
### 8.7.2 Interpolated Curves When creating control-point curves it is very difficult to make them go through specific coordinates. Even when tweaking control-points this would be an arduous task. This is why commands like *_HBar* are so important. However, if you need a curve to go through many points, you're better off creating it using an interpolated method rather than a control-point method. The *_InterpCrv* and *_InterpCrvOnSrf* commands allow you to create a curve that intersects any number of 3D points and both of these methods have an equivalent in RhinoScriptSyntax. To demonstrate, we're going to create a script that creates iso-distance-curves on surfaces rather than the standard iso-parameter-curves, or "isocurves" as they are usually called. Isocurves, thus connect all the points in surface space that share a similar u or v value. Because the progression of the domain of a surface is not linear (it might be compressed in some places and stretched in others, especially near the edges where the surface has to be clamped), the distance between isocurves is not guaranteed to be identical either. The description of our algorithm is very straightforward, but I promise you that the actual script itself will be the hardest thing you've ever done. Our script will take any base surface (image A) and extract a number of isocurves (image B). Then, every isocurve is trimmed to a specific length (image C) and the end-points are connected to give the iso-distance-curve (the red curve in image D). Note that we are using isocurves in the v-direction to calculate the iso-distance-curve in the u-direction. This way, it doesn't matter much that the spacing of isocurves isn't distributed equally. Also note that this method is only useful for offsetting surface edges as opposed to *_OffsetCrvOnSrf* which can offset any curve. We can use the RhinoScriptSytnax methods *rs.ExtractIsoCurve()* and *rs.AddInterpCrvOnSrf()* for steps B and D, but step C is going to take some further thought. It is possible to divide the extracted isocurve using a fixed length, which will give us a whole list of points, the second of which marks the proper solution: In the example above, the curve has been divided into equal length segments of 5.0 units each. The red point (the second item in the collection) is the answer we're looking for. All the other points are of no use to us, and you can imagine that the shorter the distance we're looking for, the more redundant points we get. Under normal circumstances I would not think twice and simply use the *rs.DivideCurveLength()* method. However, I'll take this opportunity to introduce you to one of the most ubiquitous, popular and prevalent algorithms in the field of programming today: binary searching. Imagine you have a list of integers which is -say- ten thousand items long and you want to find the number closest to sixteen. If this list is unordered (as opposed to sorted) , like so: > {-2, -10, 12, -400, 80, 2048, 1, 10, 11, -369, 4, -500, 1548, 8, … , 13, -344} you have pretty much no option but to compare every item in turn and keep a record of which one is closest so far. If the number sixteen doesn't occur in the list at all, you'll have to perform ten thousand comparisons before you know for sure which number was closest to sixteen. This is known as a worst-case performance, the best-case performance would be a single comparison since sixteen might just happen to be the first item in the list... if you're lucky. The method described above is known as a list-search and it is a pretty inefficient way of searching a large dataset and since searching large datasets is something that we tend to do a lot in computer science, plenty research has gone into speeding things up. Today there are so many different search algorithms that we've had to put them into categories in order to keep a clear overview. However, pretty much all efficient searching algorithms rely on the input list being sorted, like so: > {-500, -400, -369, -344, -10, -2, 1, 4, 8, 10, 11, 12, 13, 80, … , 1548, 2048} Once we have a sorted list it is possible to improve our worst case performance by orders of magnitude. For example, consider a slightly more advanced list-search algorithm which aborts the search once results start to become worse. Like the original list-search it will start at the first item {-500}, then continue to the second item {-400}. Since {-400} is closer to sixteen than {-500}, there is every reason to believe that the next item in the list is going to be closer still. This will go on until the algorithm has hit the number thirteen. Thirteen is already pretty close to sixteen but there is still some wiggle room so we cannot be absolutely sure ({14; 15; 16; 17; 18} are all closer and {19} is equally close). However, the next number in the list is {80} which is a much, worse result than thirteen. Now, since this list is sorted we can be sure that every number after {80} is going to be worse still so we can safely abort our search knowing that thirteen is the closest number. Now, if the number we're searching for is near the beginning of the list, we'll have a vast performance increase, if it's near the end, we'll have a small performance increase. On average though, the sorted-list-search is twice as fast as the old-fashioned-list-search. Binary-searching does far better. Let us return to our actual problem to see how binary-searching works; find the point on a curve that marks a specific length along the curve. In the image below, the point we are looking for has been indicated with a small yellow tag, but of course we don't know where it is when we begin our search. Instead of starting at the beginning of the curve, we start halfway between {tmin} and {tmax} (halfway the domain of the curve). Since we can ask Rhino what the length is of a certain curve subdomain we can calculate the length from {tmin} to {1}. This happens to be way too much, we're looking for something less than half this length. Thus we divide the bit between {tmin} and {1} in half yet again, giving us {2}. We again measure the distance between {tmin} and {2}, and see that again we're too high, but this time only just. We keep on dividing the remainder of the domain in half until we find a value {6} which is close enough for our purposes: This is an example of the simplest implementation of a binary-search algorithm and the performance of binary searching is O(log n) which is a fancy way of saying that it's fast. Really, really fast. And what's more, when we enlarge the size of the collection we're searching, the time taken to find an answer doesn't increase in a similar fashion (as it does with list-searching). Instead, it becomes relatively faster and faster as the size of the collection grows. For example, if we double the size of the array we're searching to 20,000 items, a list-search algorithm will take twice as long to find the answer, whereas a binary-searcher only takes ~1.075 times as long. The theory of binary searching might be easy to grasp (maybe not right away, but you'll see the beauty eventually), any practical implementation has to deal with some annoying, code-bloating aspects. For example, before we start a binary search operation, we must make sure that the answer we're looking for is actually contained within the set. In our case, if we're looking for a point {P} on the curve {C} which is 100.0 units away from the start of {C}, there exists no answer if {C} is shorter than 100.0 itself. Also, since we're dealing with a parameter domain as opposed to a list of integers, we do not have an actual list showing all the possible values. This array would be too big to fit in the memory of your computer. Instead, all we have is the knowledge that any number between and including {tmin} and {tmax} is theoretically possible. Finally, there might not exist an exact answer. All we can really hope for is that we can find an answer within tolerance of the exact length. Many operations in computational geometry are tolerance bound, sometimes because of speed issues (calculating an exact answer would take far too long), sometimes because an exact answer cannot be found (there is simply no math available, all we can do is make a set of guesses each one progressively better than the last). At any rate, here's the binary-search script I came up with, I'll deal with the inner workings afterwards: ```python def BSearchCurve(idCrv, Length, Tolerance): Lcrv = rs.CurveLength(idCrv) if Lcrv Line Description 1 Note that this is not a complete script, it is only the search function. The complete script is supplied in the article archive. This function takes a curve ID, a desired length and a tolerance. The return value is None if no solution exists (i.e. if the curve is shorter than Length) or otherwise the parameter that marks the desired length. 2 Ask Rhino for the total curve length. 3 Make sure the curve is longer than Length. If it isn't, abort. 5...6 Store the minimum and maximum parameters of this curve domain. If you're confused about me calling the Rhino.CurveDomain() function twice instead of just once and store the resulting array, you may congratulate yourself. It would indeed be faster to not call the same method twice in a row. However, since lines 7 and 8 are not inside a loop, they will only execute once which reduces the cost of the penalty. 99% of the time spend by this function is because of lines 16~25, if we're going to be zealous about speed, we should focus on this part of the code. 7...8 t0, t1 and t will be the variables used to define our current subdomain. t0 will mark the lower bound and t1 the upper bound. t will be halfway between t0 and t1. We need to start with the whole curve in mind, so t0 and t1 will be similar to tmin and tmax. 9 Since we do not know in advance how many steps our binary searcher is going to take, we have to use an infinite loop. 10 Calculate t always exactly in the middle of {t0, t1}. 11 Calculate the length of the subcurve from the start of the curve (tmin) to our current parameter (t). 12 If this length is close enough to the desired length, then we are done and we can abort the infinite loop. abs() -in case you were wondering- is a Python function that returns the absolute (non-negative) value of a number. This means that the tolerance argument works equally strong in both directions, which is what you'd usually want. 13...14 This is the magic bit. Looks harmless enough doesn't it? What we do here is adjust the subdomain based on the result of the length comparison. If the length of the subcurve {tmin, t} is shorter than Length, then we want to restrict ourself to the lower half of the old subdomain. If, on the other hand, the subcurve length is shorter than Length, then we want the upper half of the old domain. Notice how much more compact programming code is compared to English? 15 Return the solved t-parameter. I have unleashed this function on a smooth curve with a fairly well distributed parameter space (i.e. no sudden jumps in parameter "density") and the results are listed below. The length of the total curve was 200.0 mm and I wanted to find the parameter for a subcurve length of 125.0 mm. My tolerance was set to 0.0001 mm. As you can see it took 18 refinement steps in the *BSearchCurve()* function to find an acceptable solution. Note how fast this algorithm homes in on the correct value, after just 6 steps the remaining error is less than 1%. Ideally, with every step the accuracy of the guess is doubled, in practise however you're unlikely to see such a neat progression. In fact, if you closely examine the table, you'll see that sometimes the new guess overshoots the solution so much it actually becomes worse than before (like between steps #9 and #10). I've greyed out the subdomain bound parameters that remained identical between two adjacent steps. You can see that sometimes multiple steps in the same direction are required. Now for the rest of the script as outlines on page 78: ```python def equidistanceoffset(): srf_id = rs.GetObject("Pick surface to offset", 8, True, True) if not srf_id: return offset = rs.GetReal("Offset distance", 1.0, 0.0) if not offset: return udomain = rs.SurfaceDomain(srf_id, 0) ustep = (udomain[1]-udomain[0])/200 rs.EnableRedraw(False) offsetvertices = [] u = udomain[0] while u<=(udomain[1]+0.5*ustep): isocurves = rs.ExtractIsoCurve(srf_id, (u,0), 1) if isocurves: t = BSearchCurve(isocurves[0], offset, 0.001) if t is not None: offsetvertices.append(rs.EvaluateCurve(isocurves[0], t)) rs.DeleteObjects(isocurves) u+=ustep if offsetvertices: rs.AddInterpCrvOnSrf(srf_id, offsetvertices) rs.EnableRedraw(True) ``` If I've done my job so far, the above shouldn't require any explanation. All of it is straight forward scripting code. The image on the right shows the result of the script, where offset values are all multiples of 10. The dark green lines across the green strip (between offsets 80.0 and 90.0) are all exactly 10.0 units long. ### 8.7.3 Geometric Curve Properties Since curves are geometric objects, they possess a number of properties or characteristics which can be used to describe or analyze them. For example, every curve has a starting coordinate and every curve has an ending coordinate. When the distance between these two coordinates is zero, the curve is closed. Also, every curve has a number of control-points, if all these points are located in the same plane, the curve as a whole is planar. Some properties apply to the curve as a whole, others only apply to specific points on the curve. For example, planarity is a global property while tangent vectors are a local property. Also, some properties only apply to some curve types. So far we've dealt with lines, polylines, circles, ellipses, arcs and nurbs curves: The last available curve type in Rhino is the polycurve, which is nothing more than an amalgamation of other types. A polycurve can be a series of line curves for example, in which case it behaves similarly to a polyline. But it can also be a combination of lines, arcs and nurbs curves with different degrees. Since all the individual segments have to touch each other (G0 continuity is a requirement for polycurve segments), polycurves cannot contain closed segments. However, no matter how complex the polycurve, it can always be represented by a nurbs curve. All of the above types can be represented by a nurbs curve. The difference between an actual circle and a nurbs-curve-that-looks-like-a-circle is the way it is stored. A nurbs curve doesn't have a Radius property for example, nor a Plane in which it is defined. It is possible to reconstruct these properties by evaluating derivatives and tangent vector and frames and so on and so forth, but the data isn't readily available. In short, nurbs curves lack some global properties that other curve types do have. This is not a big issue, it's easy to remember what properties a nurbs curve does and doesn't have. It is much harder to deal with local properties that are not continuous. For example, imagine a polycurve which has a zero-length line segment embedded somewhere inside. The t-parameter at the line beginning is a different value from the t-parameter at the end, meaning we have a curve subdomain which has zero length. It is impossible to calculate a normal vector inside this domain: This polycurve consists of five curve segments (a nurbs-curve, a zero-length line-segment, a proper line-segment, a 90° arc and another nurbs-curve respectively) all of which touch each other at the indicated t-parameters. None of them are tangency continuous, meaning that if you ask for the tangent at parameter {t3}, you might either get the tangent at the end of the purple segment or the tangent at the beginning of the green segment. However, if you ask for the tangent vector halfway between {t1} and {t2}, you get nothing. The curvature data domain has an even bigger hole in it, since both line-segments lack any curvature: When using curve properties such as tangents, curvature or perp-frames, we must always be careful to not blindly march on without checking for property discontinuities. An example of an algorithm that has to deal with this would be the *_CurvatureGraph* in Rhino. It works on all curve types, which means it must be able to detect and ignore linear and zero-length segments that lack curvature. One thing the *_CurvatureGraph* command does not do is insert the curvature graph objects, it only draws them on the screen. We're going to make a script that inserts the curvature graph as a collection of lines and interpolated curves. We'll run into several issues already outlined in this paragraph. In order to avoid some *G* continuity problems we're going to tackle the problem span by span. In case you haven't suffered left-hemisphere meltdown yet; the shape of every knot-span is determined by a certain mathematical function known as a polynomial and is (in most cases) completely smooth. A span-by-span approach means breaking up the curve into its elementary pieces, as shown on the left: This is a polycurve object consisting of seven pieces; lines {A; C; E}, arcs {B; D} and nurbs curves {F; G}. When we convert the polycurve to a nurbs representation we get a degree 5 nurbs curve with 62 pieces (knot-spans). Since this curve was made by joining a bunch of other curves together, there are kinks between all individual segments. A kink is defined as a grouping of identical knots on the interior of a curve, meaning that the curve actually intersects one of its interior control-points. A kink therefore has the potential to become a sharp crease in an otherwise smooth curve, but in our case all kinks connect segments that are G1 continuous. The kinks have been marked by white circles in the image on the right. As you can see there are also kinks in the middle of the arc segments {B; D}, which were there before we joined the curves together. In total this curve has ten kinks, and every kink is a grouping of five similar knot parameters (this is a D5 curve). Thus we have a sum-total of 40 zero-length knot-spans. Never mind about the math though, the important thing is that we should prepare for a bunch of zero-length spans so we can ignore them upon confrontation. The other problem we'll get is the property evaluation issue I talked about on the previous page. On the transition between knots the curvature data may jump from one value to another. Whenever we're evaluating curvature data near knot parameters, we need to know if we're coming from the left or the right. I'm sure all of this sounds terribly complicated. In fact, I'm sure it is terribly complicated, but these things should start to make sense. It is no longer enough to understand how scripts work under ideal circumstances, by now, you should understand why there are no ideal circumstances and how that affects programming code. Since we know exactly what we need to do in order to mimic the *_CurvatureGraph* command, we might as well start at the bottom. The first thing we need is a function that creates a curvature graph on a subcurve, then we can call this function with the knot parameters as sub-domains in order to generate a graph for the whole curve: Our function will need to know the ID of the curve in question, the subdomain {t0; t1}, the number of samples it is allowed to take in this domain and the scale of the curvature graph. The return value should be a collection of object IDs which were inserted to make the graph. This means all the perpendicular red segments and the dashed black curve connecting them. ```python def addcurvaturegraphsection(idCrv, t0, t1, samples, scale): if (t1-t0)<=0.0: return N = -1 tstep = (t1-t0)/samples t = t0 points = [] objects = [] while t<=(t1+(0.5*tstep)): if t>=t1:t = t1-1e-10 N += 1 cData = rs.CurveCurvature(idCrv, t) if not cData: points.append( rs.EvaluateCurve(idCrv, t) ) else: c = rs.VectorScale(cData[4], scale) a = cData[0] b = rs.VectorSubtract(a, c) objects.append(rs.AddLine(a,b)) points.append(b) t += tstep objects.append(rs.AddInterpCurve(points)) return objects ```
Line Description
2 Check for a null span, this happens inside kinks.
4 Determine a step size for our loop (Subdomain length / Sample count).
7 objects() will hold the IDs of the perpendicular lines, and the connecting curve.
8 Define the loop and make sure we always process the final parameter by increasing the threshold with half the step size.
9 Make sure t does not go beyond t1, since that might give us the curvature data of the next segment.
13 In case of a curvature data discontinuity, do not add a line segment but append the point on the curve at the current curve coordinate t.
15...19 Compute the A and B coordinates, append them to the appropriate array and add the line segment.
Now, we need to write a utility function that applies the previous function to an entire curve. There's no rocket science here, just an iteration over the knot-vector of a curve object: ```python def addcurvaturegraph( idCrv, spansamples, scale): allGeometry = [] knots = rs.CurveKnots(idCrv) p=5 for i in range(len(knots)-1): tmpGeometry = addcurvaturegraphsection(idCrv, knots[i], knots[i+1], spansamples, scale) if tmpGeometry: allGeometry.append(tmpGeometry) rs.AddObjectsToGroup(allGeometry, rs.AddGroup()) return allGeometry ```
Line Description
2 allGeometry will be a list of all IDs generated by repetitive calls to AddCurvatureGraphSection()
3 knots is the knot vector of the nurbs representation of idCrv.
5 We want to iterate over all knot spans, meaning we have to iterate over all (except the last) knot in the knot vector. Hence the minus one at the end.
6 Place a call to addcurvaturegraphsection() and store all resulting IDs in tmpGeometry.
7 If the result of AddCurvatureGraphSection() is not Null, then append all items in tmpGeometry to allGeometry.
8 Put all created objects into a new group.
The last bit of code we need to write is a bit more extensive than we've done so far. Until now we've always prompted for a number of values before we performed any action. It is actually far more user-friendly to present the different values as options in the command line while drawing a preview of the result. UI code tends to be very beefy, but it rarely is complex. It's just irksome to write because it always looks exactly the same. In order to make a solid command-line interface for your script you have to do the following: - Reserve a place where you store all your preview geometry - Initialize all settings with sensible values - Create all preview geometry using the default settings - Display the command line options - Parse the result (be it escape, enter or an option or value string) - Select case through all your options - If the selected option is a setting (as opposed to options like "Cancel" or "Accept") then display a prompt for that setting - Delete all preview geometry - Generate new preview geometry using the changed settings. ```python def createcurvaturegraph(): curve_ids = rs.GetObjects("Curves for curvature graph", 4, False, True, True) if not curve_ids: return samples = 10 scale = 1.0 preview = [] while True: rs.EnableRedraw(False) for p in preview: rs.DeleteObjects(p) preview = [] for id in curve_ids: cg = addcurvaturegraph(id, samples, scale) preview.append(cg) rs.EnableRedraw(True) result = rs.GetString("Curvature settings", "Accept", ("Samples", "Scale", "Accept")) if not result: for p in preview: rs.DeleteObjects(p) break result = result.upper() if result=="ACCEPT": break elif result=="SAMPLES": numsamples = rs.GetInteger("Number of samples per knot-span", samples, 3, 100) if numsamples: samples = numsamples elif result=="SCALE": sc = rs.GetReal("Scale of the graph", scale, 0.01, 1000.0) if sc: scale = sc ```
Line Description
2 Prompt for any number of curves, we do not want to limit our script to just one curve.
5...6 Our default values are a scale factor of 1.0 and a span sampling count of 10.
8 preview() is a list that contains arrays of IDs. One for each curve in idCurves.
9 Since users are allowed to change the settings an infinite number of times, we need an infinite loop around our UI code.
10...11 First of all, delete all the preview geometry, if present.
13...15 Then, insert all the new preview geometry.
28 Once the new geometry is in place, display the command options. The array at the end of the rs.GetString() method is a list of command options that will be visible.
19...21 If the user aborts (pressed Escape), we have to delete all preview geometry and exit the sub.
23...29 If the user clicks on an option, result will be the option name. The best method IronPython implements to treat the choice is the If...Then statement shown.
23 In the case of "Accept", all we have to do is exit the sub without deleting the preview geometry.
24...26 If the picked option was "Samples", then we have to ask the user for a new sample count. If the user pressed Escape during this nested prompt, we do not abort the whole script (typical Rhino behaviour would dictate this), but instead return to the base prompt.
27...29 If the picked option was "Scale", then we have to ask the user for a new scale factor, and . If the user pressed Escape during this nested prompt, we do not abort the whole script (typical Rhino behaviour would dictate this), but instead return to the base prompt.
## 8.8 Meshes Instead of Nurbs surfaces (which would be the next logical step after nurbs curves), this chapter is about meshes. I'm going to take this opportunity to introduce you to a completely different class of geometry -officially called "polygon meshes"- which represents a radically different approach to shape. Instead of treating a surface as a deformation of a rectangular nurbs patch, meshes are defined locally, which means that a single mesh surface can have any topology it wants. A mesh surface can even be a disjoint (not connected) compound of floating surfaces, something which is absolutely impossible with Rhino nurbs surfaces. Because meshes are defined locally, they can also store more information directly inside the mesh format, such as colors, texture-coordinates and normals. The tantalizing image below indicates the local properties that we can access via RhinoScriptSyntax. Most of these properties are optional or have default values. The only essential ones are the vertices and the faces. It is important to understand the pros and cons of meshes over alternative surface paradigms, so you can make an informed decision about which one to use for a certain task. Most differences between meshes and nurbs are self-evident and flow from the way in which they are defined. For example, you can delete any number of polygons from the mesh and still have a valid object, whereas you cannot delete knot spans without breaking apart the nurbs geometry. There's a number of things to consider which are not implied directly by the theory though. - Coordinates of mesh vertices are stored as single precision numbers in Rhino in order to save memory consumption. Meshes are therefore less accurate entities than nurbs objects. This is especially notable with objects that are very small, extremely large or very far away from the world origin. Mesh objects go hay-wire sooner than nurbs objects because single precision numbers have larger gaps between them than double precision numbers (see page 6). - Nurbs cannot be shaded, only the isocurves and edges of nurbs geometry can be drawn directly in the viewport. If a nurbs surface has to be shaded, then it has to fall back on meshes. This means that inserting nurbs surfaces into a shaded viewport will result in a significant (sometimes very significant) time lag while a mesh representation is calculated. - Meshes in Rhino can be non-manifold, meaning that more than two faces share a single edge. Although it is not technically impossible for nurbs to behave in this way, Rhino does not allow it. Non-manifold shapes are topologically much harder to deal with. If an edge belongs to only a single face it is an exterior edge (naked), if it belongs to two faces it is considered interior. ### 8.8.1 Geometry vs. Topology As mentioned before, only the vertices and faces are essential components of the mesh definition. The vertices represent the geometric part of the mesh definition, the faces represent the topological part. Chances are you have no idea what I'm talking about... allow me to explain. According to MathWorld.com topology is "*the mathematical study of the properties that are preserved through deformations, twistings, and stretching of objects.*" In other words, topology doesn't care about size, shape or smell, it only deals with the platonic properties of objects, such as "how many holes does it have?", "how many naked edges are there?" and "how do I get from Paris to Lyon without passing any tollbooths?". The field of topology is partly common-sense (everybody intuitively understands the basics) and partly abstract-beyond-comprehension. Luckily we're only confronted with the intuitive part here. If you look at the images above, you'll see a number of surfaces that are topologically identical (except {E}) but geometrically different. You can bend shape {A} and end up with shape {B}; all you have to do is reposition some of the vertices. Then if you bend it even further you get {C} and eventually {D} where the right edge has been bend so far it touches the edge on the opposite side of the surface. It is not until you merge the edges (shape {E}) that this shape suddenly changes its platonic essence, i.e. it goes from a shape with four edges to a shape with only two edges (and these two remaining edges are now closed loops as well). Do note that shapes {D} and {E} are geometrically identical, which is perhaps a little surprising. The vertices of a mesh object are a list of 3D point coordinates. They can be located anywhere in space and they control the size and form of the mesh. The faces on the other hand do not contain any coordinate data, they merely indicate how the vertices are to be connected: Here you see a very simple mesh with sixteen vertices and nine faces. Commands like *_Scale*, *_Move* and *_Bend* only affect the vertex-list, commands like *_TriangulateMesh* and *_SwapMeshEdge* only affect the face-list, commands like *_ReduceMesh* and *_MeshTrim* affect both lists. Note that the last face {*I*} has its corners defined in a clockwise fashion, whereas all the other faces are defined counter-clockwise. Although this makes no geometric difference, it does affect how the mesh normals are calculated and one should generally avoid creating meshes that are cw/ccw inconsistent. Now that we know what meshes essentially consist of, we can start making mesh shapes from scratch. All we need to do is come up with a set of matching vertex/face arrays. We'll start with the simplest possible shape, a mesh plane consisting of a grid of vertices connected with quads. Just to keep matters marginally interesting, we'll mutate the z-coordinates of the grid points using a user-specified mathematical function in the form of: $$f(x, y, \Theta, \Delta) = ...$$ Where the user is allowed to specify any valid mathematical function using the variables *x*, *y*, *Θ* and *Δ*. Every vertex in the mesh plane has a unique combination of *x* and *y* values which can be used to determine the *z* value of that vertex by evaluating the custom function (*Θ* and *Δ* are the polar coordinates of *x* and *y*). This means every vertex {A} in the plane has a coordinate {B} associated with it which shares the *x* and *y* components, but not the *z* component. We'll run into four problems while writing this script which we have not encountered before, but only two of these have to do with mesh geometry/topology: It's easy enough to generate a grid of points, we've done similar looping before where a nested loop was used to generate a grid wrapped around a cylinder. The problem this time is that it's not enough to generate the points. We also have to generate the face-list, which is highly dependent on the row and column dimensions of the vertex list. It's going to take a lot of logic insight to get this right (probably easiest to make a schematic first). Let us turn to the problem of generating the vertex coordinates, which is a straightforward one: ```python def createmeshvertices(function, fdomain, resolution): xstep = (fdomain[1]-fdomain[0])/resolution ystep = (fdomain[3]-fdomain[2])/resolution v = [] x = fdomain[0] while x <= fdomain[1]+(0.5*xstep): y = fdomain[2] while y<=fdomain[3]+(0.5*ystep): z = solveequation(function, x, y) v.append( (x,y,z) ) y += ystep x += xstep return v ```
Line Description
1 This function is to be part of the finished script. It is a very specific function which merely combines the logic of nested loops with other functions inside the same script (functions which we haven't written yet, but since we know how they are supposed to work we can pretend as though they are available already). This function takes three arguments:
  1. A String variable which contains the format of the function {f(x,y,Θ,Δ) = …}
  2. An array of four doubles, indicating the domain of the function in x and y directions
  3. An integer which tells us how many samples to take in each direction
2...3 The fDomain() argument has four doubles, arranged like this: (0) Minimum x-value (1) Maximum x-value (2) Minimum y-value (3) Maximum y-value We can access those easily enough, but since the step size in x and y direction involves so much math, it's better to cache those values, so we don't repeat the same calculation over and over again.
6 Begin at the lower end of the x-domain and step through the entire domain until the maximum value has been reached. We can refer to this loop as the row-loop.
8 Begin at the lower end of the y-domain and step through the entire domain until the maximum value has been reached. We can refer to this loop as the column-loop.
9 This is where we're calling an -as of yet- non-existent function. However, I think the signature is straightforward enough to not require further explanation now.
9...11 Append the new vertex to the V list. Note that vertices are stored as a one-dimensional list, which makes accessing items at a specific (row, column) coordinate slightly cumbersome.
Once we have our vertices, we can create the face list that connects them. Since the face-list is topology, it doesn't matter where our vertices are in space, all that matters is how they are organized. The image on the right is the mesh schematic that I always draw whenever confronted with mesh face logic. The image shows a mesh with twelve vertices and six quad faces, which has the same vertex sequence logic as the vertex list created by the function on the previous page. The vertex counts in x and y direction are four and three respectively (Nx=4, Ny=3). Now, every quad face has to link the four vertices in a counter-clockwise fashion. You may have noticed already that the absolute differences between the vertex indices on the corners of every quad are identical. In the case of the lower left quad *{A=0; B=3; C=4; D=1}*. In the case of the upper right quad *{A=7; B=10; C=11; D=8}*. We can define these numbers in a simpler way, which reduces the number of variables to just one instead of four: *{A=?; B=(A+Ny); C=(B+1); D=(A+1)}*, where *Ny* is the number of vertices in the y-direction. Now that we know the logic of the face corner numbers, all that is left is to iterate through all the faces we need to define and calculate proper values for the *A* corner: ```python def createmeshfaces(resolution): nX = resolution nY = resolution f = [] for i in range(nX-1): for j in range(nY-1): baseindex = i*(nY+1)+j f.append( (baseindex, baseindex+1, baseindex+nY+2, baseindex+nY+1) ) return f ```
Line Description
2...3 Cache the {Nx} and {Ny} values, they are the same in our case because we do not allow different resolutions in {x} and {y} direction.
4 Declare a list to store the faces we will create.
5...6 These two nested loops are used to iterate over the grid and define a face for each row/column combo. I.e. the two values i and j are used to define the value of the A corner for each face.
7 Instead of the nondescript "A", we're using the variable name baseIndex. This value depends on the values of both i and j. The i value determines the index of the current column and the j value indicates the current offset (the row index).
8 Define the new quad face corners using the logic stated above.
Writing a tool which works usually isn't enough when you write it for other people. Apart from just working, a script should also be straightforward to use. It shouldn't allow you to enter values that will cause it to crash (come to think of it, it shouldn't crash at all), it should not take an eternity to complete and it should provide sensible defaults. In the case of this script, the user will have to enter a function which is potentially very complex, and also four values to define the numeric domain in {x} and {y} directions. This is quite a lot of input and chances are that only minor adjustments will be made during successive runs of the script. It therefore makes a lot of sense to remember the last used settings, so they become the defaults the next time around. There's a number of ways of storing persistent data when using scripts, each with its own advantages: We'll be using a \*.txt file to store our data since it involves very little code and it survives a Rhino restart. An \*.txt file is a textfile which stores a number of Strings in a one-level hierarchical format. ```python def SaveFunctionData(strFunction, fDomain, Resolution): file = open("MeshSettings_XY.txt", "w") file.write(strFunction) file.write("\n") for d in fDomain: file.write(str(d)) file.write("\n") file.write(str(Resolution)) file.close() ```
Line Description
2 This is a specialized function written specifically for this script. The signature consists only of the data it has to store. The open keyword creates a stream to the file we will be modifying. Specifying a file name without a path saves the file to the directory where the script resides. The second parameter indicates what the stream will be doing - writing in this instance.
3...8 Write all settings successively to the file. We will be writing them in a specific order - strFunction, fDomain values 0 through 3, and the Resolution. The same order will be used to recover them later.
9 This call finalizes modifications to the file, and closes it for other operations.
The contents of the \*.txt file should look something like this: Reading data from an \*.txt file is slightly more involved, because there is no guarantee the file exists yet. Indeed, the first time you run this script there won't be a settings file yet and we need to make sure we supply sensible defaults: ```python def loadfunctiondata(): try: file = open("MeshSettings_XY.txt", "r") items = file.readlines() file.close() function = str(items[0]) domain = (float(items[1]), float(items[2]), float(items[3]), float(items[4])) resolution = int(items[5]) except: function = "math.cos(math.sqrt(x**2+y**2))" domain = (-10.0, 10.0, -10.0, 10.0) resolution = 50 return function, domain, resolution ```
Line Description
2 10 This function needs to handle two possible conditions, the first being the first time it is called, and the second being all successive calls. The first time, there will be no "MeshSettings_XY.txt" file, so we will need to return default values, and create one later. This statement attempts to access the "MeshSettings_XY.txt" file in lines 3 to 5, and upon failure, moves to lines 11 to 13, in order to
3 Obviously we need the exact same file name. If the file does not exist, the script will throw an exception. Not to worry, though. The try...except statement we implemented earlier will handle it, and return our default values.
4 This is where we read the data strings from the \*.txt file.
6...9 The items recovered from the \*.txt file are distributed to their respective variables in the order that they were written to the file.
11...13 If an exception was thrown, we will need to return a set of default values. These are defined here.
We've now dealt with two out of four problems (mesh topology, saving and loading persistent settings) and it's time for the big ones. In our CreateMeshVertices() procedure we've placed a call to a function called SolveEquation() eventhough it didn't exist yet. SolveEquation() has to evaluate a user-defined function for a specific {x,y} coordinate which is something we haven't done before yet. It is very easy to find the answer to the question: "What is the value of {Sin(x) + Sin(y)} for {x=0.5} and {y=2.7} ?" However, this involves manually writing the equation inside the script and then running it. Our script has to evaluate custom equations which are not known until after the script starts. This means in turn that the equation is stored as a String variable. The *eval* statement runs a script inside a script. The *eval* statement takes a single String and attempts to run it as a bit of code, but nested inside the current scope. That means that you can refer to local variables inside an *eval*. This bit of magic is exactly what we need in order to evaluate expressions stored in Strings. We only need to make sure we set up our *x, y, Θ* and *Δ* variables prior to using *eval*. The fourth big problem we need to solve has to do with nonsensical users (a certain school of thought popular among programmers claims that *all* users should be assumed to be nonsensical). It is possible that the custom function is not valid Python syntax, in which case the *eval* statement will not be able to parse it. This could be because of incomplete brackets, or because of typos in functions or a million other problems. But even if the function is syntactically correct it might still crash because of incorrect mathematics. For example, if you try to calculate the value of *Sqr(-4.0)*, the script crashes with the "Invalid procedure call or argument" error message. The same applies to *Log(-4.0)*. These functions crash because there exists no answer for the requested value. Other types of mathematical problems arise with large numbers. *Exp(1000)* for example results in an "Overflow" error because the result falls outside the double range. Another favorite is the "Division by zero" error. The following table lists the most common errors that occur in the Python engine: See [Python's list of Built-In Exceptions](http://docs.python.org/release/3.1.3/library/exceptions.html#bltin-exceptions) for the complete list and descriptions of each. As you can see there's quite a lot that can go wrong. We should be able to prevent this script from crashing, even though we do not control the entire process. We could of course try to make sure that the user input is valid and will not cause any computational problems, but it is much easier to just let the script fail and recover from a crash after it happened. We've used the error catching mechanism previously, but back then we were just lazy, now there is no other solution. The try/except statement can be used in Python as a great technique for error handling. First, the user implements a statement to "Try," if this works then the statement is executed and we are finished. Otherwise, if an exception occurs we go straight to the "except" portion. If the error matches the exception named, the portion of code within the "except" segment is executed and we continue on. If an error happens that does not match the named "exception" then an "unhandled exception" message is thrown. Note - a try statement may have many except clauses and any given except clause may have multiple exceptions it is testing for! ```python def solveequation( function, x, y ): d = 10 angledata = rs.Angle( (0,0,0), (x,y,0)) a = 0.0 if angledata: a = angledata[0] * math.pi/180 try: z = eval(function) except: z = 0 return z ``` The amount of stuff the above bit of magic does is really quite impressive. It converts the {x; y} coordinates into polar coordinates {A; D} (for Angle and Distance), makes sure the angle is an actual value, in case both {x} and {y} turn out to be zero. It solves the equation to find the z-coordinate, and sets {z} to zero in case a the equation was unsolvable. Now that all the hard work is done, all that is left is to write the over arching function that provides the interface for this script, which I don't think needs further explanation: ```python def meshfunction_xy(): zfunc, domain, resolution = loadfunctiondata() zfunc = rs.StringBox( zfunc, "Specify a function f(x,y[,D,A])", "Mesh function") if not zfunc: return while True: prompt = "Function domain x{%f,%f} y{%f,%f} @%d" % (domain[0], domain[1], domain[2], domain[3], resolution) result = rs.GetString(prompt, "Insert", ("xMin","xMax","yMin","yMax","Resolution","Insert")) if not result: return result = result.upper() if result=="XMIN": f = rs.GetReal("X-Domain start", domain[0]) if f is not None: domain[0]=f elif result=="XMAX": f = rs.GetReal("X-Domain end", domain[1]) if f is not None: domain[1]=f elif result=="YMIN": f = rs.GetReal("Y-Domain start", domain[2]) if f is not None: domain[2]=f elif result=="YMAX": f = rs.GetReal("Y-Domain end", domain[3]) if f is not None: domain[3]=f elif result=="RESOLUTION": f = rs.GetInteger("Resolution of the graph", resolution) if f is not None: resolution=f elif result=="INSERT": break verts = createmeshvertices(zfunc, domain, resolution) faces = createmeshfaces(resolution) rs.AddMesh(verts, faces) ``` The default function Cos(Sqr(x^2 + y^2)) is already quite pretty, but here are some other functions to play with as well:
Notation Syntax result
$$\cos\left(\sqrt{x^2 + y^2}\right)$$ math.cos(math.sqrt(x*x + y*y))
$$\sin(x) + \sin(y)$$ math.sin(x) + math.sin(y)
$$\sin(D + A)$$ math.sin(D+A)
$$Atn\left(\sqrt{x^2 + y^2}\right)$$ math.atan(x*x + y*y) -or- math.atan(D)
$$\sqrt{|x|} + \sin(y)^{16}$$ math.sqrt(math.fabs(x))+math.pow(math.sin(y),16)
$$\sin\left(\sqrt{\min(x^2, y^2)}\right)$$ math.sin(min(math.pow([x*x, y*y]),0.5))
$$\left[\sin(x) + \sin(y) + x + y\right]$$ int(math.sin(x) + math.sin(y) + x + y)
$$\log\left(\sin(x) + \sin(y) + 2.01\right)$$ math.log(math.sin(x)+math.sin(y)+2.01)
### 8.8.2 Shape vs. Image The vertex and face lists of a mesh object define its form (geometry and topology) but meshes can also have local display attributes. Colors and Texture-coordinates are two of these that we can control via RhinoScriptSyntax. The color list (usually referred to as 'False-Colors') is an optional mesh property which defines individual colors for every vertex in the mesh. The only Rhino commands that I know of that generate meshes with false-color data are the analysis commands *(_DraftAngleAnalysis, _ThicknessAnalysis, _CurvatureAnalysis and so on and so forth)* but unfortunately they do not allow you to export the analysis meshes. Before we do something useful with False-Color meshes, let's do something simple, like assigning random colours to a mesh object: ```python def randommeshcolors(): mesh_id = rs.GetObject("Mesh to randomize", 32, True, True) if not mesh_id: return verts = rs.MeshVertices(mesh_id) faces = rs.MeshFaceVertices(mesh_id) colors = [] for vert in verts: rgb = random()*255, random()*255, random()*255 colors.append(rgb) rs.AddMesh(verts, faces, vertex_colors=colors) rs.DeleteObject(mesh_id) ```
Line Description
7...11 The False-Color array is optional, but there are rules to using it. If we decide to specify a False-Color array, we have to make sure that it has the exact same number of elements as the vertex array. After all, every vertex needs its own colour. We must also make sure that every element in the False-Color array represents a valid colour. Colours in Rhino are defined as integers which store the red, green and blue channels. The channels are defined as numbers in the range {0; 255}, and they are mashed together into a bigger number where each channel is assigned its own niche. The advantage of this is that all colours are just numbers instead of more complex data-types, but the downside is that these numbers are usually meaningless for mere mortals: 1 Lowest possible value 2 Highest possible value
Random colors may be pretty, but they are not useful. All the Rhino analysis commands evaluate a certain geometrical local property (curvature, verticality, intersection distance, etc), but none of them take surroundings into account. Let's assume we need a tool that checks a mesh and a (poly)surface for proximity. There is nothing in Rhino that can do that out of the box. So this is actually going to be a useful script, plus we'll make sure that the script is completely modular so we can easily adjust it to analyze other properties. We'll need a function who's purpose it is to generate an array of numbers (one for each vertex in a mesh) that define some kind of property. These numbers are then in turn translated into a gradient (red for the lowest number, white for the highest number in the set) and applied as the False-Color data to a new mesh object. In our case the property is the distance from a certain vertex to the point on a (poly)surface which is closest to that vertex: Vertex {A} on the mesh has a point associated with it {Acp} on the box and the distance between these two {DA} is a measure for proximity. This measure is linear, which means that a vertex which is twice as far away gets a proximity value which is twice as high. A linear distribution is indicated by the red line in the adjacent graph. It actually makes more intuitive sense to use a logarithmic scale (the green line), since it is far better at dealing with huge value ranges. Imagine we have a mesh whose sorted proximity value set is something like: {0.0; 0.0; 0.0; 0.1; 0.2; 0.5; 1.1; 1.8; 2.6; … ; 9.4; 1000.0} As you can see pretty much all the variation is within the {0.0; 10.0} range, with just a single value radically larger. Now, if we used a linear approach, all the proximity values would resolve to completely red, except for the last one which would resolve to completely white. This is not a useful gradient. When you run all the proximity values through a logarithm you end up with a much more natural distribution: There is just one snag, the logarithm function returns negative numbers for input between zero and one. In fact, the logarithm of zero is minus-infinity, which plays havoc with all mathematics down the road since infinity is way beyond the range of numbers we can represent using doubles. And since the smallest possible distance between two points in space is zero, we cannot just apply a logarithm and expect our script to work. The solution is a simple one, add 1.0 to all distance values prior to calculating the logarithm, and all our results are nice, positive numbers. ```python def VertexValueArray(points, id): return [DistanceTo(point, id) for point in points] def DistanceTo(pt, id): ptCP = rs.BrepClosestPoint(id,pt) if ptCP: d = rs.Distance(pt, ptCP[0]) return math.log10(d+1) ```
Line Description
1...2 The VertexValueArray() function is the one that creates a list of numbers for each vertex. We're giving it the mesh vertices (an array of 3D points) and the object ID of the (poly)surface for the proximity analysis. This function doesn't do much, it simply iterates through the list of points using the DistanceTo() function, and returns a list of the results.
4...8 DistanceTo() calculates the distance from pt to the projection of pt onto id. Where pt is a single 3D coordinate and id if the identifier of a (poly)surface object. It also performs the logarithmic conversion, so the return value is not the actual distance.
And the master Sub containing all the front end and color magic: ```python import rhinoscriptsyntax as rs import sys import math def ProximityAnalysis(): mesh_id = rs.GetObject("Mesh for proximity analysis", 32, True, True) if not mesh_id: return brep_id = rs.GetObject("Surface for proximity test", 8+16, False, True) if not brep_id: return vertices = rs.MeshVertices(mesh_id) faces = rs.MeshFaceVertices(mesh_id) listD = VertexValueArray(vertices, brep_id) minD = sys.float_info.min maxD = sys.float_info.max for ct in range(len(listD)): if minD>listD[ct]: minD = listD[ct] if maxD Line Description 1...3 There are a couple of import statements that may look unfamiliar here. In some scripts, the use of outside resources can come in handy. Importing the System namespace allows us to use objects from the .Net framework, such as the maximum and minimum values of all floating point variables. 16...20 Since there is not a function in the math namespace, .net, or the rhinoscriptsyntax methods to get the max and min values of an array of numbers, we will have to write some code to get the maximum and minimum values of listD. The .Net framework is a wonderful place, and for the first time, IronPython allows its use in scripts within Rhinoceros. We call the System namespace, and get the max and min values of all double-precision numbers, as a starting point. We then iterate through the items in listD, comparing each value to the current value of maxD and minD, replacing them if we happen to find a more suitable member of the list for either. Once we have iterated through the entire list, we are certain we have the max and min values. 22 Create the False-Color array.. 24 Calculate the position on the {Red~White} gradient for the current value. 25 Cook up a colour based on the proxFactor. ## 8.9 Surfaces At this point you should have a fair idea about the strengths and flexibility of mesh based surfaces. It is no surprise that many industries have made meshes their primary means of surfacing. However, meshes also have their disadvantages and this is where other surfacing paradigms come into play. In fact, meshes (like nurbs) are a fairly recent discovery whose rise to power depended heavily on demand from the computer industry. Mathematicians have been dealing with different kinds of surface definitions for centuries and they have come up with a lot of them; surfaces defined by explicit functions, surfaces defined by implicit equations, minimal area surfaces, surfaces of revolutions, fractal surfaces and many more. Most of these types are far too abstract for your every-day modeling job, which is why most CAD packages do not implement them.
Schwarz D surface, a triply periodic minimal surface which divides all of space between here and the edge of creation into two equal chunks. Easy to define mathematically, hard to model manually.
Apart from a few primitive surface types such as spheres, cones, planes and cylinders, Rhino supports three kinds of freeform surface types, the most useful of which is the Nurbs surface. Similar to curves, all possible surface shapes can be represented by a Nurbs surface, and this is the default fall-back in Rhino. It is also by far the most useful surface definition and the one we will be focusing on. ### 8.9.1 NURBS Surfaces Nurbs surfaces are very similar to Nurbs curves. The same algorithms are used to calculate shape, normals, tangents, curvatures and other properties, but there are some distinct differences. For example, curves have tangent vectors and normal planes, whereas surfaces have normal vectors and tangent planes. This means that curves lack orientation while surfaces lack direction. This is of course true for all curve and surface types and it is something you'll have to learn to live with. Often when writing code that involves curves or surfaces you'll have to make assumptions about direction and orientation and these assumptions will sometimes be wrong. In the case of NURBS surfaces there are in fact two directions implied by the geometry, because NURBS surfaces are rectangular grids of {u} and {v} curves. And even though these directions are often arbitrary, we end up using them anyway because they make life so much easier for us. But lets start with something simple which doesn't actually involve NURBS surface mathematics on our end. The problem we're about to be confronted with is called Surface Fitting and the solution is called Error Diffusion. You have almost certainly come across this term in the past, but probably not in the context of surface geometry. Typically the words "error diffusion" are only used in close proximity to the words "color", "pixel" and "dither", but the wide application in image processing doesn't limit error diffusion algorithms to the 2D realm. The problem we're facing is a mismatch between a given surface and a number of points that are supposed to be on it. We're going to have to change the surface so that the distance between it and the points is minimized. Since we should be able to supply a large amount of points (and since the number of surface control-points is limited and fixed) we'll have to figure out a way of deforming the surface in a non-linear fashion (i.e. translations and rotations alone will not get us there). Take a look at the images below which are a schematic representation of the problem: For purposes of clarity I have unfolded a very twisted nurbs patch so that it is reduced to a rectangular grid of control-points. The diagram you're looking at is drawn in {uvw} space rather than world {xyz} space. The actual surface might be contorted in any number of ways, but we're only interested in the simplified {uvw} space. The surface has to pass through point {S}, but currently the two entities are not intersecting. The projection of {S} onto the surface {S'} is a certain distance away from {S} and this distance is the error we're going to diffuse. As you can see, {S'} is closer to some control points than others. Especially {F} and {G} are close, but {B; C; J; K} can also be considered adjacent control points. Rather than picking a fixed number of nearby control points and moving those in order to reduce the distance between {S} and {S'}, we're going to move all the points, but not in equal amounts. The images on the right show the amount of motion we assign to each control point based on its distance to {S'}. You may have noticed a problem with the algorithm description so far. If a nurbs surface is flat, the control-points lie on the surface itself, but when the surface starts to buckle and bend, the control points have a tendency to move away from the surface. This means that the distance between the control points {uvw} coordinate and {S'} is less meaningful. So instead of control points, we'll be using Greville points. Both nurbs curves and nurbs surfaces have a set of Greville points (or "edit points" as they are known in Rhino), but only curves expose this in the Rhino interface. As scripters we also get access to surface Greville points, which is useful because there is a 1:1 mapping between control and Greville points and the latter are guaranteed to lie on the surface. Greville points can therefore be expressed in {uv} coordinates only, which means we can also evaluate surface properties (such as tangency, normals and curvature) at these exact locations. The only thing left undecided at this point is the equation we're going to use to determine the amount of motion we're going to assign to a certain control point based on its distance from {S'}. It seems obvious that all the control points that are "close" should be affected much more than those which are farther away. The minimum distance between two points in space is zero (negative distance only makes sense in certain contexts, which we'll get to shortly) and the maximum distance is infinity. This means we need a graph that goes from zero to infinity on the x-axis and which yields a lower value for {y} for every higher value of {x}. If the graph ever goes below zero it means we're deforming the surface with a negative error. This is not a bad thing per se, but let's keep it simple for the time being. Our choices are already pretty limited by these constraints, but there are still some worthy contestants. If this were a primer about mathematics I'd probably have gone for a Gaussian distribution, but instead we'll use an extremely simple equation known as a hyperbola. If we define the diffusion factor of a Greville point as the inverse of its distance to {S'}, we get this hyperbolic function: $$f(y)=\frac{1}{x}$$ As you can see, the domain of the graph goes from zero to infinity, and for every higher value of {x} we get a lower value of {y}, without {y} ever becoming zero. There's just one problem, a problem which only manifests itself in programming. For very small values of {x}, when the Greville point is very close to {S'}, the resulting {y} is very big indeed. When this distance becomes zero the weight factor becomes infinite, but we'll never get even this far. Even though the processor in your computer is in theory capable of representing numbers as large as 1.8 × 10308 (which isn't anywhere near infinity by any stretch of the imagination), when you start doing calculations with numbers approaching the extremes chances are you are going to cross over into binary no man's land and crash your pc. And that's not even to mention the deplorable mathematical accuracy at these levels of scale. Clearly, you might want to steer clear of very big and very small numbers altogether. It's an easy fix in our case, we can simply limit the {x} value to the domain {+0.01; +∞}, meaning that {y} can never get bigger than 100. We could make this threshold much, much smaller without running into problems. Even if we limit {x} to a billionth of a unit (0.00000001) we're still comfortably in the clear. The first thing we need to do is write a function that takes a surface and a point in {xyz} coordinates and translates it into {uvw} coordinates. We can use the rs.SurfaceClosestPoint() method to get the {u} and {v} components, but the {w} is going to take some thinking. First of all, a surface is a 2D entity meaning it has no thickness and thus no "real" {z} or {w} component. But a surface does have normal vectors that point away from it and which can be used to emulate a "depth" dimension. In the adjacent illustration you can see a point in {uvw} coordinates, where the value of {w} is simply the distance between the point and the start of the line. It is in this respect that negative distance has meaning, because negative distance denotes a {w} coordinate on the other side of the surface. Although this is a useful way of describing coordinates in surface space, you should at all times remember that the {u} and {v} components are expressed in surface parameter space while the {w} component is expressed in world units. We are using mixed coordinate systems which means that we cannot blindly use distances or angles between these points because those properties are meaningless now. In order to find the coordinates in surface {S} space of a point {P}, we need to find the projection {P'} of {P} onto {S}. Then we need to find the distance between {P} and {P'} so we know the magnitude of the {w} component and then we need to figure out on which side of the surface {P} is in order to figure out the sign of {w} (positive or negative). Since our script will be capable of fitting a surface to multiple points, we might as well make our function list-capable: ```python def ConvertToUVW(idSrf, pXYZ): pUVW = [] for point in pXYZ: u, v = rs.SurfaceClosestPoint(idSrf, point) surface_xyz = rs.EvaluateSurface(idSrf, u, v) surface_normal = rs.SurfaceNormal(idSrf, (u,v)) dirPos = rs.PointAdd(surface_xyz, surface_normal) dirNeg = rs.PointSubtract(surface_xyz, surface_normal) dist = rs.Distance(surface_xyz, point) if (rs.Distance(point, dirPos) > rs.Distance(point, dirNeg)): dist *= -1 pUVW.append((u, v, dist)) return pUVW ```
Line Description
1 pXYZ() is an array of points expressed in world coordinates.
4...6 Find the {uv} coordinate of {P'}, the {xyz} coordinates of {P'} and the surface normal vector at {P'}.
8...10 Add and subtract the normal to the {xyz} coordinates of {P'} to get two points on either side of {P'}.
12...13 If {P} is closer to the dirNeg point, we know that {P} is on the "downside" of the surface and we need to make {w} negative.
We need some other utility functions as well (it will become clear how they fit into the grand scheme of things later) so let's get it over with quickly: ```python def GrevilleNormals(idSrf): uvGreville = rs.SurfaceEditPoints(idSrf, True, True) srfNormals = [rs.SurfaceNormal(idSrf, grev) for grev in uvGreville] return srfNormals ``` This function takes a surface and returns a list of normal vectors for every Greville point. There's nothing special going on here, you should be able to read this function without even consulting help files at this stage. The same goes for the next function, which takes a list of vectors and a list of numbers and divides each vector with the matching number. This function assumes that *Vectors* and *Factors* are lists of equal size. ```python def DivideVectorList(Vectors, Factors): for i in range(0,len(Vectors)): Vectors[i] = rs.VectorDivide(Vectors[i], Factors[i]) return Vectors ``` Our eventual algorithm will keep track of both motion vectors and weight factors for each control point on the surface (for reasons I haven't explained yet), and we need to instantiate these lists with default values. Even though that is pretty simple stuff, I decided to move the code into a separate procedure anyway in order to keep all the individual procedures small. The return value for this function are two lists: Forces and Factors. ```python def InstantiateForceLists(Bound): Forces = [] Factors = [] for i in range(Bound): Forces.append((0,0,0)) Factors.append(0) return Forces, Factors ```
Line Description
2...3 Create lists to hold both Forces and Factors.
5...7 Iterate through both lists and assign default values (a zero-length vector in the case of Forces and zero in the case of Factors)
9 Note that we are returning two separate items. The assignment in the line calling this function will contain both of these. Ways of handling this assignment will be handled later in the text.
We've now dealt with all the utility functions. I know it's a bit annoying to deal with code which has no obvious meaning yet, and at the risk of badgering you even more I'm going to take a step back and talk some more about the error diffusion algorithm we've come up with. For one, I'd like you to truly understand the logic behind it and I also need to deal with one last problem... If we were to truly move each control point based directly on the inverse of its distance to {P'}, the hyperbolic diffusion decay of the sample points would be very noticeable in the final surface. Let's take a look at a simple case, a planar surface {Base} which has to be fitted to four points {A; B; C; D}. Three of these points are above the surface (positive distance), one is below the surface (negative distance): On the left you see the four individual hyperbolas (one for each of the sample points) and on the right you see the result of a fitting operation which uses the hyperbola values directly to control control-point motion. Actually, the hyperbolas aren't drawn to scale, in reality they are much *(much)* thinner, but drawing them to scale would make them almost invisible since they would closely hug the horizontal and vertical lines. We see that the control points that are close to the projections of {A; B; C; D} on {Base} will be moved a great deal (such as {S}), whereas points in between (such as {T}) will hardly be moved at all. Sometimes this is useful behaviour, especially if we assume our original surface is already very close to the sample points. If this is not the case (like in the diagram above) then we end up with a flat surface with some very sharp tentacles poking out. Lets assume our input surface is not already 'almost' good. This means that our algorithm cannot depend on the initial shape of the surface which in turn means that moving control points small amounts is not an option. We need to move all control points as far as necessary. This sounds very difficult, but the mathematical trick is a simple one. I won't provide you with a proof of why it works, but what we need to do is divide the length of the motion vector by the value of the sum of all the hyperbolas. Have a close look at control points {S} and {T} in the illustration above. {S} has a very high diffusion factor (lots of yellow above it) whereas {T} has a low diffusion factor (thin slivers of all colors on both sides). But if we want to move both {S} and {T} substantial amounts, we need to somehow boost the length of the motion vector for {T}. If you divide the motion vector by the value of the added hyperbolas, you sort of 'unitize' all the motion vectors, resulting in the following section: which is a much smoother fit. The sag between {B} and {C} is not due to the shape of the original surface, but because between {B} and {C}, the other samples start to gain more relative influence and dragging the surface down towards them. Let's have a look at the code: ```python def FitSurface(idSrf, Samples, dTranslation, dProximity): P = rs.SurfacePoints(idSrf) G = rs.SurfaceEditPoints(idSrf, True, True) N = GrevilleNormals(idSrf) S = ConvertToUVW(idSrf, Samples) [Forces, Factors] = InstantiateForceLists(len(P)) dProximity = 0.0 dTranslation = 0.0 for i in range(len(S)): dProximity = dProximity + abs(S[i][2]) for j in range(len(P)): LocalDist = math.pow((S[i][0] - G[j][0]),2) + math.pow((S[i][1] - G[j][1]),2) if (LocalDist < 0.01): LocalDist = 0.01 LocalFactor = 1 / LocalDist LocalForce = rs.VectorScale(N[j], LocalFactor * S[i][2]) Forces[j] = rs.VectorAdd(Forces[j], LocalForce) Factors[j] = Factors[j] + LocalFactor Forces = DivideVectorList(Forces, Factors) for i in range(len(P)): P[i] = rs.PointAdd(P[i], Forces[i]) dTranslation = dTranslation + rs.VectorLength(Forces[i]) srf_N = rs.SurfacePointCount(idSrf) srf_K = rs.SurfaceKnots(idSrf) srf_W = rs.SurfaceWeights(idSrf) srf_D = [] srf_D.append(rs.SurfaceDegree(idSrf, 0)) srf_D.append(rs.SurfaceDegree(idSrf, 1)) FS = rs.AddNurbsSurface(srf_N, P, srf_K[0], srf_K[1], srf_D, srf_W) return (FS, Samples, dTranslation, dProximity) ```
Line Description
1 This is another example of a function which returns more than one value. When this function completes, dTranslation will contain a number that represents the total motion of all control points and dProximity will contain the total error (the sum of all distances between the surface and the samples). Since it is unlikely our algorithm will generate a perfect fit right away, we somehow need to keep track of how effective a certain iteration is. If it turns out that the function only moved the control points a tiny bit, we can abort in the knowledge we have achieved a high level of accuracy.
2...57 P, G, N and S are lists that contain the surface control points (in {xyz} space), Greville points (in {uv} space), normal vectors at every greville point and all the sample coordinates (in {uvw} space). The names chosen can be difficult to remember, but they are short.
6 The function we're calling here has been dealt with on page 104.
11 First, we iterate over all Sample points.
13 Then, we iterate over all Control points.
14 LocalDist is the distance in {uv} space between the projection of the current sample point and the current Greville point.
15 This is where we limit the distance to some non-zero value in order to prevent extremely small numbers from entering the algorithmic meat-grinder.
16 Run the LocalDist through the hyperbola equation in order to get the diffusion factor for the current Control point and the current sample point.
17 LocalForce is a vector which temporarily caches the motion caused by the current Sample point. This vector points in the same direction as the normal, but the magnitude (length) of the vector is the size of the error times the diffusion factor we've calculated on line 22.
18 Every Control point is affected by all Sample points, meaning that every Control point is tugged in a number of different directions. We need to combine all these forces so we end up with a final, resulting force. Because we're only interested in the final vector, we can simply add the vectors together as we calculate them.
19 We also need to keep a record of all the Diffusion factors along with all vectors, so we can divide them later and unitize the motion (as discussed on page 105).
20 Divide all vectors with all factors (function explained on page 104)
22...24 Apply the motion we've calculated to the {xyz} coordinates of the surface control points.
26...33 Instead of changing the existing surface, we're going to add a brand new one. In order to do this, we need to collect all the NURBS data of the original such as knot vectors, degrees, weights and so on and so forth.
The procedure on the previous page has no interface code, thus it is not a top-level procedure. We need something that asks the user for a surface, some points and then runs the FitSurface() function a number of times until the fitting is good enough: ```python def DistributedSurfaceFitter(): idSrf = rs.GetObject("Surface to fit", 8, True, True) if idSrf is None: return pts = rs.GetPointCoordinates("Points to fit to", False) if pts is None: return dTrans = 0 dProx = 0 for N in range(1, 1000): rs.EnableRedraw(False) nSrf, pts, dTrans, dProx = FitSurface(idSrf, pts, dTrans, dProx) rs.DeleteObject(idSrf) rs.EnableRedraw(True) rs.Prompt("Translation =" + str(round(dTrans, 2)) + "Deviation =" + str(round(dProx, 2))) if dTrans < 0.1 or dProx < 0.01: break idSrf = nSrf print("Final deviation = " + str(round(dProx, 4))) ```
Line Description
11 Rather than using an infinite loop (while) we limit the total amount of fitting iterations to one thousand. That should be more than enough, and if we still haven't found a good solution by then it is unlikely we ever will. The variable N is known as a "chicken int" in coding slang. "Int" is short for "Integer" and "chicken" is because you're scared the loop might go on forever.
12...15 Disable the viewport, create a new surface, delete the old one and switch the redraw back on
16 Inform the user about the efficiency of the current iteration
17 If the total translation is negligible, we might as well abort since nothing we can do will make it any better. If the total error is minimal, we have a good fit and we should abort.
The diagrams and graphs I've used so far to illustrate the workings of this algorithm are all two-dimensional and display only simplified cases. The images on this page show the progression of a single solution in 3D space. I've started with a planar, rectangular nurbs patch of 30 × 30 control points and 36 points both below and above the initial surface. I allowed the algorithm to continue refining until the total deviation was less than 0.01 units. ### 8.9.2 Surface Curvature Curve curvature is easy to grasp intuitively. You simply fit a circle to a short piece of curve as best you can (this is called an osculating circle) and the radius and center of this circle tell you all you need to know about the local curvature. We've dealt with this already before. Points {A; B; C; D; E} have a certain curvature associated with them. The radius of the respective circles is a measure for the curvature (in fact, the curvature is the inverse of the radius), and the direction of the vectors is an indication of the curve plane. If we were to scale the curve to 50% of its original size the curvature circles also become half as big, effectively doubling the curvature values. Point {C} is special in that it has zero-curvature (i.e. the radius of the osculating circle is infinite). Points where the curvature value changes from negative to positive are known as inflection points. If we have multiple inflection points adjacent to each other, we are dealing with a linear segment in the curve. Surface curvature is not so straightforward. For one, there are multiple definitions of curvature in the case of surfaces and volumes which one suits us best depends on our particular algorithm. Curvature is quite an important concept in many manufacturing and design projects, which is why I'll deal with it in some depth. I won't be dealing with any script code until the next section, so if you are already familiar with curvature theory feel free to skip ahead to page 111. The most obvious way of evaluating surface curvature would be to slice it with a straight section through the point {P} we are interested in and then simply revert to curve curvature algorithms. But, as mentioned before, surfaces lack direction and it is thus not at all clear at which angle we should dissect the surface (we could use {u} and {v} directions, but those will not necessarily give you meaningful answers). Still, this approach is useful every now and again and it goes under the name of normal curvature. As you can see in the illustration below, there are an infinite number of sections through point {P} and thus an infinite number of answers to the question "what is the normal curvature at {P}?" However, under typical circumstances there is only one answer to the question: "what is the highest normal curvature at {P}?". When you look at the complete set of all possible normal curvatures, you'll find that the surface is mostly flat in one direction and mostly bent in another. These two directions are therefore special and they constitute the principal curvatures of a surface. The two principal curvature directions are always perpendicular to each other and thus they are completely independent of {u} and {v} directions ({u} and {v} are not necessarily perpendicular). Actually, things are more complicated since there might be multiple directions which yield lowest or highest normal curvature so there's a bit of additional magic required to get a result at all in some cases. Spheres for example have the same curvature in all directions so we cannot define principal curvature directions at all. This is not the end of the story. Starting with the set of all normal curvatures, we extracted definitions of the principal curvatures. Principal curvatures always come in pairs (minimum and maximum) and they are both values and directions. We are more often than not only interested in how much a surface bends, not particularly in which direction. One of the reasons for this is that the progression of principal curvature directions across the surface is not very smooth: The illustration on the left shows the directions of the maximum principal curvatures. As you can see there are threshold lines on the surface at which the principal direction suddenly makes 90º turns. The overall picture is chaotic and overly complex. We can use a standard tensor-smoothing algorithm to average each direction with its neighbours, resulting in the image on the right, which provides us with an already much more useful distribution (e.g. for texturing or patterning purposes), but now the vectors have lost their meaning. This is why the principal curvature directions are not a very useful surface property in every day life. Instead of dealing with the directions, the other aforementioned surface curvature definitions deal only with the scalar values of the curvature; the osculating circle radius. The most famous among surface curvature definitions are the Gaussian and Mean curvatures. Both of these are available in the _CurvatureAnalysis command and through RhinoScriptSyntax. The great German mathematician Carl Friedrich Gauss figured out that by multiplying the principal curvature radii you get another, for some purposes much more useful indicator of curvature: $$K_{Gauss}=K_{min} \cdot K_{max}$$ Where JGauss is the Gaussian curvature and lmin and lmax are the principal curvatures. Assuming you are completely comfortable with the behaviour of multiplications, we can identify a number of specific cases: From this we can conclude that any surface which has zero Gaussian curvature everywhere can be unrolled into a flat sheet and any surface with negative Gaussian curvature everywhere can be made by stretching elastic cloth. The other important curvature definition is Mean curvature ("average"), which is essentially the sum of the principal curvatures: $$K_{Mean}=\frac{K_{min} + K_{max}}{2}$$ As you know, summation behaves very different from multiplication, and Mean curvature can be used to analyze different properties of a surface because it has different special cases. If the minimum and maximum principal curvatures are equal in amplitude but have opposing signs, the average of both is zero. A surface with zero Mean curvature is not merely anticlastic, it is a very special surface known as a *minimal* or *zero-energy* surface. It is the natural shape of a soap film with equal atmospheric pressure on both sides. These surfaces are extremely important in the field of tensile architecture since they spread stress equally across the surface resulting in structurally strong geometry. ### 8.9.3 Vector and Tensor Spaces On the previous page I mentioned the words "tensor", "smoothing" and "algorithm" in one breath. Even though you most likely know the latter two, the combination probably makes little sense. Tensor smoothing is a useful tool to have in your repertoire so I'll deal with this specific case in detail. Just remember that most of the script which is to follow is generic and can be easily adjusted for different classes of tensors. But first some background information... Imagine a surface with no singularities and no stacked control points, such as a torus or a plane. Every point on this surface has a normal vector associated with it. The collection of all these vectors is an example of a vector space. A vector space is a continuous set of vectors over some interval. The set of all surface normals is a two-dimensional vector space (sometimes referred to as a vector field), just as the set of all curve tangents is a one-dimensional vector space, the set of all air-pressure components in a turbulent volume over time is a four-dimensional vector space and so on and so forth. When we say "vector", we usually mean little arrows; a list of numbers that indicate a direction and a magnitude in some sort of spatial context. When things get more complicated, we start using "tensor" instead. Tensor is a more general term which has fewer connotations and is thus preferred in many scientific texts. For example, the surface of your body is a two-dimensional tensor space (embedded in four dimensional space-time) which has many properties that vary smoothly from place to place; hairiness, pigmentation, wetness, sensitivity, freckliness and smelliness to name just a few. If we measure all of these properties in a number of places, we can make educated guesses about all the other spots on your body using interpolation and extrapolation algorithms. We could even make a graphical representation of such a tensor space by using some arbitrary set of symbols. We could visualize the wetness of any piece of skin by linking it to the amount of blue in the colour of a box, and we could link freckliness to green, or to the width of the box, or to the rotational angle. All of these properties together make up the tensor class. Since we can pick and choose whatever we include and ignore, a tensor is essentially whatever you want it to be. Let's have a more detailed look at the tensor class mentioned on the previous page, which is a rather simple one... I created a vector field of maximum-principal curvature directions over the surface (sampled at a certain custom resolution), and then I smoothed them out in order to get rid of the sudden jumps in direction. Averaging two vectors is easy, but averaging them while keeping the result tangent to a surface is a bit harder. In this particular case we end up with a two-dimensional tensor space, where the tensor class T consist of a vector and a tangent plane: Since we're sampling the surface at regular parameter intervals in {u} and {v} directions, we end up with a matrix of tensors (a table of rows and columns). We can represent this easily with a two-dimensional list. We'll need two of these in our script since we need to store two separate data-entities; vectors and planes. This progression of smoothing iterations clearly demonstrates the usefulness of a tensor-smoothing algorithm; it helps you to get rid of singularities and creases in any continuous tensor space. I'm not going to spell the entire script out here, I'll only highlight the key functions. You can find the complete script (including comments) in the Script folder. ```python def SurfaceTensorField(Nu, Nv): idSrf = rs.GetSurfaceObject()[0] uDomain = rs.SurfaceDomain(idSrf, 0) vDomain = rs.SurfaceDomain(idSrf, 1) T = [] K = [] for i in range(Nu): T.append([]) K.append([]) u = uDomain[0] + (i/Nu)*(uDomain[1] - uDomain[0]) for j in range(Nv): v = vDomain[0] + (j/Nv)*(vDomain[1] - vDomain[0]) T[i].append(rs.SurfaceFrame(idSrf,(u,v))) localCurvature = rs.SurfaceCurvature(idSrf,(u,v)) if localCurvature is None: K[i].append(T[i][j][1]) else: K[i].append(rs.SurfaceCurvature(idSrf,(u,v))[3]) return SmoothTensorField(T,K) ```
Line Description
1 This procedure has to create all the lists that define our tensor class. In this case one list with vectors and a list with planes.
9...10 At the beginning of each iteration down the range Nu, we nest a new list in both T and K, which will hold all values of iterations of the range Nv.
11 This looks imposing, but it is a very standard piece of logic. The problem here is a common one: how to remap a number from one scale to another. We know how many samples the user wants (some whole number) and we know the limits of the surface domain (two doubles of some arbitrary value). We need to figure out which parameter on the surface domain matches with the Nth sample number. Observe the diagram below for a schematic representation of the problem: Our sample count (the topmost bar) goes from {A} to {B}, and the surface domain includes all values between {C} and {D}. We need to figure out how to map numbers in the range {A~B} to the range {C~D}. In our case we need a linear mapping function meaning that the value halfway between {A} and {B} gets remapped to another value halfway between {C} and {D}. Line 11 (and line 13) contain an implementation of such a mapping algorithm. I'm not going to spell out exactly how it works, if you want to fully understand this script you'll have to look into that by yourself.
14 Retrieve the surface Frame at {u,v}. This is part of our Tensor class.
15 Retrieve all surface curvature information at {u,v}. This includes principal, mean and Gaussian curvature values and vectors.
17 In case the surface has no curvature at {u,v}, use the x-axis vector of the Frame instead.
19 If the surface has a valid curvature at {u,v}, we can use the principal curvature direction which is stored in the 4th element of the curvature data array.
This function takes two lists and it modifies the originals. The return value (the two lists) is merely cosmetic. This function is a typical box-blur algorithm. It averages the values in every tensor with all neighboring tensors using a 3×3 blur matrix. ```python def SmoothTensorField(T, K): SmoothTensorField = False Ub1 = len(T[1]) Ub2 = len(T[2]) for i in range(Ub1): for j in range(Ub2): k_tot = (0,0,0) for x in range(i-1,i+1): xm = (x+Ub1) % Ub1 for y in range(j-1, j+1): ym = (y+Ub2) % Ub2 k_tot = rs.VectorAdd(k_tot, K[xm][ym]) k_dir = rs.PlaneClosestPoint(T[i][j], rs.VectorAdd(T[i][j][0], k_tot)) k_tot = rs.VectorSubtract(k_dir, T[i][j][0]) k_tot = rs.VectorUnitize(k_tot) K[i].append(k_tot) rs.AddLine(T[i][j][0],T[i][j][0]+K[i][j]) return T, K ```
Line Description
5...6 Since our tensor-space is two-dimensional, we need 2 nested loops to iterate over the entire set.
8...11
Now that we're dealing with each tensor individually (the first two loops) we need to deal with each tensors neighbours as well (the second set of nested loops). We can visualize the problem at hand with a simple table graph. The green area is a corner of the entire two-dimensional tensor space, the dark green lines delineating each individual tensor. The dark grey square is the tensor we're currently working on. It is located at {u,v}. The eight white squares around it are the adjacent tensors which will be used to blur the tensor at {u,v}. We need to make 2 more nested loops which iterate over the 9 coordinates in this 3×3 matrix. We also need to make sure that all these 9 coordinates are in fact on the 2D tensor space and not teetering over the edge. We can use the Mod operator to make sure a number is "remapped" to belong to a certain numeric domain.
12 Once we have the mx and my coordinates of the tensor, we can add it to the k_tot summation vector.
13...16 Make sure the vector is projected back onto the tangent plane and unitized.
## Next Steps Congratulations, you have made it through the [Rhino.Python 101 Primer](/guides/rhinopython/primer-101). -------------------------------------------------------------------------------- # Chapter 1: Algorithms and Data Source: https://developer.rhino3d.com/en/guides/grasshopper/gh-algorithms-and-data-structures/algorithms-data/ ## 1.1 Algorithmic design Algorithmic design We can define algorithmic design as a design method where the **output** is achieved through **well-defined steps**. In that sense, many human activities are algorithmic. Take, for example, baking a cake. You get the **cake** (output) by using a **recipe** (well-defined steps). Any change in the ingredients (input) or the baking process results in a different cake. We will analyze the parts of typical algorithms, and identify a strategy to build algorithmic solutions from scratch. Regardless of its complexity, all algorithmic solutions have 3 building blocks: **input, key process,** and **output**. Note that the key process may require additional input and processes.
Figure(1): The building blocks of algorithmic solutions
Throughout this text, we will organize and label the solutions to identify the three blocks clearly. We will also use consistent color coding to visually distinguish between the parts. This will help us become more comfortable with reading algorithms and quickly identify input, key processing steps, and properly collect and display output. Visual cues are important to develop fluency in algorithmic thinking. In general, reading existing algorithmic solutions is relatively easy, but building new ones from scratch is much harder and requires a new set of skills. While it is useful to know how to read and modify existing solutions, it is essential to develop algorithmic design skills to build new solutions from scratch. ## 1.2 Algorithms parts In Grasshopper, a solution flows from left to right. At the far left are input values and parameters, and the far right has the output. In between are one or more key processes, and sometimes additional input and output. Let’s take a simple example to help identify the three parts of any algorithm (input, key process, output). The simple addition algorithm includes two numbers (input), the sum (output) and one key process that takes the numbers and gives the result. We will use purple for the input, maroon for the key processes and light blue for the output. We will also group and label the different parts and adhere to organizing the Grasshopper solutions from left to right. **Example 1-2-1:** Algorithm to add 2 numbers
Algorithms may involve intermediate processes. For example, suppose we need to create a circle (output) using a center and a radius (input). Notice that the input is not sufficient because we do not know the plane on which the circle should be created. In this case, we need to generate additional information, namely the plane of the circle. We will call this an intermediate process and use brown color to label it. **Example 1-2-2:** Algorithm to create a circle on the XY-Plane from a center and a radius
Some solutions are not written with styles and hence are hard to read and build on. It is very important that you take the time to organize and label your solutions to make them easier to understand, debug and use by others.
Tutorial 1-2-3: Read existing algorithm
Given the following definition, write a description of what the algorithm does, identify input, the main process(s) and output, then label and color-code all the parts. Re-write the solution to make it more readable.
Solution... In order to figure out what the algorithm is meant to do, we need to group the input on the left side, and collect the output on the right side, then organize the processes in the order of execution. We then step through the solution from left to right to deduce what it does. We can examine and preview the output in each step. The example of the tutorial is meant to create a circle that is twice as large as another circle that goes through three given points. One of the points is constructed out of its 3 coordinates.
## 1.3 Designing algorithms: the 4-step process Modeling in Rhino vs Grasshopper The 4-step process to designing algorithms Before we generalize a method to design algorithms, let’s examine an algorithm we commonly use in real life such as baking a cake. If you already have a recipe for a cake, you simply get the recommended ingredients, mix them, pour in a pan, put in a preheated oven for a certain amount of time, then serve. If the recipe is well documented, then it is relatively straightforward to use. As you become more proficient in baking cakes, you may start to modify the recipe. Perhaps add new ingredients (chocolate or nuts) or use different tools (cupcake container).
Figure(2): Steps to follow existing recipe
When designers write algorithms, they typically try to search for existing solutions and modify them to fit their purposes. While this is a good entry point, using existing solutions can be frustrating and time-consuming. Also, existing solutions have their own flavor and that may influence design decisions and limit creativity. If designers have unique problems, and they often do, they have no choice but to create new solutions from scratch; albeit a much harder endeavor. Back to our example, the task of baking a cake is much harder if you don’t have a recipe to follow and have not baked one before. You will have to guess the ingredients and the process. You will likely end up with bad results in the first few attempts, until you figure it out! In general, when you create a new recipe, you have to follow the process in reverse. You start with an image of the desired cake, you then guess the ingredients, tools and steps. Your thinking goes along the following lines: - The cake needs to be baked, so I need an oven and time, - What goes in the oven is a cake batter held by a container, - The batter is a mix of ingredients
Figure(3): Steps to invent a new recipe from scratch
We can use a similar methodology to design parametric algorithms from scratch. Keep in mind that creating new algorithms is a “skill” and it requires patience, practice and time to develop. **Algorithmic thinking in 3D modeling vs parametric design** 3D modeling involves a certain level of algorithmic thinking, but it has many implicit steps and data. For example designing a mass model using a 3D modeler may involve the following steps: 1. Think about the output (e.g. a mass out of few intersecting boxes) 2. Identify a command or series of commands to achieve the output ( e.g. run Box command a few times, Move, Scale or Rotate one or more boxes, then BooleanUnion the geometry). At that point, you are done! Data such as the base point for your initial box, width, height, scale factor, move direction, rotation angle, etc. are requested by the commands, and the designer does not need to prepare ahead of time. Also, the final output (the boolean mass) becomes directly available and visible as an object in your document.
Figure(4): Interactive 3D modeling to create and manipulate geometry uses visual widgets and guides
Algorithmic solutions are not interactive and require explicit articulation of data and processes. In the box example, you need to define the box orientation and dimensions. When copy, you need a vector and when rotate you need to define the plane and angle of rotation.
Figure(5): Algorithmic solutions involve explicit definition of geometry, vectors and transformations
**Designing algorithms** Designing algorithms requires knowledge in geometry, mathematics and programming. Knowledge in geometry and mathematics is covered in the [Essential Mathematics for Computational Design](https://developer.rhino3d.com/guides/general/essential-mathematics/). As for programming skills, it takes time and practice to build the ability to formulate design intentions into logical steps to process and manage geometric data. To help get started, it is useful to think of any **algorithm as a 4-step process** as in the following:
1. Output Clearly identify the desired outcome
2. Key processes Identify key steps to reach the outcome
3. Input Examine initial data and parameters
4. Intermediate steps Define intermediate parameters and processes to generate additional data
Thinking in terms of these 4 steps is key to developing the skill of algorithmic design. We will start with simple examples to illustrate the methodology, and gradually apply more complex examples. **Example 1-3-1: Add two numbers** Use the 4-Step process to write an algorithm to add two numbers
1. Output: The sum of the 2 numbers Use the Panel component to collect the sum
2. Key processes: Addition Use the Addition component that takes 2 numbers and gives the sum
3. Input: 2 numbers Use the Panel component to hold and view the values of input numbers
**Example 1-3-2: Create a circle** Use the 4-Step process to create a circle from a given center and radius
1. Output: A Circle Use the Circle parameter to collect the output
2. Key processes: Identify a key process that generates a circle from a radius Use the Circle component in Grasshopper
3. Input: Use the given input (center and radius). Feed the radius to the Circle component
4. Intermediate process: The circle needs a center, and also the plane on which the circle is located. Let's assume the circle is on a plane parallel to the XY-Plane and use the circle center as the origin of the plane
**Example 1-3-3: Create a line** Use the 4-Step process to create an algorithm to generate a line from 2 points. One point is referenced from Rhino, and the other is created using three coordinates (x=1, y=0.5 and z=3)
1. Output: The line geometry. Use the Geometry parameter to collect the output
2. Key processes: Identify a key process that generates a line from 2 points. Use the Line component in Grasshopper
3. Input: Use the given input (a referenced point and 3 coordinates). Feed one point to one of the ends of the line
4. Intermediate process: Before we can use the coordinates as a point, we need to construct a point
In more complex algorithms, we will need to analyze the problems, investigate possible solutions and break them down to pieces whenever possible to make it more manageable and readable. We will continue to use the 4-step process and other techniques to solve more complex algorithms throughout the book. ## 1.4 Data Data is information stored in a computer and processed by a program. Data can be collected from different sources, it has many types and is stored in well defined structures so that it can be used efficiently. While there are commonalities when it comes to data across all scripting languages, there are also some differences. This book explores data and data structures specific to Grasshopper. ## 1.5 Data sources Data and data sources In Grasshopper, there are three main ways to supply data to processes (or what is called components): internal, referenced and external.
Data sources in Grasshopper
1. Internally set data Data can be set inside any instance of a parameter. Once set, it remains constant, unless manually changed or overridden by external input. This is a good way when you do not
2. Referenced data Data can be referenced from Rhino or some external document. For example, you can reference a point created in a Rhino document. When you move the point in Rhino, its reference in Grasshopper updates as well. Grasshopper files are saved separately from Rhino files, and hence if the GH file has referenced data, the Rhino file needs to be saved and passed along with the GH file to avoid any loss of data
generally need to change the data after it is set (constant). Data is stored inside the GH file
3. Externally supplied data Data can be supplied from previous processes. This method is best suited for dynamic data or data controlled parametrically. Externally supplied data to a parameter takes precedent over the internal or referenced values (when both exist)
## 1.6 Data types Data types Data casting All programming languages identify the kind of data used in terms of the values that can be assigned to and the operations and processes it can participate in. There are common data types such as Integer, Number, Text, Boolean (Boolean type can be set to True or False), and others. Grasshopper lists those under the Params > Primitives tab.
Figure(6): Examples of primitive data types common to all programming languages
Grasshopper supports geometry types that are useful in the context of 3D modeling such as Point (3 numbers for coordinates), Line (2 points), NURBS Curve, NURBS Surface, Brep, and others. All geometry types are included under the Params> Geometry tab in GH.
Figure(7): Examples of geometry data types
There are other mathematics types that designers do not usually use in 3D modeling, but are very common in parametric design such as Domains, Vectors, Planes, and Transformation Matrices. GH provides a rich set of tools to help create, analyze and use these types. To fully understand the mathematical as well as geometry types such as NURBS curves and surfaces, you can refer to the [Essential Mathematics for Computational Design](https://developer.rhino3d.com/guides/general/essential-mathematics/) book by the author
Figure(8): Examples of data types common in computer graphics
The parameters in GH can be used to convert data from one type to another (cast). For example if you need to turn a text into a number, you can feed your text into a Number parameter. If the text cannot be converted, you’ll get an error.
Figure(9): Data conversion (casting) inside parameters in Grasshopper
Grasshopper components internally convert input to suitable types when possible. For example, if you feed a “text” to Addition component, GH tries to read the text as a number. If a component can process more than one type, it uses the input type without conversion. For example, equality in an expression can compare text as well as numbers. In such case, make sure you use the intended type to avoid confusion.
Figure(10): Some operations can be performed on multiple types. Cast to the intended type especially if the component is capable of processing multiple types (such as Expression in GH)
It is worth noting that sometimes GH components simply ignore invalid input (null or wrong type). In such cases, you are likely to end up with an unexpected result and it will be hard to find the bug. It is very important to verify the output from each component before using it.
Figure(11): Invalid input is ignored and a default value is used. For example a number inside a Panel component can be interpreted as a text and hence become invalid input to an Addition component
## 1.7 Processing Data Algorithmic designs use many data operations and processes. In the context of this book, we will focus on five categories: numeric and logical operations, analysis, sorting and selection. ### 1.7.1 Numeric operations Numeric operations in Grasshopper Numeric operations include operations such as arithmetic, trigonometry, polynomials and complex numbers. GH has a rich set of numeric operations, and they are mostly found under the Math tab. There are two main ways to perform operations in GH. First by using designated components for specific operations such as Addition, Subtraction and Multiplication.
Figure(12): Examples of numeric operations components in GH
Second, use an Expression component where you can combine multiple operations and perform a rich set of math and trigonometry operations, all in one expression.
Figure(13): Expression component in GH can be used to perform multiple operations
The Expression component is more robust and readable when you have multiple operations.
Figure(14): When a chain of operations is involved, using the Expression component is easier to maintain
Input to Expressions can be treated as text depending on the context.
Figure(15): Expression can process and format text
It is worth mentioning that most numeric input to components allow writing an expression to modify the inputs inline. For example, the Range component has N (number of steps) input. If you right mouse click on “N”, you can set an expression. You always use “x” to represent the supplied input regardless of the name.
Figure(16): Expression can be set inside the input parameter. Variable “x” refers to the supplied input value
### 1.7.2 Logical operations Logical operations in Grasshopper Main logical operations in GH include equalities, sets and logic gates.
Figure(17): Grasshopper has multiple components to perform Logical operations
Logical operations are used to create conditional flow of data. For example, if you like to draw a sphere only when the radius is between two values, then you need to create a logic that blocks the radius when it is not within your limits.
Figure(18): Data flow control using logical operations
### 1.7.3 Data analysis Data analysis in Grasshopper There are many tools in GH to examine and preview data. Panel is used to show the full details of the data and its structure, while the Parameter Viewer shows the data structure only. Other analysis components include Quick Graph that plots data in a graph, and Bounds to find the limits in a given set of numbers (the min and max values in the set).
Figure(19): Some of the ways to analyze data in Grasshopper
### 1.7.4 Data Sorting Data sorting in Grasshopper GH has designated components to sort numeric and geometry data. The Sort List component can sort a list of numeric keys. It can sort a list of numbers in ascending order or reverse the order. You can also use the Sort List component to sort geometry by some numeric keys, for example sort curves by length. GH has components designated to sort geometry sets such as Sort Points to sort points by their coordinates.
Figure(20): Sorting numbers in Grasshopper
### 1.7.5 Data Selection Data selection in Grasshopper 3D modeling allows picking specific or a group of objects interactively, but this is not possible in algorithmic design. Data is selected in GH based on the location within the data structure, or by a selection pattern. For example List Item component allows selecting elements based on their indices.
Figure(21): Select items from a list in Grasshopper
The Cull Pattern component allows using some repeated patterns to select a subset of the data.
Figure(22): An example to select every other item in a list
As you can see from the examples, selecting specific items or using cull components yield a subset of the data, and the rest is thrown away. Many times you only need to isolate a subset to operate on, then recombine back with the original set. This is possible in GH, but involves more advanced operations. We will get into the details of these operations when we talk about advanced data structures in chapter 3. ### 1.7.6 Mapping Data mapping in Grasshopper That refers to the linear mapping of a range of numbers where each number in a set is mapped to exactly one value in the new set. GH has a component to perform linear mapping called ReMap. You can use it to scale a set of numbers from its original range to a new one. This is useful to scale your range to a domain that suits your algorithm’s needs and limitations.
Figure(23): An example of linear remapping of numbers in Grasshopper
Converting data involves mapping. For example, you may need to convert an angle unit from degrees to radians ( GH components accept angles in radians only).
Figure(24): Convert angles from degrees to radians
As you know, parametric curves have “domains” (the range of parameters that evaluate to points on the curve). For example, if the domain of a given curve is between 12.5 to 51.3, evaluating the curve at 12.5 gives the point at the start of the curve. Many times you need to evaluate multiple curves using consistent parameters. Reparameterizing the domain of curves to some unified range helps solve this problem. One common domain to use is “0 To 1”. At the input of each curve in any GH component, there is the option to Reparameterize which resets the domain of the curve to be “0 to 1”.
Figure(25): Normalize the domain of curves (set to 0-1). Use Reparameterize input flag in Grasshopper
Tutorial 1-7-A: Flow control
What is the purpose of the following algorithm? Notate and color code to describe the purpose of each part.
Solution... Analyze the algorithm The algorithm has an output that is a sphere, a radius input and some conditional logic to process the radius.
Notate and color-code the solution From testing the output and following the steps of the solution it becomes apparent that the intention is to make sure that the radius of the sphere cannot be less than 1 unit. Test with radius greater than 1 (3.4 in the example)
Test with radius less than 1 (-2.8 in the example)
Tutorial 1-7-B: Data processing
Given a list of numbers of some point coordinates, do the following: 1. Analyze the list to understand the data. 2. Write an algorithm to convert the list of Numbers to a list of Points. 3. Change the domain of coordinate values to be between 3 and 9. Note that the input number list is organized so that the first 3 numbers refer to the x,y,z of the first point, the second 3 numbers belong to the second point and so on.
Solution... Analyze the algorithm
There are 2 inputs: a list of 51 numbers (3 coordinates for each point) which means that the list has 17 points. Using a QuickGraph, we can see that the numbers are between 2.60 and 15.89. We can also see that the values are distributed randomly. The other input is the target domain to be from 3 to 9.
Use the 4-step process to solve the algorithms
Output: List of points Use the Parameter Viewer to view the resulting data structure. To start, it will be empty.
Key Process #1: Remap Coordinates Map the coordinates list from its current domain (2.60 to 15.89) to a new domain (3.0 to 9.0) Use ReMap component to achieve that
Intermediate processes #1 The input domain is missing and can be extracted using Bounds component
Key Process #2: Construct Points Construct points from coordinates Use Construct Point (Pt) component
Intermediate processes #2 Extract all X coordinates as one list, Y in another and Z in the third. Use Cull Pattern component with appropriate pattern to extract each coordinate as a separate list. The input to Cull is the remapped points from process #1
Putting it all together
## 1.8 Pitfalls of algorithmic design Pitfalls of algorithmic design Writing elegant algorithms that are efficient and easy to read and debug is hard. We explained in this chapter how to write algorithms with style using color-coding and labeling. We also articulated a 4-step process to help develop algorithms. Following these guides help minimize bugs and improve the readability of the scripts. We will list a few of the common issues that lead to incorrect or unintended result. ### 1.8.1 Invalid or wrong input type If the input is of the wrong type or is invalid, GH changes the color of components to red or orange to indicate an error warning, with feedback about what the issue might be. This is helpful, but sometimes faulty input goes unnoticed if the components assign a default value, or calculate an alternative value to replace the input, that is not what was intended. It is a good practice to always double check the input (hook to a panel or parameter viewer and label the input). To avoid using wrong types, it is advisable to convert to the intended type to ensure accuracy.
Figure(26): Error resulting from wrong input type
### 1.8.2 Unintended input Input is prone to unintended change via intermediate processes or when multiple users have writing access to the script. It is very useful to preview and verify all key input and output. The Panel component is very versatile and can help check all types of values. Also you can set up guarding logic against out of range values or to trap undesired values.
Figure(27): Error resulting from unintended input. Cannot assume curve domain is 0-1 and use 0.5 to evaluate the midpoint
Figure(28): Example of a robust solution to evaluate the midpoint of a curve
### 1.8.3 Incorrect order of operation You should try to organize your solutions horizontally or vertically to clearly see the sequence of operations. You should also check the output from each step to make sure it is as expected before continuing on your code. There are also some techniques that help consolidate the script, for example use Expression when multiple numeric and math operations are involved. The following highlights some unfavorable organization.
Figure(29): Easy to confuse input to operations with poor organization
The following shows how to rewrite the same code to make it less error prone.
Figure(30): Best practices to align input with processes, or use Expressions
### 1.8.4 Mismatched data structures The issue of mismatched data structures as input to the same process or component is particularly tricky to guard against in GH, and has the potential to spiral the solution out of memory. It is essential to test the data structure of all input (except trivial ones) before feeding into any component. It is also important to examine desired matching under different scenarios (data matching will be explained at length later).
Figure(31): Mismatched data structures of input can cause errors in the output
### 1.8.5 Long processing time Some algorithms are time consuming, and you simply have to wait for it to process, but there are ways to minimize the wait when it is unnecessary. For example, at the early cycles of development, you should try to use a smaller set of data to test your solution with before committing the time to process the full set of data. It is also a good practice to break the solution into stages when possible, so you can isolate and disable the time consuming parts. Also, it is often possible to rewrite your solution to be more optimized and consume less time. Use the GH Profiler to test processing time. When a solution takes far too long to process or crashes, you should do the following: before you reopen the solution, disable it, and disconnect the input that caused the crash.
Figure(32): Grasshopper Profiler widget helps observe processing time
### 1.8.6 Poor organization Poorly organized definitions are not easy to debug, understand, reuse or modify. We can’t stress enough the importance of writing your definitions with styles, even if it costs extra time to start with. You should always color code, label everything, give meaningful names to variables, break repeated operations into modules and preview your input and output.
Figure(33): Poor organization in visual programming make the code hard to read and debug
## 1.9 Tutorials: algorithms and data
Tutorial 1.9.1: Unioned circles
Use the 4-step process to design an algorithm that combines 2 circles, given the following: Both circles are located on the XY-Plane. The first circle (Cir1) has a center (C1) = (2,2,2) and radius (R1) that is equal to a random number between 3 and 6. The second circle (Cir2) has a center (C2) that is shifted to the right of the first circle (Cir1) by an amount equal to the radius of the first circle (R1) along the positive X-Axis. The second circle radius (R2) is 20% bigger, or in other words (R2) = (R1) * 1.2.
Solution... download GH file... Analyze the question and the flow of the solution
There are 2 inputs: the coordinates of the center of the first circle (2,2,2) and the XY-Plane where both circles are located. Also, we know that the second circle is shifted the positive X-Axis direction, The following diagram shows an overview of the solution:
Solution steps
Output: Curve for the region union
Key Process: Union of 2 circles Use the Region Union component that takes curves and a plane
Input: needed to calculate the region union Identify the input needed and use given input when relevant. The plane for region union has been given. The 2 circles need their own plane and radius. The center of the plane is the center of the circle.
Intermediate processes #1: generate the center and plane of the 1st circle Construct a center from the given coordinates. Create a plane using Plane Origin component and use the constructed center and XY-Plane. The radius is from a random number between 3 and 6. Use Random component to create the radius
Intermediate processes #2 Generate the center and plane of the 2nd circle Calculate the 2nd circle plane by moving the first circle plane along the x-axis by an amount = first radius Calculate the 2nd circle radius by multiplying the first radius by 1.2
Putting it all together
Tutorial 1.9.2: Sphere with bounds
Use the 4-step process to draw a sphere with a radius between 2 and 6. If input is less than 2, then set the radius to 2, and if input radius is greater than 6, set the radius to 6. Use a number slider to input the radius and set between 0 and 10 to test. Make sure your solution is well organized, color-coded and labeled properly.
Solution... download GH file... The 4-step process to solve the algorithm
Output: The sphere as geometry
Key Process: Create a sphere Use the Sphere component to create a sphere from a base plane and radius
Input: 1. The radius parameter (0 - 10) 2. The bounds of the radius are 2 & 6
Intermediate processes #1: Construct a selection logic of radii and pattern. The radii is a list of the values from the slider, min and max. The list of pattern is generated to select the correct radius value
Intermediate processes #2 Generate the center and plane of the 2nd circle The selection logic ensures that the radius value falls within the intended range. If the radius input is less than the minimum value of the bounds, then the radius is set to the min value, and if it is greater than the maximum, then the max value is used instead.
Tutorial 1.9.3: Data operations
Given the numbers embedded in the Number parameter do the following: 1. Analyze input in terms of bounds and distribution 2. View the data and how it is structured 3. Extract even numbers 4. Sort numbers descending 5. Remap sorted numbers to (100 to 200)
Solution... download GH file...
1- Analyze the input bounds and distribution Use the QuickGraph to show that the set of numbers are between 3 and 98 and are distributed randomly.
2- Analyze the input data structure and values Use the Panel and Parameter Viewer to show that there are 16 elements organized in a list
3- Extract Even numbers Create the logic to test if a number is even (divisible by 2 without a remainder) and use Dispatch to extract even numbers
4- Sort numbers descending The Sort List component sorts numbers in ascending order. Use Reverse List component to further process the list to order descending
5- Remap to 100-200 Check the input range and use Remap component to scale data to be between 100-200
Tutorial 1.9.4: Algorithmic Pitfalls
Analyze what the following algorithm is intended to do, identify the errors that are preventing it from working as intended, then rewrite to fix the errors. Organize to reflect the algorithm flow, label and color-code.
Solution... download GH file...
The first step is to mark the errors:
Next, fix the errors and rewrite the solution with labels and proper color codes:
## Next Steps Those are the algorithms and data. Next, learn [Introduction to Data Structures](/guides/grasshopper/gh-algorithms-and-data-structures/data-structures/). This is part 1-3 of the [Essential Algorithms and Data Structures for Grasshopper](/guides/grasshopper/gh-algorithms-and-data-structures/). -------------------------------------------------------------------------------- # Chapter 1: Grasshopper C# Component Source: https://developer.rhino3d.com/en/guides/grasshopper/csharp-essentials/1-grasshopper-csharp-component/ ## 1.1 Introduction Grasshopper supports multiple scripting languages such as **C#**, **Python**, and **VB.NET** to help develop custom components using the Rhino and Grasshopper SDKs (software development kit). Rhino publishes a cross-platform **SDK** for **.NET** languages called **RhinoCommon**. The documentation of the SDK and other developer resources are available at [Rhino Developer Documentation](http://developer.rhino3d.com/). ## 1.2 C# Component Interface The scripting components are integrated within Grasshopper and have a similar interface to that of other typical components. They can read input & produce output, and they have an editor to write custom code with access to **RhinoCommon**. They are used to create specialized code and workflows not supported by other Grasshopper components. You can also use them to simplify, optimize, and streamline your definitions by combining multiple functions. To add an instance of the C# script component to the canvas, drag & drop the component from the **Script** panel under the **Maths** tab. The default script component has two inputs and two outputs. The user can change the names of input and output, set the input data type and data structure, and also add more inputs and outputs parameters or remove them. *We will explain how to do all that shortly.*
Figure(1): The C# component and its location in the toolbar. **x**: first input parameter. **y**: second input parameter. out: output string with compiling messages. **a**: Returned output of type object.
Alternatively, you can use the generic **Script** component, then click on the C# at the margin.
Figure(2): The default Script component in Grasshopper
## 1.3 The Input Parameters By default, there are two input parameters named **x** and **y**. It is possible to edit the parameters’ names, delete them or add new ones. If you zoom in, you will notice a few “+” and “-” signs appearing. You can click on those to add or remove parameters. You can also right-click on a parameter to change its name. Note that the names of the parameters and their types are passed to the main function inside the script component, which is called **RunScript**. It is a good practice to set the input and output parameter names to reflect what each parameter does. Parameter names should not use spaces or special characters.
Figure(3): Zoom in to add or remove input parameters by pressing the + and - signs
If you right-click on an input parameter, you will see a menu that has four parts as detailed in *Figure 4* below. drop_menu.png
Figure(4): Expand the input parameter menu (access by right-clicking on the parameter).
The input parts are: 1. **Name**: You can click on it and type a new parameter name. Also, you have the option to set a description of the parameter and the toolt**ip. 2. **General attributes**: common to most other GH components 3. **Data access**: to indicate whether the input should be accessed as a single item, a list of items, or as a tree. *Note: We will explain how to traverse and navigate data access options in some detail later in this chapter.* 4. **Type**: Input parameters are set by default to be of an **object** type. It is best to specify a type to make the code more readable and efficient. Types can be primitive, such as **int** or **double**, or **RhinoCommon** types that are used only in Rhino such as **Point3d** or **Curve**. You need to set the type for each input. 5. **Help**: full description of the scripting component Just like all other GH components, you can hook data to the input parameters of the script components. Your script component can process most data types. You can specify the data type of your input using the **Type hint** as in the following image.
Figure(5): The type hint in the input allows setting input parameters to specific types. (1) no type, (2) primitive types, and (3) RhinoCommon types
The Type hint, gives access to many data types and can be divided into three groups: 1. **No Type Hint**. if you do not specify the input type, GH assigns the base type **System.Object**. The **System.Object** is the root type that all other types are built on. Therefore, it is safe to assign **System.Object** type to any data type. 2. **Primitive** types. Those are made available by the **.NET** framework. 3. **RhinoCommon** types. Those are defined in the **RhinoCommon SDK**. ## 1.4 The Output Parameters Just like with input parameters, you can add or remove output parameters by zooming in, then use the “+” or “-” signs. You can also change the name of the output. However, there is no data access or data types that you can set. They are always defined as **System.Object**, and hence you can assign any type, and as defined by your script and GH, take care of parsing it to use downstream.
Figure(6): Zoom in to add or remove an output parameter by pressing the + and - gadgets
## 1.5 The Out String The **out** string is used to list errors and other useful information about your code. You can connect the **out** to a **Panel** component to be able to read the information.
Figure(7): The out parameter includes compile and runtime messages
There are two types of messages that print to the **out** string. 1. **Compile-time** messages. These include compiler errors and warnings about your code. This is very helpful information to point you to the lines in your code that the compiler is complaining about, so that you can correct the error(s). 2. **Runtime** messages. You can print any text to the **out** string to track information generated inside your code during execution. ## 1.6 Main Menu You can access the main component menu by hovering over the middle of the scripting component (black label) and right-clicking. Most of the functions are similar to other GH components, but there are a couple specialized functions such as **Open Script Editor...** to open the code editor and **Manage Assemblies** to help add external libraries to access in your script.
Figure(8): Scripting component main menu
## 1.7 Code Editor To show the code editor window for the C# script component, you need to double-click in the middle of the component (or right-click the middle and select **Open Script Editor…**). The code editor supports debugging. For a full explanation of the editor interface and functionality, please visit the [Grasshopper Script Editor for C# guide](/guides/scripting/scripting-gh-csharp/).
Figure(9): The code editor has 4 parts. (1) toolbar, (2) code, (3) cache, (4) OK
The code window of the C# scripting component has a few distinct sections as in *Figure 10*. Next, we will expand and explain each of the parts.
Figure(10): The parts of the code section of the C# scripting component: (1) default imports, (2) default members and functions of the script instance, (3) the RunScript function when the code is placed.
### 1.7.1 Imports There are assemblies that you can access and use in your code. Most of them are the .NET system ones, but there are also the Rhino and Grasshopper assemblies that give access to the Rhino geometry classes and the Grasshopper types and functions. You can also add your custom libraries or external assemblies in this section.
Figure(11): Default imports in the C# scripting component: (1) system imports, (2) Rhino imports, (3) Grasshopper imports
### 1.7.2 Utility Members & Functions The members are useful variables that can be utilized in your code. Members include a one-and-only instance to the active Rhino document, an instance to the Grasshopper document, the current GH scripting component, and finally the iteration variable, which references the number of times the script component is called (usually based on the input and data access). For example, when your input parameters are single items, then the script component runs only once, but if you input a list of values, say 10 of them, then the component is executed 10 times (that is assuming that the **data access** is set to a **single item**). You can use the **Iteration** variable if you would like your code to do different things at different iterations or to simply analyze how many times the component is called.
Figure(12): Reference list of built in utility methods
You can use the methods to help debug and communicate useful information. The **Print()** functions to send strings to the **out** output parameter. You can use the **Reflect()** functions to gain information regarding classes and their methods. For example, if you wish to get more information about the data that is available through the input parameter **x**, you can add **Reflect(x)** to your code. As a result, a string with type method information will be written to the **out** parameter.
Figure(13): Reference list of built-in utility members
### 1.7.3 The RunScript This is the main function where you write your code. The signature of the **RunScript** includes the input and output parameters along with their data types and names. There is a space between the open and closed parentheses to indicate the region where you can type your code.
Figure(14): The RunScript is the main function within which the user can put their code.
## 1.8 Data Access This topic requires knowledge in C# programming. If you need to review or refresh your knowledge in C# programming, please review *Chapter 2* before reading this section. Grasshopper scripting components, just like all other GH components, can process three types of data access; **item access**, **list access**, and **tree access**.
Icons in GH Data Access
Item Access
List Access
Tree Access
You need to right-click on the input parameter to set its data access, otherwise it is set to **item access** by default. We will explain what each access means and how data is processed inside the component in each case.
Figure(22): Set the input parameter to be an **item access** to indicate that input in processed one element at a time.
### 1.8.1 Item Access **Item access** means that the input is processed one item at a time. Suppose you have a list of numbers that you need to test if they are odd or even. To simplify your code, you can choose to only process one number at a time. In this case, **item access** is appropriate to use. So even if the input to **num** is a list of integers, the scripting component will process one integer at a time and run the script a number of times equal to the length of the list. ```C# private void RunScript( int num, ref object IsEven ) { int mod = num % 2; IsEven = (mod == 0 ? true : false); } ``` With **item access**, each element in a list is processed independently from the rest of the elements. For example, if you have a list of 6 numbers {1, 2, 3, 4, 5, 6} and you would like to increment each one of them by some number — let’s say 10 — to get the list {11, 12, 13, 14, 15, 16}, then you can set the data access to **item access**. The script will run 6 times, once for each element in the list, and the output will be a list of 6 numbers. Notice in the following implementation in GH where **x** is input as a single item of type **double**. ```C# private void RunScript( double x, ref object a ) { A = x + 10; Print("Run# " + Iteration); } ``` ### 1.8.2 List Access **List access** means the whole list is processed all in one call to the scripting component. In the previous example, we can change the data access from **item access** to **list access** and change the script to add 10 to each one of the items in the list as in the following. *Note that the script runs only once.* ```C# private void RunScript( List xList, ref object a ) { List newList = new List(); foreach( var x in xList) newList.Add(x + 10); Print("Run# " + Iteration); A = newList; } ``` The **list access** is most useful when you need to process the whole list in order to find the result. For example, to calculate the sum of a list of input numbers, you need to access the whole list. Remember to set the data access to **list access**. ```C# private void RunScript( List xList, ref object Sum) { double xSum = 0; foreach( var x in xList) xSum = xSum + x; Print("Run# " + Iteration); Sum = xSum; } ``` ### 1.8.3 Tree Access **Tree** data access in Grasshopper is an advanced topic. You might not need to deal with trees, but if you encounter them in your code, then this section will help you understand how they work and how to do some basic operations with them. Trees (or multi-dimensional data) can be processed one element at a time, one branch (path) at a time, or all branches at the same time. That will depend on how you set the data access (item, list, or tree). For example, if you divide two curves into two segments each, then we get a tree structure that has two branches (or paths), each with three points. Now, suppose you want to write a script to place a sphere around each point. The code will be different based on how you set the **data access**. If you make it **item access**, then the script will run 6 times (once for each point), but the tree data structure will be maintained in the output with two branches and three spheres in each branch. Your code will be simple because it deals with one point at a time. ```C# private void RunScript( Point3d pt, ref object Sphere) { Sphere ptSphere = new Sphere(x, 0.5); Print("Run# " + Iteration); Sphere = ptSphere; } ``` The same result can be achieved with the **list access**, but this time, your code will have to process each branch as a list. The script runs 2 times, once for each branch. ```C# private void RunScript( List ptList, ref object Spheres) { List sphereList = new List(); foreach (Point3d pt in ptList) { Sphere sphere = new Sphere(pt, 0.5); sphereList.Add(sphere); } Print("Run# " + Iteration); Spheres = sphereList; } ``` The **tree access** allows you to process the whole tree, and your code will run only once. While this might be necessary in some cases, it also complicates your code. ```C# private void RunScript( List ptList, ref object Spheres) { List sphereList = new List(); foreach (Point3d pt in ptList) { Sphere sphere = new Sphere(pt, 0.5); sphereList.Add(sphere); } Print("Run# " + Iteration); Spheres = sphereList; } ``` The **tree access** allows you to process the whole tree, and your code will run only once. While this might be necessary in some cases, it also complicates your code. ```C# private void RunScript( DataTree ptTree, ref object Spheres) { //Declare a tree of spheres DataTree sphereTree = new DataTree(); int pathIndex = 0; foreach (List branch in ptTree.Branches) { List sphereList = new List(); GH_Path path = new GH_Path(pathIndex); pathIndex = pathIndex + 1; foreach (Point3d pt in branch) { Sphere sphere = new Sphere(pt, 0.5); sphereList.Add(sphere); } sphereTree.AddRange(sphereList, path); } Print("Run# " + Iteration); Spheres = sphereTree; } ``` The **tree data** structure is a special data type that is unique to Grasshopper. If you need to generate a tree inside your script, then the following shows how to do just that. For more details about Grasshopper data structures, please refer to the [Essential Algorithms & Data Structures for Grasshopper](https://www.rhino3d.com/download/rhino/6.0/essential-algorithms). ```C# private void RunScript( ref object Numbers) { DataTree numTree = new DataTree(); int pathIndex = 0; for (int b = 0; b <= 1; b++) { List numList = new List(); GH_Path path = new GH_Path(pathIndex); pathIndex = pathIndex + 1; for (int i = 0; i <= 2; i++) { numList.Add(10); } numTree.AddRange(numList, path); } Numbers = numTree; } ``` The following example shows how to step through a given data tree of numbers to calculate the overall sum: ```C# private void RunScript( DataTree x, ref object Sum) { double xSum = 0; foreach (List branch in x.Branches){ foreach (double num in branch) { xSum = xSum + num; } } Sum = xSum; } ``` ## Next Steps Those are the basics of C# data structures. Next, learn the [Chapter 2: C# Programming Basics](/guides/grasshopper/csharp-essentials/2-csharp-basics). This is part 1 of the [Essential C# Scripting for Grasshopper guide](/guides/grasshopper/csharp-essentials/). -------------------------------------------------------------------------------- # Chapter 2: C# Programming Basics Source: https://developer.rhino3d.com/en/guides/grasshopper/csharp-essentials/2-csharp-basics/ ## 2.1 Introduction This chapter covers basic C# programming concepts. It serves as an introduction and quick reference to C#'s language syntax. It is not meant to be complete by any measure, so please refer to the C# resources available online and in print. All examples in this chapter are implemented using the Grasshopper C# component. For additional documentation [The Microsoft CSharp Programming Guide](https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/) is a good resource. ## 2.2 Comments Comments are very useful to describe your code in plain language. They are useful as a reminder for you and also for others to help them understand your code. To add a comment, you can use two forward slashes // to signal that the rest of the line is a comment and the compiler should ignore it. In the Grasshopper C# code editor, comments are displayed in green. You should enclose a multi-line comment between /* and */ as in the following example. ```C# // The compiler ignores this line /* The compiler ignores all lines enclosed within this area */ ``` ## 2.3 Variables You can think of variables as labeled containers in your computer’s memory where you can store and retrieve data. Your script can define any number of variables and label them with the names of your choice, as long as you do not use spaces, special characters, or reserved words by the programming language. Try to always use variable names that are descriptive of the data you intend to store. This will make it easier for you to remember what kind of information is stored in each variable.
Figure(16): How variables are stored in a computer's memory
Your script uses variable names to access the value stored in them. In general, you can assign new values to any variable at any point in your program, but each new value wipes out the old one. When you come to retrieve the value of your variable, you will only get the last one stored. For example, let’s define a variable and name it **x**. Suppose we want **x** to be of type integer. Also, suppose we would like to assign it an initial value of 10. This is how you write a statement in C# to declare and assign an integer: ```C# int x = 10; ``` Let us dissect all the different parts of the above statement:
int The type of your data. int is a special keyword in C# that means the type of the variable is a signed integer (can be a positive or negative whole number)
x The name of the variable
= Used for assignment and it means the value that follows will be stored in the variable x
10 The initial value stored in the x variable
; The semicolon is used to end a single statement
In general, when you declare a variable, you need to explicitly specify the **data type**. ## 2.4 Operators Operators are used to perform arithmetic, logical, and other operations. Operators make the code more readable because you can write expressions in a format similar to that in mathematics. So, instead of having to use functions and write **C=Add(A, B)**, we can write **C=A+B**, which is easier to read. The following is a table of the common operators provided by the C# programming language for quick reference:
Type Operator Description
Arithmetic Operators * Multiplies two numbers
/ Divides two numbers and returns a floating-point result
\ Divides two numbers and returns an integer result
% Remainder: divides two numbers and returns only the remainder
+ Adds two numbers or returns the positive value of a numeric expression
- Returns the difference between two numeric expressions or the negative value of a numeric expression
Assignment Operators = Assigns a value to a variable
*= Multiplies the value of a variable by the value of an expression and assigns the result to the variable
+= Adds the value of a numeric expression to the value of a numeric variable and assigns the result to the variable. Can also be used to concatenate a String expression to a String variable and assign the result to the variable.
-= Subtracts the value of an expression from the value of a variable and assigns the result to the variable
Comparison Operators < Less than
<= Less or equal
> Greater than
>= Greater or equal
== Equal
!= Not equal
Concatenation Operators + Generates a string concatenation of two expressions
Logical Operators && Performs a logical conjunction on two Boolean expressions
! Performs logical negation on a Boolean expression
|| Performs a logical disjunction on two Boolean expressions
## 2.5 Namespaces Namespaces are very useful to group and organize classes, especially for large projects. **.NET** uses namespaces to organize its classes. For example, **System** namespace has many classes under it, including the **Math** class, so if you like to calculate the square root of a number, your code will look like the following: ```C# double num = 16; double sqrNum = System.Math.Sqrt(num); ``` Notice how you use the namespace followed by “.” to access the classes within that namespace. If you do not want to type the namespace each time you need to access one of the classes within that namespace, then you can use the **using** keyword as in the following: ```C# using System; double num = 16; double sqrNum = Math.Sqrt(num); ``` ## 2.6 Data Data types refer to the kind of data stored in the variable. There are two main data types. The first is supplied by the programming language and involves things like numbers, logical true or false, and characters. Those are generally referred to as primitive or built-in types. Variables of any data type can be composed into groups or collections. ### 2.6.1 Primitive Data Types Primitive types refer to the basic and built-in types provided by the programming language. The following examples declare variables of primitive data types:
Declare primitive data types Notes
double pi = 3.1415; double: big number with decimal point
bool pass = true; bool: set to either true or false. Used mainly to represent the truth value of a variable or a logical statement
char initial = ‘R’; char: stores exactly one character
string myName = “Mary”; string: a sequence of characters
object someData = “Mary”; object type can be used to store data of any type. The use of object type is inefficient and should be avoided if at all possible.
### 2.6.2 Collections In many cases, you will need to create and manage a group of objects. There are generally two ways to group objects: either by organizing them in arrays or using a collection. Collections are more versatile and flexible, and they allow you to expand or shrink the group dynamically. There are many ways to create collections, but we will focus on ordered ones that contain elements of the same data type. For more information, you can reference the literature. **Arrays** Arrays are a common way to assemble an ordered group of data. Arrays are best suited if you already know the values you are storing, and do not need to expand or shrink the group dynamically.
Figure(17): How arrays are stored in the computer memory
For example, you can organize the days of the week using an array. The following declares and initializes the weekdays array in one step: ```C# // Declare and initialize the days of the week array string[] weekdays = {"Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"}; ``` You can also declare the array first with a specific dimension representing the number of elements to allocate in memory for the array, then assign the values of the elements in separate statements. In the C# language, each statement ends with a semicolon: ```C# // Declare the days of the week in one statement, then assign the values later string[ ] weekdays = new string[7]; weekdays[0] = "Sunday"; weekdays[1] = "Monday"; weekdays[2] = "Tuesday"; weekdays[3] = "Wednesday"; weekdays[4] = "Thursday"; weekdays[5] = "Friday"; weekdays[6] = "Saturday"; ``` Let’s take the statement that sets the first value in the weekdays array in the above. ```C# weekdays[0] = “Sunday”; ``` **Sunday** is called an **array element**. The number between brackets is called the **index** of the element in the array. Notice that in **C# language** the array always starts with index=0 that points to the first element. The last index of an array of seven elements therefore equals 6. If you try to retrieve data from an invalid index, for example 7 in the weekday example above, then you will get what is called an **out of bound error** because you are basically trying to access a part of the memory that you did not allocate for your array, and it can lead to a crash. **Lists** If you need to create an ordered collection of data dynamically, then use a **List**. You will need to use the **new** keyword and specify the data type of the list. The following example declares a new list of integers and appends elements incrementally in subsequent statements. *Note that the indices of a list are also zero based just like arrays.* ```C# // Declare a dynamic ordered collection using “List” List myWorkDays = new List(); // Add any number of new elements to the list myWorkDays.Add("Tuesday"); myWorkDays.Add("Wednesday"); myWorkDays.Add("Thursday"); myWorkDays.Add("Friday"); ``` You need to use the keyword **new** to declare an instance of a **List**. *We will explain what it means to be a reference type with some more details later.* ## 2.7 Flow Control The flow of your scripts indicates the order of code execution. Sometimes you might need to branch or loop through specific statements several times. For example, you might want to branch your script to implement a different sequence based on some condition. Other times, you might need to repeat a certain sequence multiple times. We will discuss three important mechanisms to control the flow of a program: conditional statements, loops, and methods. ### 2.7.1 Conditional Statements Conditional statements allow you to decide on which part of your code to use based on some condition that can change during runtime. Unlike regular programming instructions, conditional statements can be described as decision-making logic. Conditional statements help control and regulate the flow of the program.
Figure(17): Conditional statements 1 & 2 and how they affect the flow of the program
The following script examines a number variable and prints “zero” if it equals zero: ```C# if (myNumber == 0) { Print("myNumber is zero"); } ```
if Keyword indicating the start of the if statement
(myNumber == 0) The condition of the if statement enclosed by parentheses
{ Print("myNumber is zero"); } The if statement block enclosed between curly brackets. The block is executed only if the condition evaluates to true, otherwise it is skipped.
The following code checks if a number is between 0 and 100: ```C# if (myNumber >= 0 && myNumber <= 100) { Print("myNumber is between 0 and 100"); } ``` Sometimes, the script needs to execute one block when some condition is satisfied and another if not. Here is an example that prints the word **positive** when the given number is greater than zero and prints **less than 1** if not. It uses the **if… else** statement. ```C# if (myNumber > 0) { Print("positive"); } else { Print( "less than 1" ); } ``` You can use as many conditions as you need, as shown in the following example: ```C# if (myNumber > 0) { Print("myNumber is positive"); } else if (myNumber < 0) { Print( "myNumber is negative" ); } else { Print( "myNumber is zero" ); } ``` ### 2.7.2 Loops Loops allow you to run the body of your loop a fixed number of times or until the loop condition is no longer true.
Figure(18): Loops in the context of the overall flow of the program
There are two kinds of loops. The first is iterative where you can repeat code a defined number of times, and the second is conditional where you repeat until some condition is no longer satisfied. **Iterative Loops: for loop** This is a common way of looping when you need to run a block of code a specific number of times. Here is a simple example that prints numbers from 1 to 10. You first declare a counter and then increment in the loop to run the code a specific number of times. *Notice that the code statements you would like to repeat are bound by the block after the for statement.* ```C# for (int i = 1; i <= 10; i++) { // Convert a number to string Print( i.ToString() ); } ```
Line Description
1 The for statement. It has 4 parts: for(... ; … ; ...): the for keyword and loop condition and counter
i=1: the counter initial value
i<=10: the condition: if evaluates to true, then execute the body of the for loop
i++: increment the counter (by 1 in this case)
3-5 The body of the for loop. This is the code that is executed as long as the condition is met.
Note: if the condition of the for loop is always true, then the program will enter what is called an “infinite loop” that leads to a crash. For example, if the condition was “i>0” instead of “i<=10”, then it will cause an infinite loop.
The loop counter does not have to be increasing or change by 1. The following example prints the even numbers between 10 and -10. You start with a counter value equal to 10, and then change by -2 thereafter until the counter becomes smaller than -10. ```C# for (int i = 10; i >= -10; i = i-2) { Print( i.ToString() ); } ``` If you happen to have an array that you need to iterate through, then you can set your counter to loop through the indices of your array. Just remember that the indices of arrays are zero-based, and therefore, you need to remember to loop from index=0 to the length of the array minus 1, or else you will get an out-of-bounds error. ```C# string alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; char[ ] letters = alphabet.ToCharArray(); for (int i = 0; i < letters.Length; i++) { Print( i.ToString() + " = " + letters[i] ); } ``` Here is another example that iterates through a list of places: ```C# // List of places List< string > placesList = new List< string >(); placesList.Add( "Paris" ); placesList.Add( "NY" ); placesList.Add( "Beijing" ); int count = placesList.Count(); // Loop starting from 0 to count -1 (count = 3, but last index of the placesList is 2) for (int i = 0; i < count; i++) { Print( "I have been to " + placesList[i] ); } ``` **Iterative Loops: foreach loop** You can use the **foreach** loop to iterate through the elements of an array or a list without using a counter or index. This is a less error-prone way to avoid an out-of-bounds error. The above example can be rewritten as follows to use **foreach** instead of the **for** loop: ```C# // List of places List< string > placesList = new List< string >(); placesList.Add( "Paris" ); placesList.Add( "NY" ); placesList.Add( "Beijing" ); // Loop foreach (string place in placesList) { Print(place); } ``` **Conditional Loops: while loop** Conditional loops are ones that keep repeating until certain conditions are no longer true. You have to be very careful when you use conditional loops because you can be trapped looping infinitely until you crash when your condition continues to be true. Some problems can be solved iteratively, but others cannot. For example, if you need to find the sum of 10 consecutive positive even integers starting with 2, then this can be solved iteratively with a **for** loop. Why? This is because you have a specified number of times to loop (10 in this case). This is how you might write your loop to solve your problem: ```C# int sum = 0; for (int i = 1; i <= 10; i++) { sum = sum + ( i*2 ); } Print( sum.ToString() ); ``` On the other hand, if you need to add consecutive positive even integers starting with 2 until the sum exceeds 1000, then you cannot simply use an iterative **for** loop because you do not know how many times you will have to loop. You only have a condition to stop looping. Here is how you might solve this problem using the conditional **while** loop: ```C# int sum = 0; int counter = 0; int number = 2; // Loop while (sum < 1000) { sum = sum + number; // Make sure you increment the counter and the number counter = counter + 1; number = number + 2; } // Remove last number sum -= number; counter --; Print("Count = " + counter); Print("Sum = " + sum); ``` Here is another example from mathematics where you can solve the **[Collatz conjecture](http://en.wikipedia.org/wiki/Collatz_conjecture)**. It basically says that you can start with any natural positive integer, and if even then divide by 2, but if odd, then multiply by 3 and add 1. If you repeat the process long enough, then you always converge to 1. The following example prints all the numbers in a **Collatz conjecture** for a given number: ```C# int x = 46; while(x > 1) { Print( x.ToString() ); if( x % 2 == 0 ) x = x / 2; else x = x * 3 + 1; } Print( x.ToString() ); ``` **Nested Loops** A nested loop is a loop that contains another loop inside it. You can nest as many loops as you need, but keep in mind this can make the logic harder to follow.
Figure(19): Nested loops
For example, suppose you have the following list of words as input: {apple, orange, banana, strawberry} and you need to calculate the number of letters **a**. You will have to do the following: 1. Loop through each one of the fruits. This is the outer loop. 2. For each fruit, loop through all the letters. This is the inner loop (nested). 3. Whenever you find the letter “a”, increment your counter by 1. 4. Print the counter. ```C# // Declare and initialize the fruits string[ ] fruits = {"apple", "orange", "banana", "strawberry"}; int count = 0; // Loop through the fruits foreach (string fruit in fruits) { // Loop through the letters in each fruit foreach (char letter in fruit) { // Check if the letter is "a" If (letter == 'a') { count = count + 1; } } } Print( “Letter a is repeated “ + count + “ times“ ); ``` **Using break and continue inside loops** The **break** statement is used to terminate or exit the loop, while the **continue** statement helps skip one loop iteration. Here is an example to show the use of both. It checks if there is an orange in a list of fruits and counts how many fruits have at least one letter **r** in their name. ```C# // Declare and initialize the fruits string[ ] fruits = {"apple", "orange", "banana", "strawberry"}; //Check if there is at least one orange foreach (string fruit in fruits) { // Check if it is an orange if (fruit == "orange") { Print( "Found an orange" ); // No need to check the rest of the list, exit the loop break; } } // Check how many fruits has at least one letter ‘r’ in the name int count = 0; // Loop through the fruits foreach (string fruit in fruits) { // Loop through the letters in each fruit foreach (char letter in fruit) { // Check if the letter is not an "r", else continue with the next letter if (letter != 'r') { continue; } // Found the first "r" in fruit count = count + 1; //No need to check the rest of the letters, and skip to the next fruit break; } } } Print( "Number of fruits that have at least one letter r is: " + count ); ``` ## 2.8 Methods ### 2.8.1 Overview A method is a self-contained block of code that performs a specific task. Methods, which are similar to functions, can be called multiple times within a program, allowing for code reusability and modularity. Functions are basically used to organize your program into sub-tasks. The following is an example of a program flow that performs two specific tasks multiple times. Without using separate functions for these tasks, the same code must be rewritten each time the tasks are needed, leading to unnecessary repetition.
Figure(20): Sequential flow of the script where tasks are repeated
Most programming languages let you organize your tasks into separate modules, known as functions or methods. Think of each Grasshopper component like a function. You write the instructions for a task once, give it a name, and then use that name to refer to it whenever you need to perform the task again in your program. This makes your code cleaner and easier to manage. Your program flow will look like the following:
Figure(21): Methods can be called and executed as many times as the program requires
When you need to do the same task multiple times in your code, it’s a good idea to put that task in its own function. Using functions has several advantages: 1. It divides your program into smaller, easier-to-manage pieces. Smaller sections of code are simpler to understand, develop, test, and maintain than a large, single block. 2. You write and test the code once, then reuse it wherever needed. If you need to make changes, you only have to update it in one place. Writing functions might not always be easy, and they need some planning. There are four general steps you need to satisfy when writing a function: 1. Define the purpose of your function, and give it a descriptive name. 2. Identify the data that goes into the function when it is called. 3. Identify the data that the function needs to give back or return. 4. Consider what you need to include inside the function to perform your task. For example, suppose you would like to develop a function that checks whether a given natural number is a prime number or not. Here are four things you need to do: 1. **Purpose & name**: Check if a given number is prime. Find a descriptive name for your function, for example “**IsPrimeNumber**”. 2. **Input parameters**: Pass your number as type integer. 3. **Return**: True if the number is prime, otherwise return false. 4. **Implementation**: Use a loop of integers between 2 and half the number. If the number is not divisible by any of those integers, then it must be a prime number. *Note: There are more efficient ways to quickly test for a prime number.* ```C# bool IsPrimeNumber( int x) { bool primeFlag = true; for( int i = 2; i < (x / 2); i++) { if( x % i == 0) { primeFlag = false; break; // Exit the loop } } return primeFlag; } ``` Let us dissect the different parts of the **IsPrimeNumber** function to understand the different parts.
Line Description
1 The name of the function proceeded by the return type. The function body is enclosed inside the curly parenthesis. Function parameters and their types.
3-12 This is called the body of the function. It includes a list of instructions to examine whether the given number is prime or not, and append all divisible numbers.
13 Return value. Use return keyword to indicate the value the function gives back.
### 2.8.2 Method Parameters Input parameters are enclosed within parentheses after the method name. The parameters are a list of variables and their types. They represent the data the function receives from the code that calls it. Each parameter is passed either **by value** or **by reference**. Keep in mind that variables themselves can be a value-type such as primitive data (int, double, etc.), or reference-types such as lists and user-defined classes. The following table explains what it means to pass value or reference types by value or by reference.
Parameters Description
Pass by value a value-type data such as int, double, bool The caller passes a copy of the actual value of the parameter. Changes to the value of a variable inside the function will not affect the original variable passed from the calling part of the code.
Pass by reference a value-type data Indicates that the address of the original variable is passed and any changes made to the value of that variable inside the function will be seen by the caller.
Pass by value a reference-type data such as lists, arrays, object, and all classes Reference-Type data holds the address of the data in memory. Passing a Reference-Type data by value simply copies the address, so changes inside the function will change the original data. If the caller cares that its data is not affected by the function, it should duplicate the data before passing it to any function.
Pass by reference a reference-type data Indicates that the original reference of the variable is passed. Again, any changes made to the variable inside the function are also seen by the caller.
As you can see from the table, whether the parameter is passed as a copy (by value) or as a direct link (by reference), it's mostly relevant to **value-type** variables such as **int**, **double**, **bool**, etc. If you need to pass a **reference-type** such as objects and lists, and you do not wish the function to change your original data (act as though the variable is passed by value), then you need to duplicate your data before passing it. All input values to the GH **RunScript** function (the main function in the scripting component) are duplicated before being used inside the component so that input data is not affected. Let’s expand the **IsPrimeNumber** function to return all numbers each input is divisible by. We can pass a list of numbers to be processed inside the function (**List** is a reference type). Notice it does not matter if you pass the list with the **ref** keyword. In both cases, the caller changes the original data inside the function. ```C# private void RunScript(int num, ref object IsPrime, ref object Factors) { // Assign variables to output List factors = new List(); IsPrime= isPrimeNumber(num, factors); Factors = factors; } // Note: “List” is a reference type and hence you can pass it by value (without “ref” keyword) // and the caller still gets any changes the function makes to the list bool IsPrimeNumber( int num, ref List factors ) { // All numbers are divisible by 1 factors.Add(1); bool primeFlag = true; for( int i = 2; i < (num / 2); i++) { if( num % i == 0) { primeFlag = false; factors.Add( i); //append number } } // All numbers are divisible by the number itself factors.Add(num); return primeFlag; } ``` Passing parameters **by reference** using keyword **ref** is very useful when you need to get more than one **value-type** back from your function. Since functions are allowed to return only one value, programmers typically pass more parameters by reference as a way to return more values. Here is an example of a division function that returns success if the calculation is successful but also returns the result of the division using the **rc** parameter that is passed **by-reference**. ```C# public bool Divide(double x, double y, ref double rc) { if (Math.Abs(y) < 1e-100) return false; else rc = x / y; return true; } ``` As we explained before, **RunScript** is the main function that is available in the **C#** and other script components. This is where the main code lives. Notice that the scripting component output is passed by reference. This is what you see when you open a default **C#** script component in Grasshopper.
Code Description
{
}
Curly parentheses enclose the function's body of code.
RunScript This is the name of the main function.
RunScript(…) Parentheses after the function name enclose the input parameters. This is the list of variables passed to the function.
object x x is the first input parameter. It is passed by value, and its type is object. That means changes to “x” inside the RunScript do not change the original value.
object y y is the second input parameter. It is passed ByVal, and its type is Object.
ref object a axis the third input parameter. It is passed by reference, and its type is Object. It is also an output, because you can assign it a value inside your script and the change will be carried outside the RunScript function.
## 2.9 User-Defined Data Types We mentioned above that there are built-in data types supported by the programming language, such as int, double, string, and object. However, users can create their own custom data types with specific functionalities that suit the application. There are a few ways to create custom data types. We will explain the most common ones: enumerations, structures, and classes. ### 2.9.1 Enumerations Enumerations help make the code more readable. An enumeration “provides an efficient way to define a set of named integral constants that may be assigned to a variable.” You can use enumerations to group a family of options under one category and use descriptive names. For example, there are only three values in a traffic light signal. ```C# enum Traffic { Red = 1, Yellow = 2, Green = 3 } Traffic signal = Traffic.Yellow; if( signal == Traffic.Red) Print("STOP"); else if(signal == Traffic.Yellow) Print("YIELD"); else if(signal == Traffic.Green) Print("GO"); else Print("This is not a traffic signal color!"); ``` ### 2.9.2 Structures A structure is used to define a new **value-type**. In C# programming , we use the keyword **struct** to define new structure. The following is an example of a simple structure that defines a number of variables (fields) to create a custom type of a colored 3D point. We use **private** access to the fields and use **properties** to **get** and **set** the fields. ```C# struct ColorPoint{ // Fields for the point XYZ location & color private double _x; private double _y; private double _z; private System.Drawing.Color _c; // Properties to get and set the location & color public double X { get { return _x; } set { _x = value; } } public double Y { get { return _y; } set { _y = value; } } public double Z { get { return _z; } set { _z = value; } } public System.Drawing.Color C { get { return _c; } set { _c = value; } } } ``` As an example, you might have two instances of the **ColorPoint** type, and you need to compare their location & color. Notice that when you instantiate a new instance of the **ColorPoint** object, the object uses a default constructor that sets all fields to “0”: ```C# ColorPoint cp0 = new ColorPoint(); // Using default constructor sets the fields to zero Print("Default ColorPoint 0: X=" + cp0.X + ", Y=" + cp0.Y + ", Z=" + cp0.Z + ", Color=" + cp0.C.Name); // Set fields cp0.X = x; cp0.Y = y; cp0.Z = z; cp0.C = c; Print("ColorPoint 0: X=" + cp0.X + ", Y=" + cp0.Y + ", Z=" + cp0.Z + ", Color=" + cp0.C.Name); ColorPoint cp1 = new ColorPoint(); // Set fields cp1.X = x1; cp1.Y = y1; cp1.Z = z1; cp1.C = c1; Print("ColorPoint 1: X=" + cp1.X + ", Y=" + cp1.Y + ", Z=" + cp1.Z + ", Color=" + cp1.C.Name); // Compare location MatchLoc = false; if(cp0.X == cp1.X && cp0.Y == cp1.Y && cp0.Z == cp1.Z) MatchLoc = true; // Compare color MatchColor = cp0.C.Equals(cp1.C); ``` This is the output you get when implementing the above struct and function in a C# component: Structs typically define one or more constructors to allow setting the fields. Here is how we expand the example above to include a constructor: ```C# struct ColorPoint{ // Fields private double _x; private double _y; private double _z; private System.Drawing.Color _c; // Constructor public ColorPoint(double x, double y, double z, System.Drawing.Color c) { _x = x; _y = y; _z = z; _c = c; } // Properties public double X { get { return _x; } set { _x = value; } } public double Y { get { return _y; } set { _y = value; } } public double Z { get { return _z; } set { _z = value; } } public System.Drawing.Color C { get { return _c; } set { _c = value; } } } ``` You can use properties to only set or get data. For example, you can write a property that get the color saturation: ```C# // Property public double Saturation { get { return _c.GetSaturation(); } } ``` We can rewrite the **Saturation** property as a method as in the following: ```C# // Method public double Saturation() { return _c.GetSaturation(); } ``` However, methods typically include more complex functionality, multiple inputs, or possible exceptions. There are no fixed rules about when to use either, but it is generally acceptable that properties involve data, while methods involve actions. We can write a method to calculate the average location & color of two input ColorPoints: ```C# // Method public static Average (ColorPoint a, ColorPoint b) { ColorPoint avPt = new ColorPoint(); avPt.X = (a.X + b.X) / 2; avPt.Y = (a.Y + b.Y) / 2; avPt.Z = (a.Z + b.Z) / 2; avPt.C = Color.FromArgb((a.C.A + b.C.A) / 2, (a.C.R + b.C.R) / 2, (a.C.G + b.C.G) / 2, (a.C.B + b.C.B) / 2); return avPt; } ``` Structures can include members and methods that can be called, even if there is no instance created of that type. Those use the keyword **static**. For example, we can add a member called OriginBlack that creates a black point located at the origin. We can also include a **static** method to compare if two existing points have the same color, as in the following: ```C# // Static methods public static bool IsEqualLocation(ColorPoint p1, ColorPoint p2) { return (p1.X == p2.X && p1.Y == p2.Y && p1.Z == p2.Z); } public static bool IsEqualColor(ColorPoint p1, ColorPoint p2) { return p1.C.Equals(p2.C); } ``` Because structs are value types, if you pass your colored points to a function and change the location (x,y,z), it will only be changed inside or within the scope of that function but will not affect the original data, unless explicitly passed by reference, as in the following example: ```C# void ChangeLocationByValue(ColorPoint cp) { cp.X += 10; cp.Y += 10; cp.Z += 10; Print(" Inside change location= (" + cp.X + "," + cp.Y + "," + cp.Z + ")"); } void ChangeLocationByReference(ref ColorPoint cp) { cp.X += 10; cp.Y += 10; cp.Z += 10; Print(" Inside change location= (" + cp.X + "," + cp.Y + "," + cp.Z + ")"); } ``` Using our **ColorPoint** struct, the following is a program that generates 2 colored points, compares their location & color, then finds their average: ```C# // Create 2 instances of ColorPoints type ColorPoint cp0 = new ColorPoint(1, 1, 1, System.Drawing.Color.Red); ColorPoint cp1 = new ColorPoint(1, 1, 1, System.Drawing.Color.Blue); // Compare location bool matchLoc = ColorPoint.isEqualLocation(cp0, cp1); // Compare color Bool matchColor = ColorPoint.isEqualColor(cp0, cp1); // Output average point and color ColorPoint avPt = ColorPoint.Average(cp0, cp1); ``` ### 2.9.3 Classes Classes help create new data types that are **reference-type**. They also have added functionality compared to structures. The main added functionality is they support creating a hierarchy of types, where each new level inherits the members and methods of the level above it. Many of the geometry types in **RhinoCommon** use classes to define them. Let us redefine the **ColorPoint** above as a class that inherits from a generic **Point** class: ```C# class CPoint{ // Fields private double _x; private double _y; private double _z; // Constructors public CPoint() : this(0,0,0) //default calls another constructor { } public CPoint(double x, double y, double z) { _x = x; _y = y; _z = z; } // Properties public double X { get { return _x; } set { _x = value; } } public double Y { get { return _y; } set { _y = value; } } public double Z { get { return _z; } set { _z = value; } } // Static methods public static bool IsEqualLocation(CColorPoint p1, CColorPoint p2) { return (p1.X == p2.X && p1.Y == p2.Y && p1.Z == p2.Z); } } class CColorPoint: CPoint{ // Fields private System.Drawing.Color _c; // Constructors public CColorPoint() : base()//default { _c = System,.Drawing.Color.White; } public CColorPoint(double x, double y, double z, System.Drawing.Color c) : base (x, y, z) { _c = c; } // Properties public System.Drawing.Color C { get { return _c; } set { _c = value; } } // Static methods public static bool IsEqualColor(CColorPoint p1, CColorPoint p2) { return p1.C.Equals(p2.C); } // Method public static CColorPoint Average (CColorPoint a, CColorPoint b) { CColorPoint avPt = new CColorPoint(); avPt.X = (a.X + b.X) / 2; avPt.Y = (a.Y + b.Y) / 2; avPt.Z = (a.Z + b.Z) / 2; avPt.C = Color.FromArgb((a.C.A + b.C.A) / 2, (a.C.R + b.C.R) / 2, (a.C.G + b.C.G) / 2, (a.C.B + b.C.B) / 2); return avPt; } } ``` You can use the same program written above for **ColorPoint struct** to create an average point between 2 color points. The only difference is that if you now pass an instance of the class **CColorPoint** to a function, it will be passed by reference, even without using the **ref** keyword. Here are the key differences between structures & classes:
Struct Class What does it mean?
Value type Reference type Value types (struct) are cheaper to allocate & deallocate in memory, however the assignment of a large value type can be more costly than a reference type (class).
Passed to functions by value Passed to functions by reference When you pass a parameter to a function as an instance of a reference type, changes inside the function affect all references pointing to that same instance. Value types are copied when passed by value.
Cannot use inheritance Can use inheritance Use classes when building a hierarchy of objects or when the data type is large, and use structs when creating types that are generally small, short-lived, and embedded inside other objects.
### 2.9.4 Value vs Reference Types It is worth stressing the two data classifications: **value-types** & **reference-types**. We touched on that topic when introducing methods and how parameters are passed **by value** or **by reference**. There are also differences in how the two are stored and managed in memory. Here is a summary comparison between the two classifications:
Value data types Reference data types
Examples All built-in numeric data types (such as integer, double, bool, and char), Arrays, and Structures Lists
Classes
Memory storage Stored inline in the program stack Stored in the heap
Memory management Cheaper to create and clear from memory especially for small data Costly to clear (use garbage collectors), but more efficient for big data
When passed as parameters in methods Passes a copy of the data to methods, which means that the original data is not changed even when altered inside the method Passes the address of the original data, and hence any changes to the data inside the method change the original data as well
### 2.9.5 Interface You may need your structures & classes to implement some common functionality. For example, you may need to check if two instances of the same type are equal. In this case, you would like to make sure that you use the same method name and signature. To ensure that, you can define that functionality inside a separate entity called **interface** and have your structure & classes implement that same **interface**. **The .NET Framework** provides interfaces, but you can also define your own. For example, we can implement an **IEquatable** interface in the **ColorPoint struct** as in the following: ```C# struct ColorPoint: IEquatable { // Fields private double _x; private double _y; private double _z; private System.Drawing.Color _c; // Properties public double X { get { return _x; } set { _x = value; } } public double Y { get { return _y; } set { _y = value; } } public double Z { get { return _z; } set { _z = value; } } public System.Drawing.Color C { get { return _c; } set { _c = value; } } // Implement Equals method inside IEquatable interface - if omitted, you’ll get an error public bool Equals( ColorPoint pt ) { return (this._x == pt._x && this._y == pt._y && this._z == pt._z && this._c == pt._c); } } ``` ## 2.10 Read & Write Text Files There are many ways to read from and write to files in **C#** and many tutorials & documents are available online. In general, reading a file involves the following: - Open the file (generally, you need a path to point to). - Read a string (whole text or line by line). - Tokenize the string using some delimiter characters (such as a comma, space, etc.) - Cast each token to the appropriate data type. - Store the result in your data structure. We will discuss a simple example that parses points from a text file. Using the following text file format, we will read each line as a point and use the first value as x coordinate, the second as y, and the third as z of the point. We will then use these points to create a NURBS curve. The input to the scripting component is the path to the text file, and the output is an array of points. You can then use these points as input to create a curve in Grasshopper. Here is the code inside the C# script component. It reads the file line by line, then parses each into a point assuming that each point is stored in one line and the coordinates are comma-separated. ```C# // Read the file string[] lines = File.ReadAllLines(path); // Declare list of points (Point3d is a data type in RhinoCommon SDK) List pts = new List(); // Loop through lines foreach (string line in lines) { // Tokenize line into array of strings separated by "," string[] parts = line.Split(",".ToCharArray()); // Convert each coordinate from string to double double x = Convert.ToDouble(parts[0]); double y = Convert.ToDouble(parts[1]); double z = Convert.ToDouble(parts[2]); pts.Add(new Point3d(x, y, z)); } Points = pts; ``` The above code does not do any validation of the data and will only work if the text file has no errors, such as empty lines, invalid numbers, or simply does not follow the expected format. Validating the data before using it is a good practice that helps make your program robust and avoid crashes & errors. The following is a rewrite of the same code but with validation (highlighted): ```C# if ((!File.Exists(path)) || !read) { // Show message box System.Windows.Forms.MessageBox.Show("File does not exist or could not be read"); // Give feedback in the component out parameter Print("Exit without reading"); return; } // Read the file string[] lines = File.ReadAllLines(path); // Check that file is not empty if ((lines == null)) { Print("File has no content. Exit without reading"); return; } // Declare list of points (Point3d is a data type in RhinoCommon SDK) List pts = new List(); // Characters to remove var charsToRemove = new string[] { " ", ")", "(", "[", "]", "{", "}" }; // Loop through lines foreach (string line in lines) { // Trim invalid char var tLine = line; foreach (var c in charsToRemove) { tLine = tLine.Replace(c, string.Empty); } if (String.IsNullOrEmpty(line)) continue; // Tokenize line into array of strings separated by "," string[] parts = tLine.Split(",".ToCharArray()); // Make sure that each line has exactly 3 values if (parts.Length != 3) continue; // Convert each coordinate from string to double double x = Convert.ToDouble(parts[0]); double y = Convert.ToDouble(parts[1]); double z = Convert.ToDouble(parts[2]); pts.Add(new Point3d(x, y, z)); } A = pts; ``` ## 2.11 Recursive Functions Recursive functions are functions that call themselves until a stopping condition is met. Recursion is not very common, but it is useful in data searching, subdividing, and generative systems. Just like conditional loops, recursive functions need to be designed & tested carefully for all possible input; otherwise, you run the risk of crashing your code. The next example takes an input line then makes a copy that is shorter & rotated. It keeps doing that for each newly generated line until the line length becomes less than some minimum length. The following table shows the input & output parameters:
Function parameters
Input parameters Line inputLine Starting line
double angle Angle in radians
double minLength The stopping condition
Output parameters Line [] A The result as an array of lines
To compare the two approaches, we will solve the problem recursively & iteratively to be able to compare the two approaches. Note that not all recursive cases can be solved iteratively. Also, some cases are easier to solve recursively than iteratively or vice-versa. If you do not design your recursive function carefully, you can end up in an infinite loop. In the following recursive solution, note that inside the **DivideAndRotate** function includes: - A stopping condition to exit the function - A call to the same function from within the function (recursive function call themselves) - The result (array of lines) is passed by reference to keep adding new lines to the list. ```C# private void RunScript(Line inputLine, double angle, double minLength, ref object Lines) { // Declare all lines List allLines = new List(); // Call recursive function if(inputLine.IsValid && angle != 0 && minLength > 0) { // Append input line as a first line in the list of lines allLines.Add(inputLine); DivideAndRotate(inputLine, ref allLines, angle, minLength); } else Print("Invalid input. Nothing done."); // Assign return value Lines = allLines; } public void DivideAndRotate(Line currLine, ref List allLines, double angle, double minLength) { // Check the stopping condition if (currLine.Length < minLength) return; // Take a portion of the line Line newLine = new Line(); newLine = currLine; Point3d endPt = new Point3d(); endPt = newLine.PointAt(0.95); newLine.To = endPt; // Rotate Transform xform = default(Transform); xform = Transform.Rotation(angle, Vector3d.ZAxis, newLine.From); newLine.Transform(xform); allLines.Add(newLine); // Call self DivideAndRotate(newLine, ref allLines, angle, minLength); } ``` We can solve the same problem using an iterative solution through a conditional loop. Note that: - The stopping condition appears in the **while** loop condition. - The **DivideAndRotate** function parameters are passed by value (not changed inside the function). The function simply calculates and returns the new line. ```C# private void RunScript(Line inputLine, double angle, double minLength, ref object Lines) { // Declare all lines var allLines = new List(); if(inputLine.IsValid && angle != 0 && minLength > 0) { // Find current length double length = inputLine.Length; Line newLine = default(Line); newLine = inputLine; // Loop until length is less than min length while (length > minLength) { // Generate the new line newLine = DivideAndRotate(newLine, angle); // Add to list allLines.Add(newLine); // Stopping condition length = newLine.Length; } } else Print("Invalid input. Nothing done."); // Assign return value Lines = allLines; } public Line DivideAndRotate(Line currLine, double angle) { // Take a portion of the line Line newLine = new Line(); newLine = currLine; Point3d endPt = new Point3d(); endPt = newLine.PointAt(0.95); newLine.To = endPt; // Rotate newLine.Transform(Transform.Rotation(angle, Vector3d.ZAxis, newLine.From)); // Function return return newLine; } ``` ## Next Steps Those are the basics of C# Programming. Next, learn [RhinoCommon Geometry in Grasshopper](/guides/grasshopper/csharp-essentials/3-rhinocommon-geometry/). This is part 2 of the [Essential C# Scripting for Grasshopper guide](/guides/grasshopper/csharp-essentials/). -------------------------------------------------------------------------------- # Chapter 2: Introduction to Data Structures Source: https://developer.rhino3d.com/en/guides/grasshopper/gh-algorithms-and-data-structures/data-structures/ All algorithms involve processing input data to generate a new set of data as output. Data is stored in well-defined structures to help access and manipulate efficiently. Understanding these structures is the key for successful algorithmic designs. This chapter includes an in-depth review of the basic data structures in Grasshopper. ## 2.1 Overview Overview of data structures in Grasshopper Grasshopper has three distinct data structures: single item, list of items and tree of items. GH components execute differently based on input data structures, and hence it is essential to be fully aware of the data structure before using. There are tools in GH to help identify the data structure. Those are Panel and Param Viewer.
Figure(34): Data structures in Grasshopper
Processes in GH execute differently based on the data structure. For example, the Mass Addition component adds all the numbers in a list and produces a single number, but when operating on a tree, it produces a list of numbers representing the sum of each branch.
Figure(35): Components execute differently based on the data structures. Result of adding numbers from Figure(34)
The wires connecting the data with components in GH offer additional visual reference to the data structure. The wire from a single item is a simple line, while the wire connecting a list is drawn as a double line. A wire output from a tree data structure is a dashed double line. This is very useful to quickly identify the structure of your data.
Display the data structure Example
Item: single branch with single item Wire display: single line
List: single branch with multiple items Wire display: double line
Tree: multiple branches with any number of items per branch Wire display: double dashed line
## 2.2 Generating lists Generate lists in Grasshopper There are many ways to generate lists of data in GH. So far we have seen how to directly embed a list of values inside a parameter or a panel (with multiline data). There are also special components to generate lists. For example, to generate a list of numbers, there are three key components: Range, Series and Random. Lists can be the output of some components such as Divide curve (the output includes lists of points, tangents and parameters). Use the Panel component to preview the values in a list and Parameter Viewer to examine the data structures.
The Range component creates equally spaced range of numbers between a min and max values (called domain) and a number of steps (the number of values in the resulting list is equal to the number of steps plus one).
Figure(36): Generate a list of 8 numbers using the Range component in Grasshopper
The Series component also creates an equally spaced list of numbers, but here you set the starting number, step size and number of elements.
Figure(37): Generate a list of 7 numbers using the Series component in Grasshopper
The Random component is used to create random numbers using a domain and a number of elements. If you use the same seed, then you always get the same set of random numbers.
Figure(38): Generate a list of numbers using the Random component in Grasshopper
The Divide component outputs divide points, tangents and parameters on curve.
Figure(39): Divide Curve takes a single input (curve) and generate lists of output
Tutorial: 2_2_1 Generating lists
Explore 4 different ways to create circles. Use different data sources and data structures.
Solution...
1. Directly set a circle in a parameter
2. Set the plane input to the default XY-Plane (internal). Supply a list of radiuses using Range component
3. Supply one value for the center. Normal is set to default (internal) List of radiuses using Random component
4. Create a circle from 3 points: A: set internally to one value B: Supply one value C: Supply a list of values using the Series component to set a list of Z coordinates
## 2.3 List operations Generate lists in Grasshopper Grasshopper offers an extensive list of components for list operations and list management. We will review the most commonly used ones.
You can check the length of a list using the List Length component, and access items at specific indices using the List Item component.
Figure(40): Examples of list operations in Grasshopper
Lists can be reversed using the Reverse List component, and sorted using the Sort List component.
Figure(41): Lists can be reversed or sorted using designated components in Grasshopper
Components such as Cull Patterns and Dispatch allow selecting a subset of the list, or splitting the list based on a pattern.These components are very commonly used to control data flow and select a subset of the data.
Figure(42): Cull part of a list using components such as Cull Pattern and Dispatch
The Shift List component allows shifting a list by any number of steps. That helps align multiple lists to match in a particular order.
Figure(43): Shift operation in Grasshopper
The Subset component is another example to select part of a list based on a range of indices.
Figure(44): Select a subset of the list using a range of indices
Tutorial: 2_3_1 List operations
Given two lists of points from dividing two concentric circles, generate the following patterns:
Solution...
Solution video...
## 2.4 List matching Generate lists in Grasshopper When the input is a single item or has an equal number of elements in a simple list, it is easy to imagine how the data is matched. The matching is based on corresponding indices. Let’s use the Addition component to examine list matching in GH. Note that the same principles apply to all other Grasshopper components.
Figure(45): Examples of primitive data types common to all programming languages
There are times when input has variable length lists. In this case, GH reuses the last item on the shorter list and matches it with the next items in the longer list.
Figure(46): The default list matching in Grasshopper reuses the last element of the shorter list
Grasshopper offers alternative ways of data matching: Long, Short and Cross reference that the user can force to use. The Long matching is the same as the default matching. That is, the last element of the shorter list is repeated to create a matching length.
Figure(47): Long list matching is the default matching mode in Grasshopper
The Short list matching truncates the long list to match the length of the short list. All additional elements are ignored and the resulting list has a length that matches the shorter list.
Figure(48): Short matching of lists omits additional values in longer lists
The Cross Reference matches the first list with each of the elements in the second list. The resulting list has a length equal to the multiplication product of the length of input lists. Cross reference is useful when trying to produce all possible combinations of input data. The order of input affects the order of the result as shown in Figure (49).
Figure(49): Cross reference matching creates longer lists to account for all possible permutations
If none of the matching methods produce the desired result, you can explicitly adjust the lists to match in length based on your requirements. For example, if you like to repeat the shorter list until it matches the length of the longer list, then you’ll need to create the logic to achieve that as in the following example.
Figure(50): Need to create custom script to generate custom matching
Tutorial 2_4_1 List matching
Use the 4-step method to generate an algorithm that takes 6 numbers (0 to 5) and turn them into a cube of points as in the image:
Solution...
Output: A list of 6x6x6 = 216 points constructed from a list of X, Y, Z coordinates
Key Process: Use the Construct Point component to generate the list of points
Input: Examine input using the Parameter Viewer and Panel components. The given list has 6 points representing each coordinate along each axis
Intermediate processes: Need to find all possible permutations for the coordinates to create the cube of 216 points along all 3 axes. Use Cross Reference matching to generate lists of coordinates that have all possible permutations
Putting it all together
## 2.5 Tutorials: data structures
Tutorial 2.5.1: Variable thickness pipe
Use the 4-step method to create a surface similar to the one in the image:
Solution... download GH file... Solution video... Algorithm Analysis: We can think of two different ways to generate this surface: 1. Loft circles created along a line at random locations with random radii 2. Create a profile curve at the circles start points, and Revolve around the line
Output: The surface
Key Process: Use the Loft component to generate the surface
Input: Line (not given, so create one), Number of intervals (not given, assume it is equal 10) Thickness range (not given, assume it is equal to 1.0 to 3.0)
Intermediate processes #1: The Loft is created from a list of circles. Use the Circle component that takes centers, normals and radii lists. Use the default Loft options
Intermediate processes #2: To create a list of random radii, use the Random component and the input thickness range
Intermediate processes #3: Evaluate the line at random intervals. Use the Evaluate Curve component to extract center points and normals, and use the Random component to generate the parameters along the curve. Problem: The loft follows the order of input curves. however, the parameters (generated from the Random component) are not ordered along the line and hence it produces unordered circles. Use the Sort List component to order the parameters before feeding them into the Evaluate Curve
Putting it all together
Tutorial 2.5.2: Custom list matching
Given the following three lists of numbers: [1, 2], [10, 20, 30] and [0.2, 0.4, 0.6, 0.9, 1], explain the default GH list matching when they are used as input. Compare the default matching with the Grasshopper Shortest List matching. Finally, use the original lists to create custom matching that repeats the pattern in the shorter lists to create a periodic matching until it matches the length of the longest list. For example [1,2] becomes [1,2,1,2,1].
Solution... download GH file...
Construct default GH matching: To test the matching, feed the lists into the coordinates of the Construct Point component and observe the result
Analysize GH default matching: The last element of shorter lists is repeated until all lists have the same length, then elements are matched by indices
Construct shortest List matching: Omit additional values in longer lists so that the length of all lists equals the length of the shortest list
Create repeated custom matching: We know that the longest list has 5 items, but it is a good practice to make the script generic so it works with any input. First, figure out the length of the longest list, then use the Repeat component to repeat the elements in shorter lists until they match the length of the longest list
Tutorial 2.5.3: Simple truss
Use the 4-step method to generate a simple truss as in the image. Use the given input for the baseline (base of the truss), height, number of runs (or spans), and the radius of the joint.
Solution... download GH file... Solution video...
Algorithm analysis:
Define values for the input: L= create a Line along X-Axis H= assume height=7 R= assume number of runs=10 J= assume joint radius=0.5
Divide the baseline into 20 spans (2*R)
Move every other point by 7 units (or H) in the Z-Axis direction
Create 3 sets of ordered points for the beams along the base, top and middle, then connect each of the 3 sets with a polyline. Create spheres to represent the joints.
Solution steps:
Output: There are 2 outputs, the beams as curves (polylines) and joints as spheres (surfaces)
Key Process: Need to create the polylines for the top, middle, and bottom beams. Use the Polyline component with a relevant set of points for each. Use the Sphere component to create joints. Use middle points and joint radius as input.
Input: Specify the inputs and assume values when they are not given: line, number of runs, height and joint radius
Intermediate processes #1: Divide the curve with twice the number of runs. Use Divide Curve component and Multiply the number of runs
Intermediate processes #2: To create top points, select every other point from the list of all divide points, then move vertically by the height amount. Use Cull Pattern component to select points and Move component to shift vertically
Intermediate processes #3: To create bottom points, select every other point, in the invert pattern used to select top points. Use Cull Pattern component to select points (set the invert flag for the pattern input)
Intermediate processes #4: To create middle points, Weave the top and bottom points.
Putting it all together
Tutorial 2.5.4: Pearl necklace
Create a necklace with one big pearl in the middle, and gradually smaller size pearls towards the ends as in the image. The number of pearls is between 15-25.
Solution... download GH file...
Algorithm analysis:
The workflow to create the necklace follows these general lines: 1. Divide the curve into segments of variable distances (widest in the middle and narrow towards the ends) 2. Find length and midpoints of each segment 3. Create spheres at midpoints using half the length as radius
Solution steps:
Output: The surfaces
Key Process: Use the Sphere component to generate the pearl surfaces
Input: Necklace curve, Number of pearls as a parameter (can be changed by the user)
Intermediate processes #1: The Range component creates equal distances. We need to change to variable distances and for that we can use the Graph Mapper component to control the spacing.
Intermediate processes #2: Since we have normalized distances from the start of the curve (parameters are between 0 to 1), we can use the Evaluate Length component to find the divide points.
Intermediate processes #3: Generate the segments. Use Polyline and Explode components to turn the points into segments Center points are calculated at the middle of the segments. Use Evaluate Length at mid length Radii are calculated as half of each segment length. Use Length and Division components
Putting it all together
## Next Steps Those are the introduction to data structures. Next, learn [Advanced Data Structures](/guides/grasshopper/gh-algorithms-and-data-structures/advanced-data-structures/). This is part 2-3 of the [Essential Algorithms and Data Structures for Grasshopper](/guides/grasshopper/gh-algorithms-and-data-structures/). -------------------------------------------------------------------------------- # Chapter 3: Advanced Data Structures Source: https://developer.rhino3d.com/en/guides/grasshopper/gh-algorithms-and-data-structures/advanced-data-structures/ This chapter is devoted to the advanced data structure in GH, namely the data trees, and different ways to generate and manage them. The aim is to start to appreciate when and how to use tree structures, and best practices to effectively use and manipulate them. ## 3.1 The Grasshopper data structure ### 3.1.1 Introduction Introduction to data trees In programming, there are many data structures to govern how data is stored and accessed. The most common data structures are variables, arrays, and nested arrays. There are other data structures that are optimised for specific purposes such as data sorting or mining. In Grasshopper, there is only one structure to store data, and that is the data tree. Hold on, what about what we have learned so far: single item and list of items? Well, in GH, those are nothing but simple trees. A single item is a tree with one branch, that has one element, and a list is a tree with one branch that has a number of elements. It is actually pretty elegant to be able to fit all data in one unifying data structure, but at the same time, this requires the user to be aware and vigilant about how their data structure changes between operations, and how that can affect intended results. This chapter attempts to demystify the data tree of Grasshopper. ### 3.1.2 Processing data trees We used the Panel and Parameter Viewer components to view the data structure. We will use both extensively to show how data is stored. Let’s start with a single item input. The Parameter Viewer has two display modes, one with text and one that is graphical. You can see that the single item input is stored in one branch that has only one item.
Figure(51): Different ways to preview the data structure in Grasshopper
The Parameter Viewer shows each branch address (called “Path”), and the number of elements in that branch as shown in Figure (52).
Figure(51): The Parameter Viewer indicates the path address and the number of elements in each branch
A list of items is typically stored in a tree with one branch. Figure (53). However, the three items can also be stored in three different branches. Figure (54).
Figure(53): A list is a tree that has one branch with multiple elements
Figure(54): A tree contains any number of branches with any number of elements in each branch
The key to understanding the Grasshopper data structure is to be able to answer the following question: What is the significance of storing x number of values in one branch vs using x number of branches to store one value in each branch? The data structure informs GH components about how to match input values. In other words, components may process data differently based on their structure. The following example illustrates how different data structures for the same set of values can affect the result.
Figure(55): Organizing same set of value in different data structures affect the output
We will elaborate on data tree matching later, but you can already see that GH components do pay attention to the data structure and the result can vary considerably based on it. This is one of the complications inherited in using one unifying data structure in a programming language. ### 3.1.3 Data tree notation The first step to understanding data trees is to learn the GH notation of trees. As we have seen from the examples, trees consist of branches, and each branch holds a number of elements. The address or path of each branch is represented with integers separated by semicolons and enclosed in curly brackets. The index of each element is enclosed by square brackets. This diagram shows a breakdown of the address of elements in trees.
Figure(56): Address of elements include the address of the branch and the index of the element in the branch
Here are a few examples of various tree structures and how they show in the Parameter Viewer and the Panel.
Figure(57): Same set of values held in different structures. Left: 5 trunks (5 trees) with one item in each. Middle: 5 branches out of one trunk (1 tree), and each branch holds a single item. Right: two trunks (2 trees), the first has 2 branches with the first branching into 3 branches, each holds one item, the second holds 1 item. The second trunk holds 2 items.
Tutorial 3.1.1 Data tree
1. In the tree, what is the full address to the item 1.2? Note that order of branches and leaves is always from left to right going clockwise 2. Construct the tree of numbers shown in the image below using the Number parameter only.
Solution...
The path to 1.2 is: { 0 ; 3 ; 0} [ 1 ] Note: The three branches from the main trunk are set here to 0:1, 0:2, and 0:3. They also could have been 0:0, 0:1 and 0:2. Both are correct.
## 3.2 Generating trees Generate data trees There are many ways to generate complex data trees. Some explicit, but mostly as a result of some processes, and this is why you need to always be aware of the data structures of output before using it as input downstream. It is possible to enter the data and set the data structure directly inside any Grasshopper parameter. Once set, it is relatively hard to change and therefore is best suited for a constant input. The following is an example of how to set a data tree directly inside a parameter.
Figure(58): Set data trees directly inside the parameter
There are many components that generate data trees such as Grid and DivideSrf, and others that combine lists into a tree structure such as Entwine. Also all the components that produce lists can also create a tree if the input is a list. For example, if you input more than one curve into the DivideCrv component, we get a tree of points.
Figure(59): The SDivide component takes one input (surface) and outputs a data tree (grid)
All components that generate lists of numbers (such as Range and Series) can also generate trees when given a list of input.
Figure(60): Entwine component takes any number of lists and combines them into a tree structure
Perhaps one of the most common cases to generate a tree is when dividing a list of curves to generate a grid of points. So the input is one list and the output is a tree.
Figure(61): EDivide component takes any list (curves) and generates a tree structure (grid)
Tutorial 3-2-1: Generating trees
Given the following list of points, construct a number tree with 3 branches, one for each coordinate.
Solution... Each input point is a single data item that contains 3 numbers (coordinates). We know we would like to isolate each coordinate into a separate list, then combine them into one data structure. Hence we need to first deconstruct input points (use Deconstruct of pDecon component), then combine the lists into one structure (use Entwine component).
## 3.3 Tree matching Matching data trees We explained the Long, Short and Cross matching with lists. Trees follow similar conventions to expand the shorter branches by repeating the last element to match. If one tree has fewer branches, the last branch is repeated. The following illustrates common tree matching cases. ### 3.3.1 Item-to-tree matching When matching an item to a tree, a matching tree structure is created and the item is repeated. For example when adding a single number to a tree of numbers, the single number is added to every item in the tree and the result is a tree with matching structure to the input tree. ### 3.3.2 Short-list-to-tree matching When matching a short list to a tree, where the length of the list is shorter than the tree branches, a matching tree structure is created where the list is repeated in each branch, and the last item in the short list is repeated. See the following example adding a list of two number to a tree of numbers. ### 3.3.3 Long-list-to-tree matching When matching a long list to a tree with branches that are shorter than the list, the tree branches expand to match the length of the list. The last item in each branch is repeated to match the list length Note that the resulting tree structure will be differenent than the input tree. In the following example, both input, the list and the tree, are modified to arrive to a matching structure, then the addition result have than new data structure. ### 3.3.4 Tree-to-tree matching There are numerous possibilities when matching two trees, but the basic principle apply. The goal is to find a tree structure that can combine both input tree structure. Let’s assume the case when there is a simple tree with one level of branches that match in depth. There are two possibilities. The first is that both input trees have same number of branches. In this case, the length of the shorter corresponding branches is matched. The output tree might end up matching at least one of the input trees, or be different to both. The second possibility is that one tree might have more branches than the other. In that case, new branches are inserted into the smaller tree repeating the last branch, then each branch is expanded (repeating the last item in the branch) to create matching length among all corresponding branches as in the following example. When working with trees, it is of utmost importance to examine the data structure of each input before using it as input to one component. A small change in the structure can have big impact. For example, the following two trees of numbers appear to be matching at first, but because there is one level depth added to one of the trees (indicating an extra branch near the root of the tree), the result may be different than what is intended.
Tutorial 3.3.1 Tree matching
Inspect the following 2 number structures, then predict the structure and result of adding them (with default Grasshopper matching). Verify your answer using the Addition components.
Solution...
Key solution idea: The two input trees have different number of branches and different number of elements in each branch. The last branch of the shorter tree is repeated to match the number of branches, then corresponding branches are matched by repeating the last element of the shorter branch.
## 3.4 Traversing trees Traversing data trees Grasshopper provides components to help extract branches and items from trees. If you have the path to a branch or to an item, then you can use Branch and Item components. You need to check the structure of your input so you can supply the correct path.
Figure(62): Select branches from a tree
Figure(63): Select items from a tree
If you know that your structure might change, or you simply do not want to type the path, you can extract the path using the Param Viewer and List Item components.
Figure(64): Example of how to extract data paths dynamically
Tutorial 3.4.1 Traversing trees
The following tree has 3 branches for each one of the coordinates (x, y, z) of some list of points. Use that tree to construct a list of these points.
Solution...
Key solution idea: We can construct a point list using as input 3 lists representing X, Y and Z values. If we can isolate the 3 branches of the input tree, then we will be able to feed them to the point construction component.
## 3.5 Basic tree operations Basic tree operations are widely used and you will likely need them in most solutions. It is very important to understand what these operations do, and how they affect the output. ### 3.5.1: Viewing the tree structure As we have seen in the data matching, different data structures of the same set of elements produce different results. Grasshopper offers three ways to view the data structure, the Parameter Viewer in text or diagram format, and the Panel.
Figure(65): View trees using the Parameter Viewer and the Panel components
Tree information can be extracted using the TStats component. You can extract the list of paths to all branches, number of elements in each branch and the number of branches.
Figure(66): Extract trees structure using TStats component
### 3.5.2 List operations on trees List operations on data trees Trees can be thought of as a list of branches. When using list operations on trees, each branch is treated as a separate list and the operation is applied to each branch independently. It is tricky to predict the resulting data structure and therefore it is always important to check your input and output structures before and after applying any operation. To illustrate how list operations work in trees, we will use a simple tree, a grid of points, and apply different list operations on it. We will then examine the output and its data structure.
Common list operation and how they apply to trees
List Item: Select items at specific index in each branch
List Item: Select multiple indices to isolate part of the tree and perform one operation on such as Mass Addition
Split List: Split the elements of branches into 2 trees at the specified index
Shift List: Shifts the elements of each branch
Cull Pattern: Culls each branch
### 3.5.3 Grafting from lists to a trees In some cases you need to turn a list into a tree where each element is placed in its own branch. Grafting can handle complex trees with branches of variable depths.
Figure(67): Grafting a tree creates a new branch for each element
It might feel unintuitive to complicate the data structure (from a simple list to a tree), but grafting is very useful when trying to achieve certain matching. For example if you need to add each element of one list with all the elements in the second list, then you will need to graft the first list before inputting to the addition process.
Figure(68): Grafting complex trees
### 3.5.4 Flattening from trees to lists Other times you might need to turn your tree structure into a simple list. This is achieved with the flattening process. Data from each branch is extracted and sequentially attached to one list.
Figure(69): Flattening place all tree elements in one list
Flatten also can handle any complex tree. It takes the branches in order starting with the lowest index trunk and put all elements in one list.
Figure(70): Flattening complex trees
### 3.5.5 Combining data streams It is possible to compose a number of lists into a tree where each list becomes a branch in a new tree. It is different from the merging of lists where simply one bigger list is created.
Figure(71): Entwine and Merge components combine lists into trees or bigger lists
### 3.5.6 Flipping the data structure It is logical in some cases to flip the tree to change the direction of branches.This is specially useful in grids when points are organized in rows and columns (similar to a 2 dimensional array structure). Flipping causes corresponding elements across branches (have the same index in their branch) to be grouped in one branch. For example, a data tree that has 2 branches and 4 items in each branch, can be flipped into a tree with 4 branches and 2 elements in each branch.
Figure(72): Flip helps reorganize data in a trees
If the number of elements in the branches are variable in length, some of the branches in the flipped tree will have “null” values.
Figure(73): Add “null” when flipping trees with variable length branches
Flipping is one of the operations that cannot handle variable depth branches, simply because there is no logical solution to flip.
Figure(74): Flip fails when the input tree has variable depth branches
### 3.5.7 Simplifying the data structure Processing data through multiple components can add unnecessary complexity to the data structure. The most common form is adding leading or trailing zeros to the paths addresses. Complex data structures are hard to match. Simplify Tree process helps remove empty branches. There are other operations such as Clean Tree and Trim Tree to help remove null elements, empty branches and reduce complexity. It is also possible to extract all branches as separate lists using the Explode Tree operation.
Figure(75): Paths can increase in complexity as more operations are applied to the data. Simplify helps remove empty branches
Tutorial 3.5.A Louvers
Given one curve on XY-Plane, create horizontal and vertical louvers as in the image
Solution...
Examine the data structure of output from each step before feeding it into the next process: input curve data structure: Single item (one branch and one item in the branch)
Divide input curve to extract points. Data structure: List (one branch with 11 items). Note that the path has added leading “0”. This indicates the next layer of calculation.
Create vertical lines at each point. Data structure: List (one branch with 11 items). Note that the path did not increase in complexity.
Divide vertical lines to create a grid of points. Data structure: Tree (11 branches with 6 items). Note that the path has added leading “0”.
Create horizontal lines at each point. Data structure: Tree (11 branches with 6 items). Note that the path did not increase in complexity.
Create lofted surfaces through branches of lines. Data structure:Tree (11 branches with 1 item each). Note that the path did not increase in complexity.
Flip the tree matrix and then create lofted surfaces through branches of lines. Data structure: Tree (11 branches with 1 item each). Note that the path did not increase in complexity. You can flatten the tree to create one list of horizontal louvers.
Tutorial 3.5.B Shutters
Given four corner points on a plane and a radius for the hinge, create a shutter that can open and shut as in the image using a rotation parameter
Solution...
Algorithm analysis: For each shutter there are two parts: the rectangle and the hinge. Union the rectangle and hinge, then allow rotating around the hinge. There is one rotation control to move all shutters together.
Solution steps:
Output: Surface of the shutters and curves for the frame
Input: The window four corner points (and center), the hinge radius and the rotation angle parameter
Key processe #1: create rectangle and hinges. Use the Rectangle component
Key processe #2: Union the curves. Use the RUnion component, then create a surface from the boundary using Boundary component
Intermediate process #1: Rotate the rectangles using the angle. Use Rotate component
Properly match the data structures of the rectangles and hinges before the region union. Use the Graft so that rectangles and hinges pair correctly.
Putting it all together:
## 3.6 Advanced tree operations As your solutions increase in complexity, so will your data structures. We will discuss three advanced tree operations that are necessary to solve specific problems, or are used to simplify your solution by tabbing directly into the power of the data tree structure. ### 3.6.1 Relative items Advanced data trees operations: Relative item The first operation has to do with solving the general problem of connectivity between elements in one tree or across multiple trees. Suppose you have a grid of points and you need to connect the points diagonally. For each point, you connect to another in the +1 branch and +1 index. For example a point in branch {0}, index [0], connects to the point in branch {1}, index [1].
Figure (76): Relative Item mask {+1}[+1] create positive diagonal connectivity
In Grasshopper, the way you communicate the offset is expressed with an offset string in the format “{branch offset}[index offset]”. In our example, the string to connect points diagonally is “{+1}[+1]”. Here is an example that uses relative tree component in Grasshopper. Notice that the relative item component creates two new trees that correlate in the manner specified in the offset string.
Figure (77): Relative Item mask {+1}[+1] breaks the original tree into 2 new trees with diagonal connectivity
Here is an example implementation in Grasshopper where we define relative items in one tree, then connect the two resulting trees with lines using the Relative Item component.
Figure (78): Relative Item with mask {+1}[+1] in Grasshopper
Tutorial 3.6.1.A Relative item pattern
Create the pattern shown in the image using a square grid of 7 branches where each branch has11 elements.
Solution...
Create the grid
Create relative trees that connect each element with -1 branch and +1 index: {-1}[+1] Create lines to connect the 2 relative trees.
Change the offset to {+2}[+3] to create the second connections
We showed how to define relative items in one tree, but you can also specify relative items between 2 trees. You’ll need to pay attention to the data structure of the two input trees and make sure they are compatible. For example, if you connect each point from the first tree with another point from a different tree with the same index, but +1 branch, then you can set the offset string to be {+1}[0].
Figure (79): Relative Items create connections across multiple trees
The input to the Relative Items component is two trees and the output is two trees with corresponding items according to the offset string.
Figure (80): The offset mask of the Relative Items generates new trees with the desired connections
The following GH definition achieves the above:
Figure (81): Relative Items implementation in Grasshopper
Tutorial 3.6.1.B Relative item truss
Use relative items between 2 bounding grids to generate the structure shown in the image
Solution... Solution video...
Create the connections for the bottom tree
Cull every other index and keep the same number of branches (cull indices 1, 3,...) Define the offset strings for RelativeItem components to create the vertical and horizontal connections The Grasshopper definition:
Create the connections for the top tree
Cull every other index and keep the same number of branches. (cull indices 0, 2,...) Define the offset strings for RelativeItem components to create the vertical and horizontal connections The Grasshopper definition:
Connections between the bottom and top trees
Use culled grids, then define first offset string for RelativeItems component to create the first set of cross lines: {0}[0] Define second offset string for RelativeItems component to define the second set of cross lines: {0}[-1]
### 3.6.2 Split trees Split data trees The ability to select a portion of a tree, or split into two parts is a very powerful tree operation in Grasshopper. You can split the tree using a string mask using specific syntax (see examples below). The mask filters, or helps select, the positive part of your tree. The portion of the tree left, is also given as an output and is called the negative part of the tree. Since all trees are made out of branches and indices, the split mask should include information about which branches and indices within these branches to split along. Here are the rules of the split mask:
Mask syntax General rules
{ ; ; } Use curly brackets to enclose the mask for the tree branches.
[ ] Use square brackets to enclose the mask for the elements (leaves). Can be omitted if select all items or use [*]
( ) Round brackets are used for organizing and grouping
* Any number of integers in a path. The asterisk also allows you to include all branches, no matter what their paths look like
? Any single integer
6 Any specific integer
!6 Anything except a specific integer
(2,6,7) Any one of the specific integers in this group.
!(2,6,7) Anything except one of the integers in this group.
(2 to 20) Any integer in this range (including both 2 and 20).
!(2 to 20) Any integer outside of this range.
(0,2,...) Any integer part of this infinite sequence. Sequences have to be at least two integers long, and every subsequent integer has to be bigger than the previous one (sorry, that may be a temporary limitation, don't know yet).
(0,2,...,48) Any integer part of this finite sequence. You can optionally provide a single sequence limit after the three dots
!(3,5,...) Any integer not part of this infinite sequence. The sequence doesn't extend to the left, only towards the right. So this rule would select the numbers 0, 1, 2, 4, 6, 8, 10, 12 and all remaining even numbers.
!(7,10,21,...,425) Any integer not part of this finite sequence.
{ * }[ (0 to 4) or (6,11,41) ] It is possible to combine two or more rules using the boolean and/or operators. The example selects the first five items in every list of a tree and also the items 7, 12 and 42.
Here are some examples of valid split masks.
Split mask by branches Description
{ * } Select all (the whole tree output as positive, and negative tree will be empty)
{ *; 2 } Select the third branch
{ *; (0,1) } Select the first two end branches
{ *; (0, 2, …) } Select all even branches
Split mask by branches and leaves Description
{ * }[(1,3,...)] Select elements located at odd indices in all branches
{ *; 0 }[(1,3,...)] Select elements located at odd indices in the first branch
{ *; (0, 2) }[(1,3,...)] Select elements located at odd indices in the first and third branches
{*; (0,2,...) } [ (1,3,...) ] Select elements located at odd indices in branches located at even indices
{*; (0,2,...) } [(0) or (1,3,...)] Select elements located at odd indices, and index “0”, in branches located at even indices
One of the common applications that uses split tree functionality is when you have a grid of points that you like to transform a subset of. When splitting, the structure of the original tree is preserved, and the elements that are split out are replaced with null. Therefore, when applying transformation to the split tree, it is easy to recombine back. Suppose you have a grid with 7 branches and 11 elements in each branch, and you’d like to shift elements between indices 1-3 and 7-9. You can use the split tree to help isolate the points you need to move using the mask: {*}[ (1,2,3) or (7,8,9) ], move the positive tree, then recombine back with the negative tree.
Figure (82): Split tree allows operating on a subset of the tree with the possibility to recombine back
This is the GH definition that does the above using the Split Tree component.
Figure (83): Split tree Grasshopper implementation of Figure (82)
One of the advantages of using Split Tree over relative trees is that the split mask is very versatile and it is easier to isolate the desired portion of the tree. Also the data structure is preserved across the negative and positive trees which makes it easy to recombine the elements of the tree after processing the parts.
Tutorial 3.6.2.A: Split tree pattern
Given a 6x9 grid, use the split tree to generate the following pattern:
Solution...
Create the grid
Split the tree to isolate the middle part
Split the middle part into two new parts
Move the two middle parts in opposite directions then recombine them
Recombine the middle part with the rest of the tree and create polylines through each branch elements
Tutorial 3.6.2.B: Split tree truss
Given a grid, create the following truss system using the split tree method
Solution... Solution video...
Create the 6x9 grid
Split at every other element
Move positive tree vertically
Combine positive and negative trees, and create a polyline through each branch
Create bottom curves using negative tree
Create top curves using positive tree
### 3.6.3 Path mapper Why use the Path Mapper? Syntax of the Path Mapper When dealing with complex data structures such as the Grasshopper data trees, you’ll find that you need to simplify or rearrange your elements within the tree. There are a few components offered in Grasshopper for that purpose such as Flatten, Graft or Flip. While very useful, these might not suffice when operating on multiple trees or needing custom rearrangement. There is one very powerful component in Grasshopper that helps with reorganizing elements in trees or change the tree structure called the Path Mapper. It is perhaps the least intuitive to use and can cause a loss of data, but it is also the only way to find a solution in some cases, and hence it pays to address here. The Path Mapper maps data between source and target paths. The source path is fixed, and is given by the input tree. You can only set the target path. There is a set of constants that you can use to help construct the mapping. Those are listed in the table below.
Path Mapper Constants Description
item_count Number of items in the current branch
path_count Number of paths (branches) in the tree
path_index Index of the current path
Let’s start by familiarizing ourselves with the syntax using built-in mappings inside the Path Mappers. If you right-muse-click on the mapper components, you can open the editor, and also access a bumber of default mapping functions that are commonely used.
Figure(83): Algorithmic solutions involve explicit definition of geometry, vectors and transformations
The **Null Mapping** does not change the data tree organization, but it offers a good start because it populates the editor with the input data structure. Set the mapping to **Null Mapping** and double click on the component to open the editor. The default tree source include the path only to start, but can add the data index part if needed for your mapping.
Figure(84): The Path Mapper syntax and editor
The following example examines different built-in mapping in the Path Mapper and how it changes the data structure. The Polyline component creates one polyline through each branch of the tree. Notice how different mapping affect the result.
Mappings
Null Output = Input tree
Flatten Convert to a list
Graft Put leaves in branches
Reverse Reverse the tree
Renumbering Renumber branches
For more details about the Path Mapper, please refer to the help inside the component in Grasshopper.
Tutorial 3.6.3.A: Partitions
Given a grid, create the following truss system using the split tree method
Solution...
The input has two trees, and each has 5 branches with 11 elements in each branch, a total of 10 branches
A Polyline is used to connect the elements in each branch
To create the vertical connections, you need to create a branch for each 2 corresponding elements across the 2 trees, then use Polyline to connect them 1. Analyze the paths of the trees 2. Come up with a mapping that generates the desired grouping
First, group corresponding branches across the 2 trees. That can be achieved by switching the last two integers in the paths:
Second, Flip each of the 5 trees. Since the branches have 11 elements each, flipping each tree will create 11 branches with 2 elements in each branch. Total of 55 branches. You flip by switching the last integer of the path with the element index:
Finally, a Polyline makes the vertical connections. Note: You can combine the 2 mappings in one step as in the following:
Tutorial 3.6.3.B: Bruilding structure
Given the input tree of points, create the following structure.
Solution...
The initial tree has 42 branches, 7 branches in each of the 6 trees
The Polyline component connects the elements in each branch
Flip the trees using Path Mapper by switching branch and element indices
Regroup the elements of corresponding branches in all trees using the Path Mapper
Final result Create all connections
## 3.7 Tutorials: advanced data structures
Tutorial 3.7.1: Sloped roof
Create a parametric truss system that changes gradually in height as shown in the image.
Solution... ownload GH file...
Algorithm analysis: First, solve it for one simple truss
Identify desired output for a single truss
Define initial input 1- Base line on XY-Plane 2- Number of runs 3- Height
Algorithms steps:
Create input (L=line, H=height and R= #runs)
Divide curve by 2*R
Move every other point in the Z direction by height
Create 3 sets of ordered points for the bottom beams, top beams and middle beams, then connect each of the 3 sets with a polyline
Implement the algorithm for a single truss In Grasshopper:
Resolve for multiple trusses with variable height:
Create a series of base lines using the initial line and copy in Y-Axis direction
Use the list of lines as input instead of a single line. Notice that instead of a list of points for each of the 3 sets (bottom, top and middle), we now have a tree or grid of points with a number of branches equal to the number of trusses
Create cross connections using Flip tree operation for the bottom and top trees
Create variable height
The complete solution implementation in Grasshopper:
Tutorial 3.7.2: Diagonal triangles
Given the input grid, use the RelativeItem component to create diagonal triangles
Solution... download GH file...
Algorithm analysis:
To generate the triangles, we need 3 sets of corner points. Two of the point sets (A, B) are within the grid. B is diagonal from A (relative index is +1 branch and +1 element) The third point set (C) is a copy of set (B) moved vertically. Group corners to connect into boundaries then generate surfaces
Grasshopper implementation:
Use RelativeItem to create set A and set B (use “{+1}[+1] mask) Move set B vertically.
Create a tree with 3 branches for sets A, B and C. Flip the tree to group corresponding points. Use Polyline and Boundary to generate the surfaces.
Tutorial 3.7.3: Zigzag
Create the structure shown in the image using a base grid as input.
Solution... download GH file...
Algorithm analysis:
Since the zigzags alternate directions from one row to the next, it is best to split the grid into 2 parts, positive and negative. Find 3 sets of points in the positive tree and order Reverse the elements in the branches of the negative tree, then find the 3 sets of points and order Merge back the 2 trees to create geometry through points
Grasshopper implementation:
Use the Split Tree component to generate positive and negative trees for both bottom and top grids. Use {0,2,...} split mask. Use RelativeItems2 to create A and B trees, use {0}[+1] relative mask. Use Shift to create the C tree. Use Weave to combine data in the intended order, then remove consecutive duplicates using the DCon component.
Merge ordered positive and negative trees to generate geometry using Polyline and Pipe components.
Tutorial 3.7.4: Truss
Create the structure shown in the image using a base grid as input.
Solution... download GH file...
Algorithm analysis:
Understanding input: The 2 input grids with similar data structure (7 branches and with 9 elements). Bottom grid: Top grid:
Understanding output: There are 4 parts to the output:
1. Bottom beams 2. Top beams
3. Middle beams 4. Middle plates
Discussion: Constructing the bottom and top grids can be achieved with culling some points and flipping the points grid to get both directions. The middle beams weave the 2 culled grids of the bottom and top grids. We can also use the culled points to create the joints. Constructing the triangular connections is more involved since we need to create groups of 3 points that use a pair of consecutive points from the bottom grid and one point from the top. We can use relative trees to solve this. We can then offset the triangles to create the frame points, and offset again to create the plates points.
Grasshopper implementation:
Cull top and bottom tree, flip culled tree, then feed the 4 trees into a pipe component with the desired radius as a parameter
Weave bottom and top grids to generate the grid of middle beams. Connect grid rows with a Polyline the use Pipe with the radius as a parameter
To create the triangular connections, we will use a relative tree on the culled bottom grid, and combine with the culled top grid. Use Offset to generate smaller grid for the plates and their frame Offset the triangles to create desired sizes. Use Pipe and boundary to create frames and surfaces for the plates
The full Grasshopper definition
Tutorial 3.7.5: Weaving
Create the structure shown in the image using a base grid as input.
Solution... download GH file...
Algorithm analysis:
The input is a planar square grid with vertical branches. For vertical threads: Split the grid into two parts alternating elements in each branch. Move the first part up, and the second down, then recombine the parts into one set Draw a curve through the points in each branch. Flip the grid, then repeat to create horizontal curves
Grasshopper implementation:
Use Split Tree to separate alternating points and move up and down Combine points and use IntCrv to interpolate through points of each branch Flip the tree, and repeat Split, Combine and IntCrv to create curves in the other direction
The full Grasshopper definition
Expanded solution:
Instead of using the Z-Axis to move points up and down, use the surface normal direction at each point Note: Make sure the data structure of normals and points match
The Grasshopper definition:
## End of guide This is part 3-3 of the [Essential Algorithms and Data Structures for Grasshopper](/guides/grasshopper/gh-algorithms-and-data-structures/). -------------------------------------------------------------------------------- # Chapter 3: RhinoCommon Geometry Source: https://developer.rhino3d.com/en/guides/grasshopper/csharp-essentials/3-rhinocommon-geometry/ ## 3.1 Overview **RhinoCommon** is the **.NET SDK** for Rhino. It is used by Rhino plug-in developers to write **.NET** plug-ins for Rhino and Grasshopper. All Grasshopper scripting components can access **RhinoCommon** including all geometry objects and functions. For the whole namespace, see the **[RhinoCommon documentation](https://developer.rhino3d.com/api/RhinoCommon)**. In this chapter, we will focus on the part of the **SDK** dealing with Rhino geometry. We will show examples of how to create and manipulate geometry using the Grasshopper C# component. The use of the geometry classes in the **SDK** requires basic knowledge in vector mathematics, transformations, and NURBS geometry. If you need to review or refresh your knowledge in these topics, then refer to the *[Essential Mathematics for Computational Design](https://www.rhino3d.com/download/rhino/6/essentialmathematics)* If you recall from Chapter 2, we worked with value types such as **int** and **double**. Those are system built-in types provided by the programming language, **C#** in this case. We also learned that you can pass the value types to a function without changing the original variables (unless passed by reference using the **ref** keyword). We also learned that some types, such as **objects**, are always passed by reference. That means changes inside the function also changes the original value. The system built-in types, whether they are value or reference types, are often very limiting in specialized programming applications. For example, in computer graphics, we commonly deal with points, lines, curves, or matrices. These types need to be defined by the **SDK** to ease the creation, storage, and manipulation of geometry data. Programming languages offer the ability to define new types using **structures** (value types) and **classes** (reference types). The **RhinoCommon SDK** defines many new types as we will see in this chapter. ## 3.2 Geometry Structures **RhinoCommon** defines basic geometry types using structures. We will dissect the **Point3d** structure, show how to read in the documentation, and use it in a script. This should help you navigate and use other structures. Below is a list of the geometry structures:
Structures Summary description
Point3d Location in 3D space. There are other points that have different dimensions such as: Point2d (parameter space point) and Point4d to represent control points.
Vector3d Vector in 3D space. There is also Vector2d for vectors in parameter space.
Interval Domain. Has min and max numbers
Line A line defined by two points (from-to)
Plane A plane defined by a plane origin, X-Axis, Y-Axis, and Z-Axis
Arc Represents the value of a plane, two angles, and a radius in a subcurve of a circle
Circle Defined by a plane and radius
Ellipse Defined by a plane and 2 radii
Rectangle3d Represents the values of a plane and two intervals that form an oriented rectangle
Cone Represents the center plane, radius, and height values in a right circular cone
Cylinder Represents the values of a plane, a radius, and two heights--on top and beneath--that define a right circular cylinder
BoundingBox Represents the value of two points in a bounding box defined by the two extreme corner points.This box is therefore aligned to the world X, Y, and Z axes
Box Represents the value of a plane and three intervals in an orthogonal, oriented box that is not necessarily parallel to the world Y, X, Z axes
Sphere Represents the plane and radius values of a sphere
Torus Represents the value of a plane and two radii in a torus that is oriented in 3D space
Transform 4x4 matrix of numbers to represent geometric transformation
### 3.2.1 The Point3d Structure The **[Point3d](https://developer.rhino3d.com/api/RhinoCommon/html/T_Rhino_Geometry_Point3d.htm)** type includes three **fields** (X, Y, and Z). It defines a number of **properties** and also has **constructors** and **methods**. We will walk through all the different parts of **Point3d** and how it is listed in the **RhinoCommon** documentation. First, you can navigate to **Point3d** from the left menu under the **Rhino.Geometry** namespace. When you click on it, the full documentation appears on the right. At the very top, you will see the following: #### Here is a break down of what each part in the above **Point3d** documentation means:
Part Description
Point3D Structure Represents the ... Title and description
Namespace: Rhino.Geometry The namespace that contains Point3d
Assembly: RhinoCommon (in RhinoCommon.dll) The assembly that includes that type. All geometry types are part of the RhinoCommon.dll.
/[SerializableAttribute/] Allow serializing the object
public struct Point3d public: public access: your program can instantiate an object of that type
struct: structure value type.
Point3d: name of your structure
: ISerializable,
IEquatable,
IComparable,
IComparable,
IEpsilonComparable
The “:” is used after the struct name to indicate what the struct implements. Structures can implement any number of interfaces. An interface contains a common functionality that a structure or a class can implement. It helps with using consistent names to perform similar functionality across different types. For example, IEquatable interface has a method called “Equal”. If a structure implements IEquatable, it must define what it does (in Point3d, it compares all X, Y, and Z values and returns true or false).
#### Point3d Constructors Structures define constructors to instantiate the data. One of the **Point3d** constructors takes three numbers to initialize the values of X, Y, and Z. Here are all the constructors of the **Point3d** structure . The following example shows how to define a variable of type Point3d using a GH C# component. ```C# private void RunScript(double x, double y, double z, ref object Point) { // Create an instance of a point and initialize to x, y, and z Point3d pt = new Point3d(x, y, z); // Assign the point "pt" to output Point = pt; } ``` #### Point3d Properties Properties are mostly used to “get” and/or “set” the fields of the structure. For example, there are the “X”, “Y”, and “Z” properties to get & set the coordinates of an instance of **Point3d**. Properties can be **static** to get specific points, such as the origin of the coordinate system (0,0,0). The following are **Point3d** properties as they appear in the documentation: Here are two GH examples to show how to get & set the coordinates of a **Point3d**. ```C# private void RunScript(Point3d pt, ref object A, ref object B, ref object C) { // Assign the point coordinates to output a = pt.X; b = pt.Y; c = pt.Z; } ``` ```C# private void RunScript(double x, double y, double z, ref object Point) { // Declare a new point Point3d newPoint = new Point3d(Point3d.Unset); // Set "new_pt" coordinates newPoint.X = x; newPoint.Y = y; newPoint.Z = z; // Assign the point coordinates to output Point = newPoint; } ``` Static properties get or set a generic data of that type. The above example uses the static **Point3d** method **Unset** to unset the new point. Another example that is commonly used is the **origin** property in **Point3d** to get the origin of the coordinate system, which is (0,0,0) as in the following example. ```C# private void RunScript( ref object Origin) { Origin = Point3d.Origin; } ``` #### Point3d Methods The methods are used to help inquire about the data or perform specific calculations, comparisons, etc. For example, **Point3d** has a method to measure the distance to another point. All methods are listed in the documentation with full details about the parameters and the return value, and sometimes an example to show how they are used. In the documentation, **Parameters** refer to the input passed to the method and the **Return Value** describes the data returned to the caller. Notice that all methods can also be navigated through the left menu. Here is how the **DistanceTo** method is used in an example. ```C# private void RunScript( double x, double y, double z, Point3d other, ref object Point, ref object Distance) { // Create an instance of a point and initialize to x, y, and z Point3d pt = new Point3d(x, y, z); // Calculate the distance from "pt" to the "other" input point double dis = pt.DistanceTo(other); // Assign the point "point" to the A output Point = pt; // Assign the distance to the B output Distance = dis; } ``` The **DistanceTo** method does not change the fields (the X, Y, and Z). However, other methods such as **Transform** change the fields. The **Transform** method resets the X, Y, and Z values to reflect the new location after applying the transform. Here is an example that uses **Point3d.Transform**. ```C# private void RunScript(Point3d pt, Transform xform, ref object Point) { // Create an instance of a point and initialize to input point Point3d newPoint = new Point3d(pt); // Transform the point newPoint.Transform(xform); // Assign the point "new_pt" to the A output Point = newPoint; } ``` #### Point3d Static Methods Point3d has **static** methods that are accessible without instantiating an instance of **Point3d**. For example, if you would like to check if a list of given instances of points are all coplanar, you can call the static method **Point3d.ArePointsCoplanar** without creating an instance of a point. Static methods have the little red “s” symbol in front of them in the documentation. The **ArePointsCoplanar** has the following syntax, parameters, and the return value. Here is an example that uses the static method **Point3d.ArePointsCoplanar**. ```C# private void RunScript(List pts, double tol, ref object IsCoplanar) { // Test if the list of input points are coplanar bool coplanar = Point3d.ArePointsCoplanar(pts, tol); // Assign the co-planar test to output IsCoplanar = coplanar; } ``` #### Point3d Operators Many Structures & Classes in RhinoCommon implement operators whenever relevant. Operators enable you to use the “+” to add two points or use the “=” to assign the coordinates of one point to the other. **Point3d** structure implements many operators, and this simplifies the coding & its readability. Although it is fairly intuitive in most cases, you should check the documentation to verify which operators are implemented and what they return. For example, the documentation of adding 2 **Point3d** indicates the result is a new instance of **Point3d** where X, Y, and Z are calculated by adding corresponding fields of 2 input **Point3d**. Note that all operators are declared **public** and **static**. Here is an example that shows how the “+” operator in **Point3d** is used. Note that the “+” returns a new instance of a **Point3d**. ```C# private void RunScript(Point3d pt1, Point3d pt2, ref object Point) { // Add 2 points and assign result to output Point = pt1 + pt2; } ``` #### Point3d as a Function Parameter **Point3d** is a value type because it is a structure. That means if you pass an instance of **Point3d** to a function and change its value inside that function, the original value outside the function will not be changed, as in the following example: ```C# private void RunScript(double x, double y, double z, Point3d pt2, ref object Point) { Point3d pt = new Point3d(x, y, z); Print("Before calling ChangeX function: pt.x=" + pt.X); // Call a function to change the value of X in the point ChangeX(pt); Print("After ChangeX: pt.x=" + pt.X); Point = pt; } public void ChangeX(Point3d pt) { pt.X = 100; Print("Inside ChangeX: pt.x=" + pt.X); } ``` ### 3.2.2 Points & Vectors **RhinoCommon** has a few structures to store and manipulate points and vectors. Take for example the double precision points. There are three types of points that are commonly used listed in the table below. For more detailed explanation of vectors and points, please refer to the *[Essential Mathematics for Computational Design](https://www.rhino3d.com/download/rhino/6/essentialmathematics)*, a publication by McNeel.
Class Name Member Variables Notes
Point2d X as Double
Y as Double
Used for parameter space points
Point3d X as Double
Y as Double
Most commonly used to represent points in three dimensional coordinate space
Point4d X as Double
Y as Double
Z as Double
W as Double
Used for grips or control points. Grips have weight information in addition to the 3D location.
As for vectors, there are two main types:
Class Name Member Variables Notes
Vector2d X as Double
Y as Double
Used in two dimensional space
Vector3d X as Double
Y as Double
Z as Double
Used in three dimensional space
The following are a few point and vector operations with examples of output. The script starts with declaring and initializing a few values, then applying some operations:
Notation Syntax result
Create a new instance of a point and vector ```C# Point3d p0 = new Point3d(3, 6, 0); Vector3d v0 = new Vector3d(4, 1.5, 0); double factor = 0.5; ```
Move a point by a vector ```C# Point3d p1 = new Point3d(Point3d.Unset); p1 = p0 + v0; ```
Distance between 2 points ```C# double distance = p0.DistanceTo(p1); ```
Point subtraction (create vector between two points) ```C# Vector3d v1 = new Vector3d(Vector3d.Unset); v1 = Point3d.Origin - p0; ```
Vector addition (create average vector) ```C# Vector3d addVectors = new Vector3d(Vector3d.Unset); addVectors = v0 + v1; ```
Vector subtraction ```C# Vector3d subtractVectors = v0 - v1; ```
Vector dot product (if result is positive number, then vectors are in the same direction) ```C# double dot = v0 * v1; /*--- For example if v0 = <10, 0, 0> v1 = <0, 10, 0> then dot = 0 */ ```
Vector cross product (result is a vector normal to the 2 input vectors) ```C# Vector3d cross = new Vector3d(Vector3d.Unset); cross = Vector3d.CrossProduct(v0, v1); ```
Scale a vector ```C# Vector3d scaleV0 = new Vector3d(Vector3d.Unset); scaleV0 = factor * v0; ```
Get vector length ```C# double v0Length = v0.Length; ```
Use vector operations to get the angle between 2 vectors ```C# // Unitize the input vectors v0.Unitize(); v1.Unitize(); double dot = v0 * v1; // Force the dot product of the two input vectors to // fall within the domain for inverse cosine, which // is -1 <= x <= 1. This will prevent runtime // "domain error" math exceptions. if ((dot < -1.0)) dot = -1.0; if ((dot > 1.0)) dot = 1.0; double angle = System.Math.Acos(dot); ``` ```C# v0 = <10, 0, 0> v1 = <0, 10, 0> dot = 0 angle = acos(0) = 90 degrees ```
### 3.2.3 Lightweight Curves **RhinoCommon** defines basic types of curves such as lines & circles as structures, and hence, most of them are value types. The mathematical representation is easier to understand and is typically more lightweight. If needed, it is relatively easy to get the NURBS approximation of these curves using the method **ToNurbsCurve**. The following is a list of the lightweight curves:
Lightweight Curves Types
Line Line between two points
Polyline Polyline connecting a list of points (not value type)
Arc Arc on a plane from center, radius, start, and end angles
Circle Circle on a plane from center point and radius
Ellipse Defined by a plane and 2 radii
The following shows how to create instances of different lightweight curve objects:
Notation Syntax Result
Declare and initialize 3 new Points ```C# Point3d p0 = new Point3d(0, 0, 0); Point3d p1 = new Point3d(5, 1, 0); Point3d p2 = new Point3d(6, -3, 0); ```
Create an instance of a Line ```C# // Create an instance of a lightweight Line Line line = new Line(p0, p1); ```
Distance between 2 Points ```C# double distance = p0.DistanceTo(p1); ```
Create an instance of a Arc ```C# // Create an instance of a lightweight Arc Arc arc = new Arc(p0, p1, p2); ```
Create an instance of a Polyline ```C# // Put the 3 Points in a list Point3d[ ] pointList = {p0, p1, p2}; // Create an instance of a lightweight Polyline Polyline polyline = new Polyline(pointList); ```
Create an instance of a Circle ```C# double radius = 3.5; // Create an instance of a lightweight Circle Circle circle = new Circle(p0, radius); ```
Create an list of instances of an Ellipse ```C# double angle = 0.01; // angle in radian int ellipseCount = 20; // number of ellipses double x = 1.5; // shift along X-axis // Declare a new list of ellipse curve type List ellipseListist = new List(); // Use a loop to create a number of ellipse curves Plane plane = Plane.WorldXY; for (Int i = 1; i <= ellipseCount; i++) { Point3d pt = new Point3d(x, 0, 0); Vector3d y = new Vector3d(0,1,0); plane.Rotate(angle * i, y , pt); // Declare and instantiate a new ellipse Ellipse ellipse = new Ellipse(plane, (double) i / 2, i); // Add the ellipse to the list ellipseList.Add(ellipse); } ```
### 3.2.4 Lightweight Surfaces Just like with curves, **RhinoCommon** defines a number of lightweight surfaces that are defined as structures. They can be converted to NURBS surfaces using the **ToNurbsSurface()** method. They include common surfaces such as cones & spheres. Here is a list of them:
Lightweight Surface Types
Sphere Line between two points
Cylinder Polyline connecting a list of points (not value type)
Cone Arc on a plane from center, radius, start, and end angles
Torus Circle on a plane from center point and radius
#### The following shows how to create instances of different lightweight surface objects:
Notation Syntax result
Create an instance of a Sphere ```C# Point3d center = Point3d.Origin; double radius = 7.5; Sphere sphere = new Sphere(center, radius); ```
Create an instance of a Cylinder ```C# double height = 7.5; double radius = 2.5; Point3d center = Point3d.Origin; Circle baseCircle = new Circle(center, radius); Cylinder cy = new Cylinder(baseCircle, height); ```
Create an instance of a Cone ```C# double distance = p0.DistanceTo(p1); ```
Create an instance of a Arc ```C# double height = 7.5; double radius = 7.5; Cone cone = new Cone(Plane.WorldXY, height, radius); ```
Create an instance of a Polyline ```C# // Put the 3 Points in a list Point3d[ ] pointList = {p0, p1, p2}; // Create an instance of a lightweight Polyline Polyline polyline = new Polyline(pointList); ```
Create an instance of a Torus ```C# double minorRadius = 2.5; double majorRadius = 7.5; Torus torus = new Torus(Plane.WorldXY, majorRadius, minorRadius); ```
### 3.2.5 Other Geometry Structures Now that we have explained the **Point3d** structure in some depth, and some of the lightweight geometry structures, you should be able to review & use the rest using the **RhinoCommon** documentation. As a wrap up, the following example uses eight different structures defined in the **Rhino.Geometry** namespace. Those are **Plane**, **Point3d**, **Interval**, **Arc**, **Vector3d**, **Line**, **Sphere**, and **Cylinder**. The goal is to create the following composition: #### Create an instance of a circle on the xy-plane, center (2,1,0), and a random radius between 10 and 20 ```C# // Generate a random radius of a circle Random rand = new Random(); double radius = rand.Next(10, 20); // Create xy_plane using Plane static method WorldXY Plane plane = Plane.WorldXY; // Set plane origin to (2,1,0) Point3d center = new Point3d(2, 1, 0); plane.Origin = center; // Create a circle from plane and radius Circle circle = new Circle(plane, radius); ``` #### Create an instance of an arc from the circle and angle interval between 0 and Pi ```C# // Create an arc from an input circle and interval Interval angleInterval = new Interval(0, Math.PI); Arc arc = new Arc(circle, angleInterval); ``` #### Extract the end points of the arc, and create a vertical lines with length = 10 units ```C# // Extract end points Point3d startPoint = arc.StartPoint; Point3d endPoint = arc.EndPoint; // Create a vertical vector Vector3d vec = Vector3d.ZAxis; // Use the multiplication operation to scale the vector by 10 vec = vec * 10; // Create start & end lines Line line1 = new Line(startPoint, vec); Line line2 = new Line(endPoint, vec); ``` #### Create a cylinder around the start line with radius = line height/4, and crearte a sphere around the second line centered in the middle with radius = line height/4 ```C# // Create a cylinder at line1 with radius = 1/4 the length double height = line1.Length; double radius = height / 4; Circle circle = new Circle(line1.From, radius); Cylinder cylinder = new Cylinder(c_circle, height); // Create a sphere at the center of line2 with radius = ¼ the length Point3d sphereCenter = line2.PointAt(0.5); Sphere sphere = new Sphere(sphereCenter, radius); ``` ## 3.3 Geometry Classes Just like structures, classes enable defining custom types by grouping other types together, along with some custom methods & events. A class is like a blueprint that encapsulates the data and the behavior of the user-defined type. But, unlike structures, classes allow **inheritance** which enables defining a hierarchy of types that starts with a generic type and branches into more specific types. For example, the **Curve** class in RhinoCommon branches into specialized curve types such as **ArcCurve** and **NurbsCurve**. The following diagram shows the hierarchy of the **Curve** class: Most geometry classes are derived from the **GeometryBase** class. The following diagram shows the hierarchy of these classes: You can instantiate an instance of most of the classes above. However, there are some classes that you cannot instantiate. Those are usually higher in the hierarchy, such as the **GeometryBase**, **Curve**, and **Surface**. Those are called **abstract** classes. * **Abstract Classes**: The **GeometryBase** in **RhinoCommon** is one example of an abstract class. You cannot create an object or instantiate an instance of an abstract class. The purpose is to define common data and functionality that all derived classes can share. * **Base Classes**: refer to parent classes that define common functionality for the classes that are derived from them. The **Curve** class is an example of a base class that also happens to be an abstract (cannot instantiate an object from it). Base classes do not have to be abstract though. ```C# private void RunScript(ref object A) { // ERROR: attempt to create an instance of the abstract "Curve" class Rhino.Geometry.Curve crv = new Rhino.Geometry.Curve(); } ``` * **Derived Classes**: inherit the members of a class they are derived from and add their own specific functionality and implementation. The **NurbsCurve** is an example of a derived class from the **Curve** class. The **NurbsCurve** can use all members in **Curve** class methods. The same is true for all other classes derived from **Curve**, such as **ArcCurve** & **PolyCurve**. The following example shows how to create a new instance of the **PolylineCurve** class: ```C# private void RunScript(List pts, int degree, ref object Crv, ref object Type) { // Declare & create a new instance of a polyline curve from points var crv = new Rhino.Geometry.PolylineCurve(pts); // Assign curve to A output Crv = crv; // Assign curve type to B output Type = crv.GetType(); } ``` The most common way to create an instance of a class is to use the new keyword when declaring the object: ```C# private void RunScript( Point3d p0, Point3d p1, ref object Line ) { // Create an instance of a point object & unset var lineCurve = new Rhino.Geometry.LineCurve(p0, p1); Print(lineCurve.ToString()); Line = lineCurve } ``` There is another common way to create instances of classes. That is to use special methods in some classes to help create and initialize a new instance of an object. For example, the **Curve** class has **static** methods to create a variety of curve types, as in the following example. Notice that you do not need to use **new** in this case: ```C# private void RunScript(List points, int degree, ref object A, ref object B) { // Declare a curve variable Rhino.Geometry.Curve inter_crv = default(Rhino.Geometry.Curve); // Create new instance of a curve from interpolate points inter_crv = Rhino.Geometry.Curve.CreateInterpolatedCurve(points, degree); // Declare a curve variable Rhino.Geometry.Curve nurbs_crv = default(Rhino.Geometry.Curve); // Create new instance of a curve from control points nurbs_crv = Rhino.Geometry.Curve.CreateControlPointCurve(points, degree); // Assign output A = inter_crv; B = nurbs_crv; } ``` The following sections summarize the different ways to create new instances of objects, which apply to both class & structure types: #### Use the Class Constructor Need to use the new keyword. For example, the following creates a line from two points: ```C# Rhino.Geometry.LineCurve lc = new Rhino.Geometry.LineCurve(p0, p1); ``` Note that each class may have a number of constructors that include different sets of parameters. For example the **LineCurve** class has the following constructors: Many times, there are “protected” constructors. Those are used internally by the class and you cannot use them to create a new instance of the object with them. They are basically locked. The LineCurve class has one marked in the image above. #### Use the Class Static Create Methods Some classes include a **Create** method to generate a new instance of the class. Here is an example: ```C# Rhino.Geometry.NurbsCurve nc = NurbsCurve.Create(isPeriodic, degree, controlPoints); ``` You can find these methods in the **RhinoCommon** help when you navigate the class “members”. Here are different ways to create a **NurbsCurve** for example and how they appear in the help: #### Use the Static Create Methods of the Parent Class There are times when the parent class has “Create” methods that can be used to instantiate an instance of the derived class. For example, the Curve class has few static methods that a derived class like NurbsCurve can use as in the example: ```C# Rhino.Geometry.Curve crv= Curve.CreateControlPointCurve(controlPoints, degree); Rhino.Geometry.NurbsCurve nc = crv as Rhino.Geometry.NurbsCurve; ``` For the full set of the **Curve Create** methods, check the **RhinoCommon** documentation. Here is an example of a few of them: #### Use the Return Value of a Function Class methods return values and sometimes those are new instances of objects. For example, the Offset method in the Curve class returns a new array of curves that is the result of the offset. ```C# Curve[ ] offsetCurves = x.Offset( Plane.WorldXY, 1.4, 0.01, CurveOffsetCornerStyle.None ); ``` Once you create an instance of a class or a structure, you will be able to see all class methods and properties through the auto-complete feature. When you start filling the method parameters, the auto-complete will show you which parameter you are at and its type. This is a great way to navigate all available methods for each class and be reminded of what parameters are needed. Here is an example from a **Point3d** structure. Note that you don’t always get access to all the methods via the auto-complete. For the complete list of properties, operations, and methods of each class, you should use the **RhinoCommon** help file.
Figure(23): Auto-complete help navigate the type methods
Classes and structures define properties to set & retrieve data. Each property has either a **Get** or a **Set** method, or both. The following example shows how to get and set the coordinates of a **Point3d** object: ```C# private void RunScript(double degree, ref object A) { // Create an instance of a point object and initialize to origin (0,0,0) Point3d pt = new Point3d(0, 0, 0); // Get coordinates and print to "output" Print("X = " + pt.X); // Set the x coordinate to a new value from input pt.X = x; // Print the new x value Print("X = " + pt.X); // Get the pt "x" value and assign to output A = pt.X; } ``` Copying data from an existing class to a new one can be done a few different ways depending on what the class supports. The following example shows how to copy data between two points using three different ways. It also shows how to use the **DuplicateCurve** method of the **Curve** class to create a new identical instance of a **Curve**: ```C# private void RunScript(Point3d pt, Curve crv, ref object newCrv) { // Different ways to copy data between objects // Use the constructor when you instantiate an instance Of the Point3d class Point3d newPt1 = new Point3d(pt); Print("new pt1 = " + newPt1.ToString()); // Use the “= Operator” If the Class provides one Point3d newPt2 = new Point3d(Point3d.Unset); newPt2 = pt; Print("new pt2 = " + newPt2.ToString()); // Copy the properties one by one Point3d newPt3 = new Point3d(Point3d.Unset); newPt3.X = pt.X; newPt3.Y = pt.Y; newPt3.Z = pt.Z; Print("new pt3 = " + newPt3.ToString()); // Some geometry classes provide a “Duplicate” method that is very efficient to use newCrv = crv.DuplicateCurve(); } ``` ### 3.3.1 Curves The **RhinoCommon SDK** has the abstract **Rhino.Geometry.Curve** class that provides a rich set of functionality across all curves. There are many classes derived from the parent **Curve** class, and we will learn about how to create and manipulate them. The following is a list of the classes derived from the **Curve** class:
Curve Types Notes
ArcCurve Used to create arcs & circles
LineCurve Used to create lines
NurbsCurve Used to create freeform curves
PolyCurves A curve that has multiple segments joined together
PolylineCurve A curve that has multiple lines joined together
CurveProxy Cannot instantiate an instance of it. Both BrepEdge & BrepTrim types are derived from the CurveProxy class.
#### Create Curve Objects One way to create a curve object is to use the create methods available as **static** methods in the parent **Rhino.Geometry.Curve** class. Here is an example: Create an instance of a **NurbsCurve** from control points and degree ```C# Curve nc = Rhino.Geometry.Curve.CreateControlPointCurve(points, degree); ``` Create an array of tween curves using two input curves and a the number of tween curves ```C# // Declare a variable of type Curve Curve[ ] tweenCurves = null; // Create an array of tween curves tweenCurves = Rhino.Geometry.Curve.CreateTweenCurves(curve0, curve1, count, 0.01); ``` Another way to create new curves is to use the constructor of the curve with the **new** keyword. The following are examples to create different types of curves using the constructor or the **Create** method in the class. You can reference the **RhinoCommon** help for more details about the constructors of each one of the derived curve classes. The following example uses 3 new declared points: ```C# Point3d p0 = new Point3d(0, 0, 0); Point3d p1 = new Point3d(5, 1, 0); Point3d p2 = new Point3d(6, -3, 0); ``` Create an instance of a **LineCurve** using the class constructor and **new** keyword: ```C# // Create an instance of an LineCurve LineCurve line = new LineCurve(p0, p1); ``` Create an instance of a **ArcCurve** using the class constructor and **new** keyword: ```C# // Create an instance of a lightweight Arc to pass to the // constructor of the ArcCurve class Arc arc = new Arc(p0, p1, p2); // Create a new instance of ArcCurve ArcCurve arcCurve = new ArcCurve(arc); ``` Create an instance of a **PolylineCurve** using the class constructor and **new** keyword ```C# // Put the 3 points in a list Point3d[ ] pointList = {p0, p1, p2}; // Create an instance of an PolylineCurve PolylineCurve polyline = new PolylineCurve(pointList); ``` Create one open and one closed (periodic) curve using the **Create** function of the **NurbsCurve** class ```C# bool isPeriodic = false; int degree = 3; NurbsCurve openCurve = NurbsCurve.Create(isPeriodic , degree, pointList); isPeriodic = true; NurbsCurve periodicCurve = NurbsCurve.Create(isPeriodic , degree, pointList); ``` Curves can also be the return value of a method. For example, offsetting a given curve creates one or more new curves. Also the surface **IsoCurve** method returns an instance of a curve. Extract isocurve from a surface. A new instance of a curve is the return value of a method: ```C# // srf = input surface, t = input parameter var uIso = srf.IsoCurve(0, t); var vIso = srf.IsoCurve(1, t); ``` Or, create multiple offsets of a curve: ```C# private void RunScript(Curve crv, int num, double dis, double tol, Plane plane) { // Declare the list of curve List crvs = new List(); Curve lastCurve = crv; for (int i = 1; i <= num; i++) { Curve[ ] curveArray = last_crv.Offset(plane, dis, tol, CurveOffsetCornerStyle.None); // Ignore if output is multiple offset curves if (crv.IsValid && curveArray.Count() == 1) { // Append offset curve to array crvs.Add(curveArray[0]); // Update the next curve to offset lastCUrve = curveArray[0]; } else break; } } ``` #### Curve Methods Each class can define methods that help navigate the data of the class and extract some relevant information. For example, you might want to find the endpoints of a curve, find the tangent at some point, get a list of control points, or divide the curve. **AutoComplete** helps to quickly navigate and access these methods, but you can also find the full description in the **RhinoCommon** help. Keep in mind that a derived class such as **NurbsCurve** can access not only its own methods but also the methods of the classes it was derived from. Therefore, an instance of a **NurbsCurve** can access the **NurbsCurve** methods and the **Curve** methods as well. The methods that are defined under the **Curve** are available to all of the classes derived from the **Curve** class, such as **LineCurve**, **ArcCurve**, **NurbsCurve**, etc. Here are a few examples of curve methods:
NurbsCurve methods
```C# // Get the domain or interval of the curve Interval domain = crv.Domain; ```
```C# // Get the start & end points of a curve Point3d startPoint = crv.PointAtStart; Point3d endPoint = crv.PointAtEnd; ```
```C# // Get the tangent at start of a curve Vector3d startTangent = crv.TangentAtStart; ```
```C# // Get the control points of a NurbsCurve (nc) List cpList = new List(); int count = nc.Points.Count; // Loop to get all cv points for (int i = 0; i <= count - 1; i++) { ControlPoint cp = nc.Points[i]; cpList.Add(cp.Location); } ```
```C# // Get the knot list of a NurbsCurve (nc) List knotList = new List(); int count = nc.Points.Count; // Loop to get all knots values for (int i = 0; i <= count - 1; i++) { double knot = nc.Knots[i]; knotList.Add(knot); } ```
```C# // Divide curve (crv) by number (num) // Declare an array of points Point3d[ ] points = { }; // Divide the curve by number crv.DivideByCount(num, true, out points); ```
### 3.3.2 Surfaces There are many surface classes derived from the abstract **Rhino.Geometry.Surface** class. The **Surface** class provides common functionality among all of the derived types. The following is a list of the surface classes and a summary description:
Surface Types Notes
Extrusion Represents surfaces from extrusion. It is much lighter than a NurbsSurface.
NurbsSurface Used to create free form surfaces
PlaneSurface Used to create planar surfaces
RevSurface Represents a surface of revolution
SumSurface Represents a sum surface or an extrusion of a curve along a curved path
SurfaceProxy Cannot instantiate an instance of it. Provides a base class to brep faces and other surface proxies
#### Create Surface Objects One way to create surfaces is by using the static methods in the **Rhino.Geometry.Surface** class that start with the keyword **Create**. Here are some of these create methods:
Create methods in Rhino.Geometry.Surface class
CreateExtrusion Constructs a surface by extruding a curve along a vector
CreateExtrusionToPoint Constructs a surface by extruding a curve to a point
CreatePeriodicSurface Constructs a periodic surface from a base surface & a direction
CreateRollingBallFillet Constructs a rolling ball fillet between two surfaces
CreateSoftEditSurface Creates a soft-edited surface from an existing surface using a smooth field of influence
The following example creates a fillet surfaces between 2 input surfaces given some radius & tolerance: ```C# private void RunScript(Surface srfA, Surface srfB, double radius, double tol, ref object A) { // Declare an array of surfaces Surface[ ] surfaces = {}; // Check for a valid input if (srfA != null && srfB != null) { // Create fillet surfaces surfaces = Surface.CreateRollingBallFillet(srfA, srfB, radius, tol); } } ``` However, the most common way to create a new instance of a derived surface type is to either use the constructor (with **new** keyword) or the **Create** method of the derived surface class. Here are a couple of examples that show how to create instances from different surface types:
Notation Syntax result
Create an instance of a **PlaneSurface** using the constructor & **new** keyword ```C# var plane = Plane.WorldXY; var x_interval = new Interval(1.0, 3.5); var y_interval = new Interval(2.0, 6.0); // Create planar surface var planeSrf = new PlaneSurface(plane, x_interval, y_interval); ```
Create an instance of a **RevSurface** from a line & a profile curve ```C# RevCurve revCrv = … // from input Line revAxis = … // from input // Create surface of revolution var revSrf = RevSurface.Create(revCrv, revAxis); ```
Create an instance of a **NurbsSurface** from a list of control points ```C# List points = … // from input // Create nurbs surface from control points NurbsSurface ns = null; ns = NurbsSurface.CreateThroughPoints(points, 2, 2, 1, 1, false, false); ```
Create an instance of a **NurbsSurface** from extruding a curve in certain direction ```C# Curve cv = … // from input Vector3d dir = … // from input // Create a nurbs surface from extruding a curve in some dir var nSrf = NurbsSurface.CreateExtrusion(crv, dir); // Create an extrusion from extruding a curve in some dir var ex = Extrusion.Create(crv, 10, false); // Note that in Grasshopper 1.0 there is no support for extrusion objects, and hence, the output within GH is converted to a NURBS surface. The only way to bake an Extrusion instance is to add the object to the active document from within the scripting component, as in the following: Rhino.RhinoDoc.ActiveDoc.Objects.AddExtrusion(ex); ```
#### Surface Methods Surface methods help edit & extract information about the surface object. For example, you might want to learn if the surface is closed or if it is planar. You might need to evaluate the surface at some parameter to calculate points on the surface or get its bounding box. There are also methods to extract a lightweight geometry out of the surface. They start with the “Try” keyword. For example, you might have a RevSurface that is actually a portion of a torus. In that case, you can call TryGetTorus. All these methods and many more can be accessed through the surface methods. A full list of these methods is documented in the **RhinoCommon SDK** documentation.
Examples of the **Surface** & **NurbsSurface** methods
```C# Surface srf = … // from input // Check if the input surface is closed in the u or v direction bool isClosedU = srf.IsClosed(0); bool isClosedV = srf.IsClosed(1); // Check if the surface is planar at zero tolerance bool isPlanar = srf.IsPlanar(); ```
```C# Surface srf = … // from input double u = 0.5; double v = 0.5; // Declare & instantiate the evaluation point and derivative vectors Point3d evalPt = new Point3d(Point3d.Unset); Vector3d[ ] derivatives = {}; // Evaluate the surface and extract the first derivative to get tangents srf.Evaluate(u, v, 1, out evalPt, out derivatives); Vector3d tanU = derivatives[0]; Vector3d tanV = derivatives[1]; ```
```C# // Declare the list of surfaces List srfs = new List(); Surface lastSrf = srf; for (int i = 1; i <= num; i++) {t Surface offset_srf = last_srf.Offset(dis, tol); if (srf.IsValid) { // append offset surface to array srfs.Add(offset_srf); // update the next curve to offset lastSrf = offset_srf; } else break; } ```
```C# // Declare & instantiate an axis and a curve Line axis = new Line (Point3d.Origin, new Point3d(0, 0, 10)); LineCurve crv = new LineCurve(new Point3d(2, 0, 0), new Point3d(3.5, 0, 5)); // Create surface of revolution RevSurface revSrf = RevSurface.Create(crv, axis); // Try to get a Cone Cone cone; if( revSrf.TryGetCone(out cone)) Print(“Cone was successfully create from surface”); ```
### 3.3.3 Meshes Meshes represent a geometry class that is defined by faces & vertices. The mesh data structure basically includes a list of vertex locations, faces that describe vertices connections, and normal of vertices & faces. More specifically, the geometry lists of a mesh class include the following:
Mesh geometry Description
Vertices Of type MeshVertexList - includes a list of vertex locations type Point3f
Normals Of type MeshVertexNormalList - includes a list of normals of type Vector3f
Faces Of type MeshFaceList - includes a list of normals of type MeshFace
FaceNormals Of type “MeshFaceNormalList” - includes a list of normals of type Vector3f
#### Create Mesh Objects You can create a mesh from scratch by specifying vertex locations & faces and compute the normal as in the following examples:
Examples of the **Mesh** methods
```C# // Create a simple rectangular mesh that has 1 face // Create a new instance of a mesh Rhino.Geometry.Mesh mesh = new Rhino.Geometry.Mesh(); // Add mesh vertices mesh.Vertices.Add(0.0, 0.0, 0.0); // 0 mesh.Vertices.Add(5.0, 0.0, 0.0); // 1 mesh.Vertices.Add(5.0, 5.0, 0.0); // 2 mesh.Vertices.Add(0.0, 5.0, 0.0); // 3 // Add mesh faces mesh.Faces.AddFace(0, 1, 2, 3); // Compute mesh normals mesh.Normals.ComputeNormals(); // Generate any additional mesh data mesh.Compact(); ```
```C# // Create simple triangular mesh that has 2 faces // Create a new instance of a mesh Rhino.Geometry.Mesh mesh = new Rhino.Geometry.Mesh(); // Add mesh vertices mesh.Vertices.Add(0.0, 0.0, 0.0); // 0 mesh.Vertices.Add(5.0, 0.0, 2.0); // 1 mesh.Vertices.Add(5.0, 5.0, 0.0); // 2 mesh.Vertices.Add(0.0, 5.0, 2.0); // 3 // Add mesh faces mesh.Faces.AddFace(0, 1, 2); mesh.Faces.AddFace(0, 2, 3); // Compute mesh normals mesh.Normals.ComputeNormals(); // Generate any additional mesh data mesh.Compact(); ```
The Mesh class includes many **CreateFrom** static methods to create a new mesh from various other geometry. Here is the list with description as it appears in the **RhinoCommon** help: Here are a couple examples to show how to create a mesh from a brep & a closed polyline:
**Mesh** Samples
```C# // Set mesh parameters to default var mp = MeshingParameters.Default; // Create meshes from brep var newMesh = Mesh.CreateFromBrep(brep, mp); ```
```C# // Create a new mesh from polyline var newMesh = Mesh.CreateFromClosedPolyline(pline); ```
#### Navigate Mesh Geometry & Topology You can navigate mesh data using **Vertices** & **Faces** properties. The list of vertices & faces are stored in a collection class or type with added functionality to manage the list efficiently. **Vertices** list, for example, is of type **MeshVertexList**. So if you need to change the locations of mesh vertices, then you need to get a copy of each vertex, change the coordinates, then reassign in the vertices list. You can also use the Set methods inside the MeshVertexList class to change vertex locations. The following example shows how to randomly change the **Z** coordinate of a mesh using two different approaches. Notice that mesh vertices use a **Point3f** and not **Point3d** type because mesh vertex locations are stored as a single precision floating point.
Change the location of mesh vertices by assigning the new value to the Vertices’ list
```C# // Change mesh vertex location int index = 0; Random rand = new Random(); foreach (Point3f loc in mesh.Vertices) { Point3f newLoc = loc; newLoc.Z = rand.Next(10) / 3; mesh.Vertices[index] = newLoc; index = index + 1; } ```
Change the location of mesh vertices using the **SetVertix** method
```C# Mesh mesh = … // from input // Create a new instance of random generator Random rand = new Random(); for (int i = 0; i <= mesh.Vertices.Count - 1; i++){ // Get vertex Point3f loc = mesh.Vertices[i]; loc.Z = rand.Next(10) / 3; // Assign new location mesh.Vertices.SetVertex(i, loc); } ```
Here is an example that deletes a mesh vertex and all surrounding faces:
Delete mesh Vertices randomly
```C# Mesh mesh = … // from input int index = 32; // Remove a mesh vertex (make sure index falls within range) if (index >= 0 & i < mesh.Vertices.Count) { mesh.Vertices.Remove(index, true); } ```
You can also manage the **Faces** list of a mesh. Here is an example that deletes about half the faces randomly from a given mesh:
Delete mesh faces randomly
```C# List faceIndices = new List(); int count = mesh.Faces.Count; // Create a new instance of random generator Random rand = new Random(); for (int i = 0; i <= count - 1; i += 2){ int index = rand.Next(count); faceIndices.Add(index); } // Delete faces mesh.Faces.DeleteFaces(distinctIndices); ```
Meshes keep track of the connectivity of the different parts of the mesh. If you need to navigate related faces, edges, or vertices, then this is done using the mesh topology. The following example shows how to extract the outline of a mesh using the mesh topology:
Mesh topology example: extract the outline of a mesh
```C# // Get the mesh's topology Rhino.Geometry.Collections.MeshTopologyEdgeList meshEdges = mesh.TopologyEdges; List lines = new List(); // Find all of the mesh edges that have only a single mesh face for (int i = 0; i <= meshEdges.Count - 1; i++) { int numOfFaces = meshEdges.GetConnectedFaces(i).Length; if ((numOfFaces == 1)) { Line line = meshEdges.EdgeLine(i); lines.Add(line); } } ```
#### Mesh Methods Once a new mesh object is created, you can edit & extract data out of that mesh object. The following example extracts naked edges out of some input mesh:
Extract the outline of a mesh
```C# Mesh mesh = … // from input // Declare an array of polylines Polyline[ ] naked_edges = { }; // Create a new mesh from polyline nakedEdges = mesh.GetNakedEdges(); ```
Test if a given point is inside a closed mesh
```C# Mesh mesh = … // from input Point3d pt = … // from input // Test if the point is inside the mesh mesh.IsPointInside(pt, tolerance, true); ```
Delete every other mesh face, then split disjoint meshes
```C# Mesh mesh = … // from input List faceIndeces = new List(); // Loop through faces & delete every other face for (int i = 0; i <= mesh.Faces.Count - 1; i += 2) { faceIndeces.Add(i); } // Delete faces mesh.Faces.DeleteFaces(faceIndeces); // Split disjoint meshes Mesh[ ] meshArray = mesh.SplitDisjointPieces(); ```
### 3.3.4 Boundary Representation (Brep) The boundary representation is used to unambiguously represent trimmed nurbs surfaces and polysurfaces. There are two sets of data that are needed to fully describe the 3D objects using the boundary representation. Those are geometry & topology. #### Brep Geometry Three geometry elements are used to create any Breps: 1. The 3D untrimmed nurbs surfaces in the modeling space 2. The 3D curves, which are the geometry of edges in modeling space 3. The 2D curves, which are the geometry of trims in the parameter space
Figure(24): Geometry elements of a typical trimmed nurbs surface. (1) Trimmed surface with control points turned on (2) Underlying 3-D untrimmed surface (3) 3D curves (for the edges) in modeling space (4) 2D curves (for the trims) in parameter space
#### Brep Topology A topology refers to how different parts of the 3D object are connected. For example, we might have two adjacent faces with one of their edges aligned. There are two possibilities to describe the relationship or connectivity between these two faces. They could either be two separate entities that can be pulled apart, or they are joined in one object sharing that edge. The topology is the part that describes such relationships.
Figure(25): Topology describes connectivity between geometry elements. Two adjacent faces can either be joined together in one polysurface or are two separate faces that can be pulled apart.
Brep topology includes faces, edges, vertices, trims, and loops. The following diagram lists the Brep topology elements and their relation to geometry in the context of **RhinoCommon**:
Figure(26): The Brep topology elements and their relation to geometry
The topology elements of the Brep can be defined as follows:
Topology Referenced Geometry Description
Vertices 3D points
(location in 3D space)
They describe the corners of the brep. Each vertex has a 3D location. They are located at the ends of edges and are shared between neighboring edges.
Edges 3D Nurbs curves
(location in 3D space)
Edges describe the bounds of the brep. Each references the 3D curve, two end vertices, and the list of trims. If an edge has more than one trim, that means the edge is shared among more than one face. Multiple edges can reference the same 3D curve (typically a different portion of it).
Faces 3D underlying NURBS surfaces
(location in 3D space)
Faces reference the 3D surface and at least one outer loop. A face normal direction might not be the same as that of the underlying surface normal. Multiple faces can reference the same 3D surface (typically a different portion of it).
Loops 2D closed curves of connected trims
(in 2D parameter space)
Each face has exactly one outer loop defining the outer boundary of the face. A face can also have inner loops (holes). Each loop contains a list of trims.
Trims 2D Curves
(in 2D parameter space)
Each trim references one 2D curve & exactly one edge, except in singular trims, there is no edge. The 2D curves of the trims run in a consistent direction. Trims of outer or boundary of the face run counter-clockwise regardless of the 3D curve direction of its edge. The 2D curves of the trims of the inner loops run clockwise.
Each brep includes lists of geometry & topology elements. Topology elements point to each other and the geometry they reference, which makes it easy to navigate through the brep data structure. The following diagram shows navigation paths of the brep topology & geometry elements:
Figure(27): Navigation diagram of the Brep data structure in RhinoCommon.
The topology of the Brep and navigating different parts: ```C# Brep brep = … // from input // BrepFace topology (3D modeling space) Rhino.Geometry.Collections.BrepFaceList faces = brep.Faces; for(int fi = 0; fi < faces.Count; fi++) { BrepFace face = faces[fi]; // Get Adjacent faces var aFaces = face.AdjacentFaces(); // Get Adjacent edges var aEdges = face.AdjacentEdges(); // Get face loops var faceLoops = face.Loops; // Get the 3D untrimmed surface var face3dSurface = face.UnderlyingSurface(); } // BrepLoop topology (2D parameter space) Rhino.Geometry.Collections.BrepLoopList loops = brep.Loops; for(int li = 0; li < loops.Count; li++) { BrepLoop loop = loops[li]; // Get loop face var loopFace = loop.Face; // Get loop trims var loopTrims = loop.Trims; // Get loop 2D & 3D curves var loop2dCurve = loop.To2dCurve(); var loop3dCurve = loop.To3dCurve(); } // BrepEdge topology (3D modeling space) Rhino.Geometry.Collections.BrepEdgeList edges = brep.Edges; for(int ei = 0; ei < edges.Count; ei++) { BrepEdge edge = edges[ei]; // Get edge faces var eFaces_i = edge.AdjacentFaces(); // Get edge start & end vertices var eStartVertex = edge.StartVertex; var eEndVertex = edge.EndVertex; // Get edge trim indices var eTrimIndeces = edge.TrimIndices(); // Get edge 3D curve var e3dCurve = edge.EdgeCurve; } // BrepTrim topology (2D parameter space) Rhino.Geometry.Collections.BrepTrimList trims = brep.Trims; for(int ti = 0; ti < trims.Count; ti++) { BrepTrim trim = trims[ti]; // Get the edge var trimEdge = trim.Edge; // Get trim start & end vertices var trimStartVertex = trim.StartVertex; var trimEndVertex = trim.EndVertex; // Get trim loop var trimLoop = trim.Loop; // Get trim face var trimFace = trim.Face; // Get trim 2D curve var trim2dCurve = trim.TrimCurve; } // BrepVertex topology (3D modeling space) Rhino.Geometry.Collections.BrepVertexList vertices = brep.Vertices; for(int vi = 0; vi < vertices.Count; vi++) { BrepVertex vertex = vertices[vi]; // Get vertex edges var vEdges = vertex.EdgeIndices(); // Get vertex location var vPoint = vertex.Location; } ``` In polysurfaces (which are **Breps** with multiple faces), some geometry and topology elements are shared along the connecting curves & vertices where polysurface faces join together. In the following example, the two faces F0 & F1 share one edge E0 and two vertices V0 & V2.
Figure(28): Vertices, edges and 3-D curves are shared between neighboring faces. Edge (E0) and Vertices (V0 & V2) are shared between the two faces (F0 & F1).
As illustrated before, **Breps** has a rather complex data structure, and it is useful to learn how to navigate this data. The following examples show how to get some of the geometry parts: Example to count geometry & topology elements of a Brep and then extract the 3D geometry ```C# Brep brep = … // from input // Print the number of geometry elements Print("brep.Vertices.Count = {0}", brep.Vertices.Count); Print("brep.Curves3D.Count = {0}", brep.Curves3D.Count); Print("brep.Curves2D.Count = {0}", brep.Curves2D.Count); Print("brep.Surfaces.Count = {0}", brep.Surfaces.Count); // Print the number of topology elements Print("brep.Trims.Count = {0}", brep.Trims.Count); Print("brep.Loops.Count = {0}", brep.Loops.Count); Print("brep.Faces.Count = {0}", brep.Faces.Count); Print("brep.Edges.Count = {0}", brep.Edges.Count); // Extract 3d geometry elements var V = brep.Vertices; var C = brep.Curves3D; var S = brep.Surfaces; ``` Example to extract the outline of a Brep ignoring all holes ```C# Brep brep = … // from input // Declare outline list of curves List outline = new List(); // Check all the loops and extract naked that are not inner foreach (BrepLoop eLoop in brep.Loops) { // Make sure the loop type is outer if (eLoop.LoopType == BrepLoopType.Outer) { // Navigate through the trims of the loop foreach (BrepTrim trim in eLoop.Trims) { // Get the edge of each trim BrepEdge edge = trim.Edge; // Check if the edge has only one trim if (edge.TrimCount == 1) { // Add a copy of the edge curve to the list outline.Add(edge.DuplicateCurve()); } } } } ``` Example to extract all the faces of a **Brep** box and move them away from the center: ```C# Brep brep = … // from input // Declare a new list of faces List faces = new List(); // Find the center Point3d center = brep.GetBoundingBox(true).Center; for (int i = 0; i <= brep.Faces.Count - 1; i++) { // Extract the faces int[ ] iList = { i }; Brep face = brep.DuplicateSubBrep(iList); // Find face center Point3d faceCenter = face.GetBoundingBox(true).Center; // Find moving direction Vector3d dir = new Vector3d(); dir = faceCenter - center; // Move the face and add to the list face.Translate(dir); faces.Add(face); } ``` #### Create Brep Objects The **Brep** class has many **Create** methods. For example, if you need to create a twisted box, then you can use the **CreateFromBox** method in the **Brep** class as in the following. You can also create a surface out of boundary curves or create a solid out of bounding surfaces. Create a **Brep** from corners ```C# List corners = … // from input // Create the brep from corners Brep twistedBox = Brep.CreateFromBox(corners); ``` Create a **Brep** from bounding edge curves ```C# List crvs = … // from input // Build the brep from edges Brep edgeBrep = Brep.CreateEdgeSurface(crvs); ``` Create a **Brep** from bounding surfaces ```C# List breps = … // from input double tol = … // from input // Build the brep from corners Brep[ ] solids = Brep.CreateSolid(breps, tol); ``` #### Brep Methods Here is a list of some of the commonly used create methods found under the **Brep** class. For the full list and details about each method, please refer to the **RhinoCommon SDK** help. The **Brep** methods serve multiple functions. Some are to extract information such as finding out if the brep is solid (closed polysurface), others perform calculations in relation to the brep such as area, volume or find a point inside a solid. Some methods change the brep, such as cap holes or merge coplanar faces. Methods that operate on multiple instances of breps include joining or performing some boolean operations. The following are examples to show the range of **Brep** methods: Example methods to extract **Brep** information: Find if a brep is valid & is a solid ```C# Brep[] breps = … // from input List isSolid = new List(); foreach( Brep brep in breps) if( brep.IsValid ) isSolid.Add(brep.IsSolid); ``` Example to calculate a brep area, volume, and centroid ```C# Brep brep = … // from input // Declare variable double area = 0; double volume = 0; Point3d vc = default(Point3d); // Create a new instance of the mass properties classes VolumeMassProperties volumeMp = VolumeMassProperties.Compute(brep); AreaMassProperties areaMp = AreaMassProperties.Compute(brep); // Calculate area, volume, and centroid area = brep.GetArea(); // or use: area = areaMp.Area volume = brep.GetVolume(); // or use: volume = volumeMp.Volume centroid = volumeMp.Centroid; ``` Example methods that change a brep topology: merge coplanar faces in a brep ```C# Brep brep = … // from input double tol = 0.01; bool merged = brep.MergeCoplanarFaces(tol); ``` Example methods that operate on multiple breps: Boolean union 2 breps ```C# List breps = … // from input // Boolean union the input breps Brep[ ] unionBrep = Brep.CreateBooleanUnion(breps, tol); ``` ### 3.3.5 Other Geometry Classes There are many important classes under the **Rhino.Geometry** namespace that are not derived from **GeometryBase**. Those are commonly used when writing scripts to create & manipulate geometry. We used a couple of them in the examples. You can find the full list of **RhinoCommon** classes in the documentation. Most of these classes are derived directly from the **C#** abstract class **System.Object**. This is the inheritance hierarchy for these classes: One important cluster of classes in this list has to do with extracting 2D outlines of the geometry, (**Make2D**). These classes start with **HiddenLineDrawing** keyword. The following diagram shows the sequence of creating the drawing and which classes are used in each step:
Figure(29): Workflow & classes used to extract 2D drawing of the geometry
The first step involves setting the parameters such as the source geometry, view, and all other options. Once the parameters are set, the HLD core class can calculate all the lines and hand back to the HLD Segment class with all visibility information & associated source. The user can then use this information to create the final drawing. #### Create 2D drawing from 3D geometry (Make2D) ```C# List geomList = … // from input List cplaneList = … // from input // Use the active view in the Rhino document var _view = doc.Views.ActiveView; // Set Make2D Parameters var _hldParams = new HiddenLineDrawingParameters { AbsoluteTolerance = doc.ModelAbsoluteTolerance, IncludeTangentEdges = false, IncludeHiddenCurves = true }; _hldParams.SetViewport(_view.ActiveViewport); // Add objects to hld_param foreach (var geom in geomList){ _hldParams.AddGeometry(geom, Transform.Identity, null); } // Add clipping planes foreach (var cplane in cplaneList ){ _hldParams.AddClippingPlane(cplane); } // Perform HLD calculation var visibleList = new List(); var hiddenList = new List(); var secVisibleList = new List(); var secHiddenList = new List(); var _hld = HiddenLineDrawing.Compute(_hldParams, true); if (_hld != null) { // Transform var flatten = Transform.PlanarProjection(Plane.WorldXY); BoundingBox pageBox = _hld.BoundingBox(true); var delta2D = new Vector2d(0, 0); delta2D = delta2D - new Vector2d(pageBox.Min.X, pageBox.Min.Y); var delta3D = Transform.Translation(new Vector3d(delta2D.X, delta2D.Y, 0.0)); flatten = delta3D * flatten; // Add curves to lists foreach (HiddenLineDrawingSegment hldCurve in _hld.Segments) { if (hldCurve == null || hldCurve.ParentCurve == null || hldCurve.ParentCurve.SilhouetteType == SilhouetteType.None) continue; var crv = hldCurve.CurveGeometry.DuplicateCurve(); if (crv != null) { crv.Transform(flatten); if (hldCurve.SegmentVisibility == HiddenLineDrawingSegment.Visibility.Visible) { if (hldCurve.ParentCurve.SilhouetteType == SilhouetteType.SectionCut) secVisibleList.Add(crv); else visibleList.Add(crv); } else if (hldCurve.SegmentVisibility == HiddenLineDrawingSegment.Visibility.Hidden) { if (hldCurve.ParentCurve.SilhouetteType == SilhouetteType.SectionCut) secHiddenList.Add(crv); else hiddenList.Add(crv); } } } } ``` ## 3.4 Geometry Transformations All classes derived from **GeometryBase** inherit four transformation methods. The first three are probably the most commonly used which are **Rotate**, **Scale**, and **Translate**. But, there is also a generic **Transform** method that takes a **Transform** structure and can be set to any transformation matrix. The following example shows how to use the scale, rotate, and translate methods on a list of geometry objects. Use different transformation methods (Scale, Rotate, and Translate) ```C# List objs = … // from input // Create a new list of geometry objects List newObjs = new List(); foreach (GeometryBase obj in objs) { // Scale, rotate, and move obj.Scale(factor); obj.Rotate(angle, Vector3d.YAxis, Point3d.Origin); obj.Translate(dir); // Add to list newObjs.Add(obj); } ``` The **Transform** structure is a 4x4 matrix with several methods to help create geometry transformations. This includes defining a shear, projection, rotation, mirror, and others. The structure also has functions that support operations such as multiplication & transpose. For more information about the mathematics of transformations, please refer to *“The Essential Mathematics for Computational Design”*. The following examples show how to create a shear transform & planar projection: Create shear transformation and output the matrix ```C# Brep brep = … // from input // Create a shear transform Plane p = Plane.WorldXY; var v = new Vector3d(0.5, 0.5, 0); var y = Vector3d.YAxis; var z = Vector3d.ZAxis; var xform = Transform.Shear(p, v, y, z); // Shear the Brep brep.Transform(xform); ``` Create planar projection transform to project curves ```C# List objs = … // from input // Create a new list of geometry objects List newObjs = new List(); foreach (GeometryBase obj in objs) { // Create a project transform obj.Transform(Transform.PlanarProjection(Plane.WorldXY)); // Add to list newObjs.Add(obj); } ``` ## Next Steps That was a basic overview of RhinoCommon Geometry in Rhino. Now learn to use these methods in [Design Algorithms](/guides/grasshopper/csharp-essentials/4-design-algorithms/) to get something done. This is part 3 of the [Essential C# Scripting for Grasshopper guide](/guides/grasshopper/csharp-essentials/). -------------------------------------------------------------------------------- # Chapter 4: Design Algorithms Source: https://developer.rhino3d.com/en/guides/grasshopper/csharp-essentials/4-design-algorithms/ ## 4.1 Introduction In this chapter we will implement a few examples to create mathematical curves & surfaces and solve a few generative algorithms using C# in Grasshopper. Many of the examples involve using loops & recursions, which are not supported in regular Grasshopper components. ## 4.2 Geometry Algorithms It is relatively easy to create curves & surfaces that follow certain mathematical equations when you use scripting. You can generate control points to create smooth NURBS or interpolate points to create the geometry. ### 4.2.1 Sine Curves & Surface The following example shows how to create NurbsCurves, NurbsSurfaces, and a lofted Brep using the sine of an angle: Create curves & surface using the sine equation ```C# private void RunScript(int num, ref object OutCurves, ref object OutSurface, ref object OutLoft) { // List of all points List allPoints = new List(); // List of curves List curves = new List(); for(int y = 0; y < num; y++) { // Curve points List crvPoints= new List(); for(int x = 0; x < num; x++) { double z = Math.Sin(Math.PI / 180 + (x + y)); Point3d pt = new Point3d(x, y, z); crvPoints.Add(pt); allPoints.Add(pt); } // Create a degree 3 nurbs curve from control points NurbsCurve crv = Curve.CreateControlPointCurve(crvPoints, 3); curves.Add(crv); } // Create a nurbs surface from control points NurbsSurface srf = NurbsSurface.CreateFromPoints(allPoints, num, num, 3, 3); // Create a lofted brep from curves Brep[ ] breps = Brep.CreateFromLoft(curves, Point3d.Unset, Point3d.Unset, LoftType.Tight, false); // Assign output OutCurves = curves; OutSurface = srf; OutLoft = breps; } ``` ### 4.2.2 De Casteljau Algorithm to Interpolate a Bezier Curve You can create a cubic Bezier curve from four input points. The De Casteljau algorithm is used in computer graphics to evaluate the Bezier curve at any parameter. If evaluated at multiple parameters, then the points can be connected to draw the curve. The following example shows a recursive function implementation to interpolate through a Bezier curve: The De Casteljau algorithm to draw a Bezier curve with 2, 4, 8, and 16 segments ```C# private void RunScript(Point3d pt0, Point3d pt1, Point3d pt2, Point3d pt3, int segments, ref object BezierCrv) { if(segments < 2) segments = 2; List bezierPts = new List(); bezierPts.Add(pt0); bezierPts.Add(pt1); bezierPts.Add(pt2); bezierPts.Add(pt3); List evalPts = new List(); double step = 1 / (double) segments; for(int i = 0; i <= segments; i++) { double t = i * step; Point3d pt = Point3d.Unset; EvalPoint(bezierPts, t, ref pt); if(pt.IsValid) evalPts.Add(pt); } Polyline pline = new Polyline(evalPts); BezierCrv = pline; } void EvalPoint(List points, double t, ref Point3d evalPt) { // Stopping condition - point at parameter t is found if(points.Count < 2) return; List tPoints = new List(); for(int i = 1; i < points.Count; i++) { Line line = new Line(points[i - 1], points[i]); Point3d pt = line.PointAt(t); tPoints.Add(pt); } if(tPoints.Count == 1) evalPt = tPoints[0]; EvalPoint(tPoints, t, ref evalPt); } ``` ### 4.2.3 Simple Subdivision Mesh The following example takes a surface & closed polyline, then creates a subdivision mesh. It pulls the midpoints of the polyline edges to the surface to then subdivide & pull again: ```C# private void RunScript(Surface srf, List inPolylines, int degree, ref object OutPolylines, ref object OutMesh) { // Instantiate the collection of all panels List outPanels = new List(); // Limit to 6 subdivisions if( degree > 6) degree = 6; for(int i = 0; i < degree; i++) { // Outer polylines List plines = new List(); // Mid polylines List midPlines = new List(); // Generate subdivided panels bool result = SubPanelOnSurface(srf, inPolylines, ref plines, ref midPlines); if( result == false) break; // Add outer panels outPanels.AddRange(plines); // Add mid panels only in the last iteration if(i == degree - 1) outPanels.AddRange(midPlines); else // Subdivide mid panels only inPolylines = midPlines; } // Create a mesh from all polylines Mesh joinedMesh = new Mesh(); for(int i = 0; i < outPanels.Count; i++) { Mesh mesh = Mesh.CreateFromClosedPolyline(outPanels[i]); joinedMesh.Append(mesh); } // Make sure all mesh faces normals are in the same general direction joinedMesh.UnifyNormals(); // Assign output OutPolylines = outPanels; OutMesh = joinedMesh; bool SubPanelOnSurface( Surface srf, List inputPanels, ref List outPanels, ref List midPanels) { // Check for a valid input if (inputPanels.Count == 0 || null == srf) return false; for (int i = 0; i < inputPanels.Count; i++) { Polyline ipline = inputPanels[i]; if (!ipline.IsValid || !ipline.IsClosed) continue; // Stack of points List stack = new List(); Polyline newPline = new Polyline(); for (int j = 1; j < ipline.Count; j++) { Line line = new Line(ipline[j - 1], ipline[j]); if (line.IsValid) { Point3d mid = line.PointAt(0.5); double s, t; srf.ClosestPoint(mid, out s, out t); mid = srf.PointAt(s, t); newPline.Add(mid); stack.Add(ipline[j - 1]); stack.Add(mid); } } // Add the first 2 point to close last triangle stack.Add(stack[0]); stack.Add(stack[1]); // Close newPline.Add(newPline[0]); midPanels.Add(newPline); for (int j = 2; j < stack.Count; j = j + 1) { Polyline pl = new Polyline { stack[j - 2], stack[j - 1], stack[j], stack[j - 2] }; outPanels.Add(pl); } } return true; } ``` ## 4.3 Generative Algorithms Most of the generative algorithms require recursive functions that are only possible through scripting in Grasshopper. The following are four examples of generative solutions to generate the dragon curve, fractals, penrose tiling, and game of life: ### 4.3.1 Dragon Curve
```C# private void RunScript(string startString, string ruleX, string ruleY, int Num, double Length, ref object DragonCurve) { // Declare string string dragonString = startString; // Generate the string GrowString(ref Num, ref dragonString , ruleX, ruleY); // Generate the points List dragonPoints = new List();; ParseDeagonString(dragonString, Length, ref dragonPoints); // Create the curve PolylineCurve dragonCrv= new PolylineCurve(dragonPoints); // Assign output DragonCurve = dragonCrv; } void GrowString(ref int Num, ref string finalString, string ruleX, string ruleY) { // Decrement the count with each new execution of the grow function Num = Num - 1; char rule; // Create new string string newString = ""; for (int i = 0; i < finalString.Length ; i++) { rule = finalString[i]; if (rule == 'X') newString = newString + ruleX; if (rule == 'Y') newString = newString + ruleY; if (rule == 'F' | rule == '+' | rule == '-') newString = newString + rule; } finalString = newString; // Stopper condition if (Num == 0) return; // Grow again GrowString(ref Num, ref finalString, ruleX, ruleY); } void ParseDeagonString(string dragonString, double Length, ref List dragonPoints) { // Parse instruction string to generate points // Let base point be world origin Point3d pt = Point3d.Origin; dragonPoints .Add(pt); // Drawing direction vector - strat along the x-axis // Vector direction will be rotated depending on (+,-) instructions Vector3d V = new Vector3d(1.0, 0.0, 0.0); char rule; for(int i = 0 ; i < dragonString.Length;i++) { // Always start for 1 & length 1 to get one char at a time rule = DragonString[i]; // Move forward using direction vector if( rule == 'F') { pt = pt + (V * Length); dragonPoints.Add(pt); } // Rotate Left if( rule == '+') V.Rotate(Math.PI / 2, Vector3d.ZAxis); // Rotate Right if( rule == '-') V.Rotate(-Math.PI / 2, Vector3d.ZAxis); } } ``` ### 4.3.2 Fractal Tree
```C# private void RunScript(string startString, string ruleX, string ruleY, int num, double length, ref object FractalLines) { // Declare string string fractalString = startString; // Denerate the string GrowString(ref num, ref dragonString , ruleX, ruleY); // Generate the points List fractalLines = new List();; ParsefractalString(fractalString, length, ref fractalLines ); // Assign output FractalLines = fractalLines ; } void GrowString(ref int num, ref string finalString, string ruleX, string ruleF) { // Decrement the count with each new execution of the grow function num = num - 1; char rule; // Create new string string newString = ""; for (int i = 0; i < finalString.Length ; i++) { rule = finalString[i]; if (rule == 'X') newString = newString + ruleX; if (rule == 'F') newString = newString + ruleF; if (rule == '[' || rule == ']' || rule == '+' || rule == '-') newString = newString + rule; } finalString = newString; // Stopper condition if (num == 0) return; // Grow again GrowString(ref num, ref finalString, ruleX, ruleF); } void ParsefractalString(string fractalString, double length, ref List fractalLines) { // Parse instruction string to generate points // Let base point be world origin Point3d pt = Point3d.Origin; // Declare points array // Vector rotates with (+,-) instructions by 30 degrees List arrPoints = new List(); // Draw forward direction // Vector direction will be rotated depending on (+,-) instructions Vector3d vec = new Vector3d(0.0, 1.0, 0.0); // Stacks of points and vectors List ptStack = new List(); List vStack = new List(); // Declare loop variables char rule; for(int i = 0 ; i < fractalString.Length; i++) { // Always start for 1 & length 1 to get one char at a time rule = fractalString[i]; // Rotate Left if( rule == '+') vec.Rotate(Math.PI / 6, Vector3d.ZAxis); // Rotate Right if( rule == '-') vec.Rotate(-Math.PI / 6, Vector3d.ZAxis); // Draw Forward by direction if( rule == 'F') { // Add current points Point3d newPt1 = new Point3d(pt); arrPoints.Add(newPt1); // Calculate next point Point3d newPt2 = new Point3d(pt); newPt2 = newPt2 + (vec * length); // Add next point arrPoints.Add(newPt2); // Save new location pt = newPt2; } // Save point location if( rule == '[') { // Save current point & direction Point3d newPt = new Point3d(pt); ptStack.Add(newPt); Vector3d newV = new Vector3d(vec); vStack.Add(newV); } // Retrieve point & direction if( rule == ']') { pt = ptStack[ptStack.Count - 1]; vec = vStack[vStack.Count - 1]; // Remove from stack ptStack.RemoveAt(ptStack.Count - 1); vStack.RemoveAt(vStack.Count - 1); } } // Generate lines List allLines = new List(); for(int i = 1; i < arrPoints.Count; i = i + 2) { Line line = new Line(arrPoints[i - 1], arrPoints[i]); allLines.Add(line); } } ``` ### 4.3.3 Penrose Tiling
```C# private void RunScript(string startString, string rule6, string rule7, string rule8, string rule9, int num, ref object PenroseString) { // Declare string string finalString; finalString = startString; // Generate the string GrowString(ref num, ref finalString, rule6, rule7, rule8, rule9); // Return the string PenroseString = finalString; } void GrowString(ref int num, ref string finalString, string rule6, string rule7, string rule8, string rule9) { // Decrement the count with each new execution of the grow function num = num - 1; char rule; // Create new string string newString = ""; for (int i = 0; i < finalString.Length; i++) { rule = finalString[i]; if (rule == '6') newString = newString + rule6; if (rule == '7') newString = newString + rule7; if (rule == '8') newString = newString + rule8; if (rule == '9') newString = newString + rule9; if (rule == '[' || rule == ']' || rule == '+' || rule == '-') newString = newString + rule; } finalString = newString; // Stopper condition if (num == 0) return; // Grow again GrowString(ref num, ref finalString, rule6, rule7, rule8, rule9); } private void RunScript(string penroseString, double length, ref object PenroseLines) { // Parse instruction string to generate points // Let base point be world origin Point3d pt = Point3d.Origin; // Declare points array // Vector rotates with (+,-) instructions by 36 degrees List arrPoints = new List(); // Draw forward direction // Vector direction will be rotated depending on (+,-) instructions Vector3d vec = new Vector3d(1.0, 0.0, 0.0); // Stacks of points & vectors List ptStack = new List(); List vStack = new List(); // Declare loop variables char rule; for(int i = 0 ; i < penroseString.Length; i++) { // Always start for 1 & length 1 to get one char at a time rule = penroseString[i]; // Rotate Left if( rule == '+') vec.Rotate(36 * (Math.PI / 180), Vector3d.ZAxis); // Rotate Right if( rule == '-') vec.Rotate(-36 * (Math.PI / 180), Vector3d.ZAxis); // Draw Forward by direction if( rule == '1') { // Add current points Point3d newPt1 = new Point3d(pt); arrPoints.Add(newPt1); // Calculate next point Point3d newPt2 = pt + (vec * length); // Add next point arrPoints.Add(newPt2); // Save new location pt = newPt2; } // Save point location if( rule == '[') { // Save current point & direction Point3d newPt = new Point3d(pt); ptStack.Add(newPt); Vector3d newVec = new Vector3d(vec); vStack.Add(newVec); } // Retrieve point & direction if( rule == ']') { pt = ptStack[ptStack.Count - 1]; vec = vStack[vStack.Count - 1]; // Remove from stack ptStack.RemoveAt(ptStack.Count - 1); vStack.RemoveAt(vStack.Count - 1); } } // Generate lines List allLines = new List(); for(int i = 1; i < arrPoints.Count; i = i + 2) { Line line = new Line(arrPoints[i - 1], arrPoints[i]); allLines.Add(line); } PenroseLines = allLines; } ``` ### 4.3.4 Conway Game of Life A cellular automaton consists of a regular grid of cells, each in one of a finite number of states, "On" & "Off" for example. The grid can be in any finite number of dimensions. For each cell, a set of cells called its neighborhood (usually including the cell itself) is defined relative to the specified cell. For example, the neighborhood of a cell might be defined as the set of cells a distance of 2 or less from the cell. An initial state (time t=0) is selected by assigning a state for each cell. A new generation is created (advancing t by 1), according to some fixed rule (generally, a mathematical function) that determines the new state of each cell in terms of the current state of the cell and the states of the cells in its neighborhood. *Check wikipedia for full details and examples.* ```C# private void RunScript(Surface srf, int uNum, int vNum, int seed, ref object PointGrid, ref object StateGrid) { if(uNum < 2) uNum = 2; if(vNum < 2) vNum = 2; double uStep = srf.Domain(0).Length / uNum; double vStep = srf.Domain(1).Length / vNum; double uMin = srf.Domain(0).Min; double vMin = srf.Domain(1).Min; // Create a grid of points & a grid of states DataTree pointsTree = new DataTree(); DataTree statesTree = new DataTree(); int pathIndex = 0; Random rand = new Random(seed); for (int i = 0; i <= uNum; i++) { List ptList = new List(); List stateList = new List(); GH_Path path = new GH_Path(pathIndex); pathIndex = pathIndex + 1; for (int j = 0; j <= vNum; j++) { Point3d srfPt = srf.PointAt(uMin + i * uStep, vMin + j * vStep); ptList.Add(srfPt); int randState = rand.Next(0, 2); stateList.Add(randState); } pointsTree.AddRange(ptList, path); statesTree.AddRange(stateList, path); } PointGrid = pointsTree; StateGrid = statesTree; } private void RunScript(DataTree grid, int gen, ref object OutGrid) { // Get state at the defined generation for(int i = 0; i < gen; i++) grid = NewGeneration(grid); OutGrid = grid; } public DataTree NewGeneration(DataTree inStates) { int i, j, c, nc; List prvBranch; List nxtBranch; List branch; DataTree states = new DataTree(); states = inStates; for (i = 0; i <= states.Branches.Count - 1; i++) { branch = states.Branches[i]; for (j = 0; j <= branch.Count - 1; j++) { c = branch[j]; nc = 0; // Check neighbouring states // Next nc = nc + branch[(j + 1 + branch.Count) % branch.Count]; // Prev nc = nc + branch[(j - 1 + branch.Count) % branch.Count]; // Top nxtBranch = states.Branches[(i + 1 + states.Branches.Count) % states.Branches.Count]; nc = nc + nxtBranch[(j + 1 + nxtBranch.Count) % nxtBranch.Count]; nc = nc + nxtBranch[(j + nxtBranch.Count) % nxtBranch.Count]; nc = nc + nxtBranch[(j - 1 + nxtBranch.Count) % nxtBranch.Count]; // Bottom prvBranch = states.Branches[(i - 1 + states.Branches.Count) % states.Branches.Count]; nc = nc + prvBranch[(j + 1 + prvBranch.Count) % prvBranch.Count]; nc = nc + prvBranch[(j + prvBranch.Count) % prvBranch.Count]; nc = nc + prvBranch[(j - 1 + prvBranch.Count) % prvBranch.Count]; // Set the new state if (c == 1) { if (nc < 2 | nc > 3) c = 0; } else if (c == 0) { if (nc == 3) c = 1; } branch[j] = c; } } return states; } ``` ## End of Guide This is part 4 of the [Essential C# Scripting for Grasshopper guide](/guides/grasshopper/csharp-essentials/). -------------------------------------------------------------------------------- # Custom Getpoint Source: https://developer.rhino3d.com/en/samples/rhinopython/custom-get-point/ A Rhino GetPoint that performs some custom dynamic drawing. ```python # A Rhino GetPoint that performs some custom dynamic drawing import Rhino import System.Drawing import scriptcontext def CustomArc3Point(): # Color to use when drawing dynamic lines line_color = System.Drawing.Color.FromArgb(255,0,0) arc_color = System.Drawing.Color.FromArgb(150,0,50) rc, pt_start = Rhino.Input.RhinoGet.GetPoint("Start point of arc", False) if( rc!=Rhino.Commands.Result.Success ): return rc, pt_end = Rhino.Input.RhinoGet.GetPoint("End point of arc", False) if( rc!=Rhino.Commands.Result.Success ): return # This is a function that is called whenever the GetPoint's # DynamicDraw event occurs def GetPointDynamicDrawFunc( sender, args ): #draw a line from the first picked point to the current mouse point args.Display.DrawLine(pt_start, args.CurrentPoint, line_color, 2) #draw a line from the second picked point to the current mouse point args.Display.DrawLine(pt_end, args.CurrentPoint, line_color, 2) #draw an arc through these three points arc = Rhino.Geometry.Arc(pt_start, args.CurrentPoint, pt_end) args.Display.DrawArc(arc, arc_color, 1) # Create an instance of a GetPoint class and add a delegate # for the DynamicDraw event gp = Rhino.Input.Custom.GetPoint() gp.DynamicDraw += GetPointDynamicDrawFunc gp.Get() if( gp.CommandResult() == Rhino.Commands.Result.Success ): pt = gp.Point() arc = Rhino.Geometry.Arc(pt_start,pt,pt_end) scriptcontext.doc.Objects.AddArc(arc) scriptcontext.doc.Views.Redraw() if( __name__ == "__main__" ): CustomArc3Point() ``` -------------------------------------------------------------------------------- # How to get user input in a script Source: https://developer.rhino3d.com/en/guides/rhinopython/python-user-input/ How to prompt the user for input into a script. ## Overview Prompting the user of a script for the input of a value, selecting a layer, picking a point or selecting a Rhino object is important to many interactive scripts. Many input methods will also validate the user input to make sure only the proper input is accepted. The RhinoscriptSyntax module contains many ways to interactively prompt for several different types of input. There are three main styles of input that are contained in Rhinosciptsyntax: - [**Get methods**](#the-get-methods). These are methods that work with the command line, wait for mouse input or prompt for specific input. - [**Special Dialogs**](#special-dialogs). There are some simple specific dialogs to prompt for input - [**File system dialogs**](#file-system-dialogs). Browsing, saving and opening files on the system with Python. - [**Eto Custom Dialog Framework**](#eto-custom-dialog-framework). A cross-platform dialog framework to create more custom modal and non-modal dialog box classes that can be used in scripts. ## The GET methods Get are basic ways to prompt for specifc user feedback on the commandline and mouse input. Good examples of a Get might be get a point, a string, or a distance. There are 25 get methods that can 1. **Commandline Gets** - [GetReal](/api/RhinoScriptSyntax/#userinterface-GetReal), [GetString](/api/RhinoScriptSyntax/#userinterface-GetString), [GetInteger](/api/RhinoScriptSyntax/#userinterface-GetInteger), [GetBoolean](/api/RhinoScriptSyntax/#userinterface-GetBoolean) 2. **Interactive Gets** - [GetPoint](/api/RhinoScriptSyntax/#userinterface-GetPoint)([s](/api/RhinoScriptSyntax/#userinterface-GetPoints)), [GetPointCoordinates](/api/RhinoScriptSyntax/#userinterface-GetPointCoordinates), [GetLine](/api/RhinoScriptSyntax/#userinterface-GetLine), [GetDistance](/api/RhinoScriptSyntax/#userinterface-GetDistance), [GetAngle](/api/RhinoScriptSyntax/#userinterface-GetAngle), [GetPolyline](/api/RhinoScriptSyntax/#userinterface-GetPolyline), [GetRectangle](/api/RhinoScriptSyntax/#userinterface-GetRectangle), [GetBox](/api/RhinoScriptSyntax/#userinterface-GetBox), [GetCursorPos](/api/RhinoScriptSyntax/#userinterface-GetCursorPos). 3. **Geometry Gets** - [GetObject](/api/RhinoScriptSyntax/#userinterface-GetObject), [GetCurveObject](/api/RhinoScriptSyntax/#userinterface-GetCurveObject), [GetSurfaceObject](/api/RhinoScriptSyntax/#userinterface-GetSurfaceObject), [GetEdgeCurves](/api/RhinoScriptSyntax/#userinterface-GetEdgeCurves), [GetMeshFaces](/api/RhinoScriptSyntax/#userinterface-GetMeshFaces), [GetMeshVertices](/api/RhinoScriptSyntax/#userinterface-GetMeshVertices), [GetPointOnCurve](/api/RhinoScriptSyntax/#userinterface-GetPointOnCurve), [GetPointOnMesh](/api/RhinoScriptSyntax/#userinterface-GetPointOnMesh), [GetPointOnSurface](/api/RhinoScriptSyntax/#userinterface-GetPointOnSurface). ### Command Line gets The simplest Get function is to ask for a specific value on the command line. For instance a Get method may prompting for a *number* on the command line with `rs.GetReal()`. #### Getreal() ```python import rhinoscriptsyntax as rs # GetReal prompts on the command line with optional defaults and a minimum allowable value radius = rs.GetReal("Radius of new circle", 3.14, 1.0) if radius: rs.AddCircle( (0,0,0), radius ) ``` rs.GetReal() accepts any number, including decimals. In some cases your code may need only whole numbers- in this case use `rs.GetInteger()` The other command line gets are [GetString](/api/RhinoScriptSyntax/#userinterface-GetString), [GetInteger](/api/RhinoScriptSyntax/#userinterface-GetInteger), and [GetBoolean](/api/RhinoScriptSyntax/#userinterface-GetBoolean). They all work essentially the same as [GetReal](/api/RhinoScriptSyntax/#userinterface-GetReal). ### Interactive gets #### GetPoint() Some Get functions will prompt on the commandline and also allow for input from the mouse. A typical interactive get is rs.GetPoint()` to ask the user for a single point location. AS an example, let's say you would like to prompt for the center of a circle. The default prompt of GetPoint is "Pick point", but you can specify a different prompt, for example, "Set center point" depending on what you wish to convey to the user. ```python pt = rs.GetPoint("Set center point") ``` If the function succeeds, a Rhino point is returned, which can be treated as a list of three numbers representing the world x, y and z coordinates of the point. ```python import rhinoscriptsyntax as rs pt = rs.GetPoint("Click to get information about a point location") if pt is not None:# note it is a good idea to check if there is a result you can use print "That point has an x coordinate of " + str(pt[0]) # when you build a string that includes elements that are not text, convert to a string with str() ``` #### GetPoints() Use `rs.GetPoints()` to ask the user for multiple point locations. As in rs.GetPoint(), all parameters are optional. Note that there is a separate prompt for the first point, and a second one for subsequent points. You need to set the parameters in order, separated by commas. If you do not want to specify a parameter at all, and accept the default, you can leave it out but you must then specify any following parameters explicitly using the parameter name. For example, this will not work to set a custom first prompt: ```python import rhinoscriptsyntax as rs pts = rs.GetPoints( "Set the first point", "Set the next point") ``` Why? because the function has two parameters that come before the first prompt, 'draw_lines' and 'in_plane'. If you leave these out, you must specify what parameters you are setting explicitly in order for it to be recognized: ```python import rhinoscriptsyntax as rs pts = rs.GetPoints( message1= "Set the first point", message2= "Set the next point") ``` You could also make sure to set the other parameters even if you don't care what they are i.e. defaults are OK: ```python import rhinoscriptsyntax as rs pts = rs.GetPoints( None, None, "Set the first point", "Set the next point") ``` Many of the interactive get functions also allow a first point to be placed. This way a rubber band line is drawn from the point to the cusor, emulating a drawn line. ```python import rhinoscriptsyntax as rs point1 = rs.GetPoint("Pick first point") #By adding the first point to this prompt, a line is interactively drawn. point2 = rs.GetPoint("Pick second point", point1) ``` The interactive gets have more options on how the input is filtered from the mouse. For details on all the Get functions in RhinoScriptSyntax for Python go to the [RhinoScriptSyntax User interface methods](/api/RhinoScriptSyntax/#userinterface). Additional interactive gets include: [GetLine](/api/RhinoScriptSyntax/#userinterface-GetLine), [GetDistance](/api/RhinoScriptSyntax/#userinterface-GetDistance), [GetAngle](/api/RhinoScriptSyntax/#userinterface-GetAngle), [GetPolyline](/api/RhinoScriptSyntax/#userinterface-GetPolyline), [GetRectangle](/api/RhinoScriptSyntax/#userinterface-GetRectangle), [GetBox](/api/RhinoScriptSyntax/#userinterface-GetBox), [GetCursorPos](/api/RhinoScriptSyntax/#userinterface-GetCursorPos). ### Geometry Gets Geometry gets are used to pick geometry off the screen or related geometry off an existing objects. Be aware the Gemetry gets can pass very different values back from the functions. So, take special note on what is being returned by these functions. In addition to object identifiers there may be additional information on what was selected and how it was selected. #### GetObject() The basic get for an Object in the scene is the `GetObject()` function. GetObject will return the *Guid* of the object selected. Guids are identifiers of existing geometry items that can be used in other RhinoScriptSyntax functions to identify the objects. ```python import rhinoscriptsyntax as rs objectId = rs.GetObject("Pick any object") if objectId: print objectID ``` Like other Geometry gets, there are optional arguments to filter out unwanted selection by type. The example below will only allow curves and surfaces to be selected. ```python objectId = rs.GetObject("Pick a curve or surface", rs.filter.curve | rs.filter.surface) if objectId: print objectID ``` Additional geometry gets are: [GetCurveObject](/api/RhinoScriptSyntax/#userinterface-GetCurveObject), [GetSurfaceObject](/api/RhinoScriptSyntax/#userinterface-GetSurfaceObject), [GetEdgeCurves](/api/RhinoScriptSyntax/#userinterface-GetEdgeCurves), [GetMeshFaces](/api/RhinoScriptSyntax/#userinterface-GetMeshFaces), [GetMeshVertices](/api/RhinoScriptSyntax/#userinterface-GetMeshVertices), [GetPointOnCurve](/api/RhinoScriptSyntax/#userinterface-GetPointOnCurve), [GetPointOnMesh](/api/RhinoScriptSyntax/#userinterface-GetPointOnMesh), [GetPointOnSurface](/api/RhinoScriptSyntax/#userinterface-GetPointOnSurface). ## Special Dialogs The Dialog methods in RhinoScript syntax are used to prompt of with generic custom information. Dialogs can be used to draw more attention to a required interaction with the user. Dialogs generally interrupt the workflow - the script cannot continue until the dialog is dealt with by the user. #### MessageBox() The simplest dialog box is the `rs.MessageBox()` function. The rs.MessageBox() comes with many options to customize the buttons based on your needs: ```python import rhinoscriptsyntax as rs rs.MessageBox("Hello Rhino!") # Simple message dialog rs.MessageBox("Hello Rhino!", 4 | 32) # A Yes, No dialog rs.MessageBox("Hello Rhino!", 2 | 48) # An Abort, Retry dialog ``` Note that rs.MessageBox() returns a value - you can set a variable to record the result from a message box so that you can tell which button the user has clicked. ```python import rhinoscriptsyntax as rs button = rs.MessageBox("Hello Rhino!", 2 | 48) # An Abort, Retry dialog ``` The value of 'button' in the code above will tell the script which button was clicked and it can proceed appropriately. See the Rhino IronPython Help for details on the available buttons and the return codes from rs.MessageBox() #### ListBox() Some of the more advanced dialogs can be populated with custom selections: ```python import rhinoscriptsyntax as rs options = ('First Pick', 'Second Pick', 'Third Pick') if options: result = rs.ListBox(options, "Pick an option") if result: rs.MessageBox( result + " was selected" ) ``` Will result in: #### Pre-defined dialog box methods: **CheckListBox** - Displays a list of strings in a checkable list. The user can pick multiple items. **ComboListBox** - Displays a list of strings in a combo list. **EditBox** - Displays a dialog box with a multi-line edit control. **PopupMenu** - Displays a context-like popup menu. **PropertyListBox** - Displays a list of items and values in a property list. **RealBox** - Displays a dialog box prompting the user to enter a number. **StringBox** - Displays a dialog box prompting the user to enter a string. **GetLayer** - Displays dialog box prompting the user to select a layer For details on all the dialog box functions in RhinoScriptSyntax for Python go to the [RhinoScriptSyntax User interface methods](/api/RhinoScriptSyntax/win/#userinterface) ## File System dialogs Working with files and folders on the computer take a special class of dialogs. ```python import rhinoscriptsyntax as rs filename = rs.OpenFileName() if filename: rs.MessageBox(filename) ``` RunPythonScript #### Additional File System Dialogs | Method | | | Description | | :-------------- | ---- | ---- | :--------------------------------------- | | BrowseForFolder | | | Displays a Windows browse-for-folder dialog box. | | OpenFileName | | | Displays a Windows file open dialog box. | | OpenFileNames | | | Displays a Windows file open dialog box. | | SaveFileName | | | Displays a Windows file save dialog box. | For a complete detailed sample see the [How to read and write a simple file guide](/guides/rhinopython/python-reading-writing/). ## [Eto Custom Dialog Framework](/guides/rhinopython/eto-forms-python/) Eto is an open source cross-platform dialog box framework available in Rhino 6. Eto can be used to create advanced dialog boxes from within C#, C++ and Rhino.Python. If the pre-defined dialogs above are not enough for your purposes, a Eto dialog might be the right solution. As an example, here is a custom collapsing dialog that uses many controls: Eto is very powerful, but that power comes with more sophisticated Python syntax. Understanding how best to write, organize and use Eto dialogs will take some work. To start learning about the Eto framework in Python, go to the [Eto Forms in Python](/guides/rhinopython/eto-forms-python/) guide. ## Related Topics - [Reading and Writing files with Python](/guides/rhinopython/python-reading-writing) - [RhinoScriptSyntax User interface methods](/api/RhinoScriptSyntax/win/#userinterface) - [Eto Forms in Python](/guides/rhinopython/eto-forms-python/) guide -------------------------------------------------------------------------------- # Interacting with Rhino Accounts Source: https://developer.rhino3d.com/en/guides/rhinocommon/rhinoaccounts/ra-overview/ This guide discusses all the steps needed to interact with Rhino Accounts within Rhino. ## Overview Rhino Accounts is an authentication and authorization system built and supported by Robert McNeel & Associates. It is built on top of the [OpenID Connect protocol](https://openid.net/connect/). As a brief summary, the authentication services of Rhino Accounts enables you, the developer, to verify an individual's identity and access (with the user's permission) account information such as their name, email addresses, profile picture, and more. This allows you to tailor your experience for the user or learn more about who is using your product. The authorization services are based on OAuth 2 Tokens. A token allows you to access any services that use Rhino Accounts for authorization, including your own. For example, your web server might accept certain files to be uploaded and downloaded from your plugin that sync accross different machines. An authorization token can be obtained by your plugin and can be presented to your web server when uploading or downloading files. The web server can check with Rhino Accounts that the token is valid and the individual it belongs to. For a more thourough overview of Rhino Accounts, please see the [Rhino Accounts Reference](https://docs.google.com/document/d/1-U0FYt6iQAM3UA6Rio4z0sDVXBSdc0kQk5e4zumnKig). OpenID Connect is built on top of HTTP. Since Rhino Accounts is an OpenID Connect provider, you can interact with Rhino Accounts using any HTTP client from .NET, JavaScript, Python, etc. However, making raw HTTP calls to Rhino Accounts can be tedious. Handling all the possible outcomes can be time consuming, and future-proofing your code in case the HTTP endpoints change can be frustrating. More importantly, taking into account all the possible security considerations to avoid leaking sensisive user data requires a rigorous review of your code. For all these reasons, we _strongly_ recommend that you use Rhino's built in capabilities to interact with Rhino Accounts described in this guide. It will also greatly simplify your development and make things easier down the road. Taking advantage of Rhino Accounts within Rhino is simple, and requires the following steps to be implemented. ## Required Steps To take Advantage of Rhino Accounts within Rhino: 1. [Contact us](mailto:support@mcneel.com) to obtain a unique client id and secret for Rhino Accounts. This will represent your service within the system. 2. [Call `GetTokensAsync` or `TryGetAuthTokens` to retrieve authentication and authorization tokens](/guides/rhinocommon/rhinoaccounts/ra-example). 3. (*Optional*) [Call `RevokeAuthTokenAsync` to invalidate an authorization token when you no longer need it.](/guides/rhinocommon/rhinoaccounts/ra-revoke). -------------------------------------------------------------------------------- # 5 Conditional Execution Source: https://developer.rhino3d.com/en/guides/rhinoscript/primer-101/5-conditional-execution/ ## 5.1 What if? What if I were to fling this rock at that bear? What if I were to alleviate that moose from its skin and wear it myself instead? It's questions like these that signify abstract thought, perhaps the most stunning of all human traits. It's no good actually throwing rocks at bears by the way, you're only going to upset it and severely diminish your chances of getting back to your cave by nightfall in one piece. As a programmer, you need to take abstract though to the next level; the very-very-conscious level. A major part of programming is recovering from screw-ups. A piece of code does not always behave in a straightforward manner and we need to catch these aberrations before they propagate too far. At other times we design our code to deal with more than one situation. In any case, there's always a lot of conditional evaluation going on, a lot of 'what if' questions. Let's take a look at three conditionals of varying complexity: 1. If the object is a curve, delete it. 2. If the object is a short curve, delete it. 3. If the object is a short curve, delete it, otherwise move it to the "curves" layer. The first conditional statement evaluates a single boolean value; an object is either is a curve or it is not. There's no middle ground. The second conditional must also evaluate the constraint 'short'. Curves don't become short all of a sudden any more than people grow tall all of a sudden. We need to come up with a boolean way of talking about 'short' before we can evaluate it. The third conditional is identical to the second one, except it defines more behavioural patterns depending on the outcome of the evaluation. The translation from English into VBScript is not very difficult. We just need to learn how conditional syntax works. Problem 1: ```vb If Rhino.IsCurve(strObjectID) Then Call Rhino.DeleteObject(strObjectID) End If ``` Problem 2: ```vb If Rhino.IsCurve(strObjectID) Then If Rhino.CurveLength(strObjectID) < 0.01 Then Call Rhino.DeleteObject(strObjectID) End If End If ``` Problem 3: ```vb If Rhino.IsCurve(strObjectID) Then If Rhino.CurveLength(strObjectID) < 0.01 Then Call Rhino.DeleteObject(strObjectID) Else Call Rhino.ObjectLayer(strObjectID, "Curves") End If End If ``` The most common conditional evaluation is the *If…Then statement*. *If…Then* allows you to bifurcate the flow of a program. The simplest *If…Then* structure can be used to shield of certain lines of code. It always follows the same format: ``` If SomethingOrOther Then DoSomething() DoSomethingElseAsWell() End If ``` The bit of code between the If and the Then on line 1 is evaluated and when it turns out to be True, the block of code between the first and last line will be executed. If SomethingOrOther turns out to be False, lines 2 and 3 are skipped and the script goes on with whatever comes after line 4. In case of very simple *If…Then* structures, such as the first example on the previous page, it is possible to use a shorthand notation which only takes up a single line instead of three. The shorthand for *If…Then* looks like: ```vb If SomethingOrOther Then DoSomething() ``` Whenever you want to put more than one action into an If…Then block, you have to use the regular notation. The *If…Then…Else* syntax has a similar shorthand but you will rarely see it used. It's better to keep the lines of code short since that will improve readability. Whenever you need an *If…Then…Else* structure, I suggest you use the following syntax: ```vb If SomethingOrOther Then DoSomething() Else DoSomethingElse() End If ``` If SomethingOrOther turns out to be True, then the bit of code between lines 1 and 3 are executed. This block can be as long as you like of course. However, if SomethingOrOther is False, then the code between *Else* and End If is executed. So in the case of *If…Then…Else*, one -and only one- of the two blocks of code is put to work. You can nest *If…Then* structures as deep as you like, though code readability will suffer from too much indenting. The following example uses four nested *If…Then* structures to delete short, closed curves on Tuesdays. ```vb If Rhino.IsCurve(strObjectID) Then If Rhino.CurveLength(strObjectID) < 1.0 Then If Rhino.IsCurveClosed(strObjectID) Then If WeekDay(Now()) = vbTuesday Then Call Rhino.DeleteObject(strObjectID) End If End If End If End If ``` When you feel you need to split up the code stream into more than two flows and you don't want to use nested structures, you can instead switch to something which goes by the name of the *If…Then…ElseIf…Else* statement. As you may or may not know, the * _Make2D* command in Rhino has a habit of creating some very tiny curve segments. We could write a script which deletes these segments automatically, but where would we draw the line between 'short' and 'long'? We could be reasonably sure that anything which is shorter than the document absolute tolerance value can be removed safely, but what about curves which are slightly longer? Rule #1 in programming: When in doubt, make the user decide. That way you can blame them when things go wrong. A good way of solving this would be to iterate through a predefined set of curves, delete those which are definitely short, and select those which are ambiguous. The user can then decide for himself whether those segments deserve to be deleted or retained. We won't discuss the iteration part here, for you need to know more about arrays than you do now. The conditional bit of the algorithm looks like this: ```vb Dim dblCurveLength dblCurveLength = Rhino.CurveLength(strObjectID) If Not IsNull(dblCurveLength) Then If dblCurveLength < Rhino.UnitAbsoluteTolerance() Then Call Rhino.DeleteObject(strObjectID) ElseIf dblCurveLength < (10 * Rhino.UnitAbsoluteTolerance()) Then Call Rhino.SelectObject(strObjectID) Else Call Rhino.UnselectObject(strObjectID) End If End If ``` Saying "that red dress makes your bottom look big" and "that yellow dress really brings out the colour of your eyes" essentially means the same thing. In VBScript you can also say the same thing in different ways, though in general the "your bottom looks big" approach is preferable in programming. The above snippet could have been written as a nested If…Then structure, but then it would not resemble the way we think about the problem. ## 5.2 Select Case Even though the If…Then…ElseIf…Else statement allows us to split up the code stream into any number of substreams, it is not a very elegant piece of syntax. Even very simple conditional evaluation will look rather complex because of the repeated comparisons. The Select…Case structure was designed to simplify conditional evaluation which potentially results in many different code streams. (For those among you who are/were Java or C programmers, Select…Case in VBScript is the same as switch…case in Java/C). There are a few drawbacks compared to If…Then…ElseIf…Else statements. For one, a Select…Case can only evaluate equality, meaning you can only check to see if some variable is equal to 2, not if it is smaller than 2. The syntax for Select…Case looks like this: ```vb Select Case iVariable Case 0 DoSomething() Case 1 DoSomethingElse() Case 2 DoSomethingTotallyDifferent() Case 30, 45, 60, 90, 180 SurpriseMe() Case Else DoWhateverItIsYouWouldNormallyDo() End Select ``` On line 1, the *iVariable* represents a value which we will be evaluating for equality. In this case the *iVariable* has to be a number, but it could equally well be a String. Line 2 lists the first evaluation we'll perform. Since the *Select…Case* statement will take care of the comparisons itself, we only have to supply the value we want to compare to. If iVariable happens to the be same as 0, the bit of code directly beneath Case 0 (the function call to DoSomething()) is executed. If *iVariable* equals 45, then the *SurpriseMe()* function is called. You can supply any number of cases and you can add more comparisons to a case by comma separating them (line 8). If none of the cases you have specified is a match, the *Case Else* bit will be executed. *Case Else* is optional, you do not have to implement it. Now, how about an example? ```vb Dim intObjectType 'An Integer to store the Rhino Object-Type code intObjectType = Rhino.ObjectType(strObjectID) If IsNull(intObjectType) Then Exit Sub 'this probably means the object does not exist; abort Dim strLayerName 'A String to store a layer name Select Case intObjectType 'Compare the actual type code with the preset ones Case 1, 2 'Points and PointCloud objects strLayerName = "Points" Case 4 'Curves strLayerName = "Curves" Case 8, 16 'Surfaces and PolySurfaces strLayerName = "(Poly)Surfaces" Case 32 'Meshes strLayerName = "Meshes" Case 256 'Lights strLayerName = "Lights" Case 512, 8192 'Annotations and TextDots strLayerName = "Annotations" Case 2048, 4096 'Instanced and Referenced Block definitions strLayerName = "Blocks" Case Else 'Icky objects such as Layers, Materials and Grips; abort Exit Sub End Select If Not Rhino.IsLayer(strLayerName) Then 'If the layer we are about to assign does not yet exist… Call Rhino.AddLayer(strLayerName) 'Create it. End If Call Rhino.ObjectLayer(strObjectID, strLayerName) 'Assign the object to the layer ``` This snippet of code will check the type of the object which is referenced by the variable *strObjectID* and it will assign it to a specific layer. Some object type codes do not belong to 'real' objects (such as grips and edges) so we need the Case Else bit to make sure we don't try to assign them to a layer. I'm going to be very naughty right now and not discuss this in detail. The comments should be enough to help you on your way. ## 5.3 Looping Executing certain lines of code more than once is called looping in programming slang. On page 5 I mentioned that there are two types of loops; conditional and incremental which can be described respectively as: ``` Keep adding milk until the dough is kneadable Add five spoons of cinnamon ``` Conditional loops will keep repeating until some condition is met where as incremental loops will run a predefined number of times. Life isn't as simple as that though, and there are as many as eight different syntax specifications for loops in VBScript, we'll only discuss the two most important ones in depth. ## 5.4 Conditional Loops Sometimes we do not know how many iterations we will need in advance, so we need a loop which is potentially capable of running an infinite number of times. This type is called a Do…Loop. In the most basic form it looks like this: ```vb Do DoSomething() [If (condition is met) Then Exit Do] Loop ``` All the lines between the Do keyword and the Loop keyword will be repeated until we abort the loop ourselves. If we do not abort the loop, I.e. if we omit the `Exit Do` statement or if our condition just never happens to be met, the loop will continue forever. This sounds like an easy problem to avoid but it is in fact a very common bug. In VBScript it does not signify the end of the world to have a truly infinite loop. Scripts are always run under the supervision of the RhinoScript plug-in. Jamming the Escape key several times in a row is pretty likely to gut any script which happens to be running. The following example script contains an endless *Do…Loop* which can only be cancelled by the user pressing (and holding) escape. ```vb Option Explicit 'Display an updating digital clock in all viewports ViewportClock() Sub ViewportClock() Dim strTextObjectID strTextObjectID = Rhino.AddText(CStr(Time()), Array(0,0,0), 20) If IsNull(strTextObjectID) Then Exit Sub Do Call Rhino.Sleep(1000) Call Rhino.TextObjectText(strTextObjectID, CStr(Time())) Loop End Sub ``` Here's how it works:
Line Description
1 & 2 Option Explicit declaration and comments about who's who and what's what
4 Main Function call
5 Main function declaration
6 We declare a variable which is capable of storing a Rhino object ID.
7 We create a new Rhino Text object. The RhinoScript helpfile tells us how to approach this particular method: Rhino.AddText (strText, arrPoint [, dblHeight [, strFont [, intStyle]]]) Five arguments, the last three of which are optional. When adding a text object to Rhino we must specify the text string and the location for the object. There are no defaults for this. The height of the text, font name and style do have default values. However, since we're not happy with the default height, we will override it to be much bigger: strTextObjectID = Rhino.AddText(CStr(Time()), Array(0,0,0), 20) The strText argument must contain a String description of the current system time. We will simply nest two native VBScript functions to get it. Since these functions are not designed to fail we do not have to check for a Null variable and we can put them 'inline'. Time() returns a variable which contains only the system time, not the date. We could also have used the Now() function (as on page 20) in which case we would have gotten both the date and the time. Time() does not return a String type variable, so before we pass it into Rhino we have to convert it to a proper String using the CStr() function. This is analogous with our code on page 20. The *arrPoint* argument requires an array of doubles. We haven't done arrays yet, but it essentially means we have to supply the x, y and z coordinates of the text insertion point. `Array(0,0,0` means the same as the world origin. The default height of text objects is 1.0 units, but we want our clock to look big since big things look expensive. Therefore we're overriding it to be 20 units instead.
8 I don't think there's anything here that could possibly go wrong, but it never hurts to be sure. Just in case the text object hasn't been created we need to abort the subroutine in order to prevent an error later on.
10 We start an infinite Do…Loop, lines 11 and 12 will be repeated for all eternity.
11 There's no need to update our clock if the text remains the same, so we really only need to change the text once every second. The Rhino.Sleep() method will pause Rhino for the specified amount of milliseconds. We're forcing the loop to take it easy, by telling it to take some time off on every iteration. We could remove this line and the script will simply update the clock many times per second. This kind of reckless behaviour will quickly flood the undo buffer.
12 This is the cool bit. Here we replace the text in the object with a new String representing the current system time.
13 End of the *Do…Loop*, tells the interpreter to go back to line 10
14 End of the Subroutine. This line will never be called because the script is not capable of actually breaking out of the loop itself. Once the user presses the Escape key the whole script will be cancelled. We still need to add it since every single Sub statement needs to have a matching End Sub.
A simple example of a non-endless loop which will terminate itself would be an iterative scaling script. Imagine we need a tool which makes sure a curve does not exceed a certain length {L}. Whenever a curve does exceed this predefined value it must be scaled down by a factor {F} until it no longer exceeds {L}. This approach means that curves that turn out to be longer than {L} will probably end up being shorter than {L}, since we always scale with a fixed amount. There is no mechanism to prevent undershooting. Curves that start out by being shorter than {L} should remain unmolested. A possible solution to this problem might look like this: ```vb Option Explicit 'Iteratively scale down a curve until it becomes shorter than a certain length FitCurveToLength() Sub FitCurveToLength() Dim strCurveID strCurveID = Rhino.GetObject("Select a curve to fit to length", 4, True, True) If IsNull(strCurveID) Then Exit Sub Dim dblLength dblLength = Rhino.CurveLength(strCurveID) Dim dblLengthLimit dblLengthLimit = Rhino.GetReal("Length limit", 0.5 * dblLength, 0.01 * dblLength, dblLength) If IsNull(dblLengthLimit) Then Exit Sub Do If Rhino.CurveLength(strCurveID) <= dblLengthLimit Then Exit Do strCurveID = Rhino.ScaleObject(strCurveID, Array(0,0,0), Array(0.95, 0.95, 0.95)) If IsNull(strCurveID) Then Call Rhino.Print("Something went wrong...") Exit Sub End If Loop Call Rhino.Print("New curve length: " & Rhino.CurveLength(strCurveID)) End Sub ```
Line Description
1...6 This should be familiar by now
7 Prompt the user to pick a single curve object, we're allowing preselection.
11 Retrieve the current curve length. This function should not fail, no need to check for Null.
14 Prompt the user for a length limit value. The value has be chosen between the current curve length and 1% of the current curve length. We're setting the default to half the current curve length.
17 Start a Do…Loop
18 This is the break-away conditional. If the curve length no longer exceeds the preset limit, the Exit Do statement will take us directly to line 26.
20 If the length of the curve did exceed the preset limit, this line will be executed. The Rhino.ScaleObject() method takes four arguments, the last one of which is optional. We do not override it. We do need to specify which object we want rescaled (strCurveID), what the center of the scaling operation will be (Array(0,0,0); the world origin) and the scaling factors along x, y and z (95% in all directions).
25 Instructs the interpreter to go back to line 17
27 Eventually all curves will become shorter than the limit length and the Do…Loop will abort. We print out a message to the command line informing the user of the new curve length.
## 5.5 Alternate Syntax Do…Loops are almost always conditional. The infinite loop example of the viewport clock is a rare exception. If the condition for the continuation of the loop is fairly complicated we will probably want to do it ourselves. In simple cases we could use one of the alternative loop syntax rules, which has the conditional evaluation baked in: ```vb Do While SomeCondition DoSomething() Loop ``` This kind of loop syntax will abort the loop when `SomeCondition` is no longer True. In light of the curve scaling example, we could have put the curve length condition in the loop definition itself, like so: ```vb Do While Rhino.CurveLength(strCurveID) > dblLengthLimit strCurveID = Rhino.ScaleObject(strCurveID, Array(0,0,0), Array(0.95, 0.95, 0.95)) If IsNull(strCurveID) Then Rhino.Print "Something went wrong..." Exit Sub End If Loop ``` We can still add any number of additional evaluations inside the body of the loop if we want, but the syntax above will behave exactly the same as the original code on the previous page. If we want the loop to terminate when a condition becomes True instead of False, we can use the *Until* keyword instead of the *While* keyword. This is just a syntactic trick, using *Until* is exactly the same as using *While* with an additional *Not* operator: ```vb Do Until SomeCondition DoSomething() Loop ``` The problem you might have with both these options is that the body of the loop might not be executed at all. If the curve which is indicated by `strCurveID` is already shorter than `dblLengthLimit` to begin with the entire loop is skipped. If you want your loop to run at least once and evaluate itself at the end rather than at the beginning, you can put the *While/Until* conditional after the *Loop* keyword instead: ```vb Do DoSomething() Loop While SomeCondition ``` Now, you are guaranteed that *DoSomething()* will be called at least once. ## 5.6 Incremental Loops When the number of iterations is known in advance, we could still use a Do…Loop statement, but we'll have to do the bookkeeping ourselves. This is rather cumbersome since it involves us declaring, incrementing and evaluating variables. The For…Next statement is a loop which takes care of all this hassle. The underlying idea behind For…Next loops is to have a value incremented by a fixed amount every iteration until it exceeds a preset threshold: ```vb Dim i For i = A To B [Step N] AddSpoonOfCinnamon() Next ``` The variable i starts out by being equal to *A* and it is incremented by *N* until it becomes larger than *B*. Once *i > B* the loop will terminate. The Step keyword is optional and if we do not override it the default stepsize of 1.0 will be used. In the example above the variable *i* is not used in the loop itself, we're using it for counting purposes only. If we want to abort a *For…Next* loop ahead of time, we can place a call to *Exit For* in order to short-circuit the process. Creating mathematical graphs is a typical example of the usage of *For…Next*: ```vb Option Explicit 'Draw a sine wave using points DrawSineWave() Sub DrawSineWave() Dim x, y Dim dblA, dblB, dblStep dblA = -8.0 dblB = 8.0 dblStep = 0.25 For x = dblA To dblB Step dblStep y = 2*Sin(x) Call Rhino.AddPoint(Array(x, y, 0)) Next End Sub ``` The above example draws a sine wave graph in a certain numeric domain with a certain accuracy. There is no user input since that is not the focus of this paragraph, but you can change the values in the script. The numeric domain we're interested in ranges from -8.0 to +8.0 and with the current stepsize of 0.25 that means we'll be running this loop 65 times. 65 is one more than the expected number 64 (64 = dblStep-1 × (dblB - dblA)) since the loop will start at `dblA` and it will stop only after `dblB` has been exceeded. The *For…Next* loop will increment the value of x automatically with the specified stepsize, so we don't have to worry about it when we use x on line 14. We should be careful not to change x inside the loop since that will play havoc with the logic of the iterations. Loop structures can be nested at will, there are no limitations, but you'll rarely encounter more than three. The following example shows how nested *For…Next* structures can be used to compute distributions: ```vb Sub TwistAndShout() Dim z, a Dim pi, dblTwistAngle pi = Rhino.Pi() dblTwistAngle = 0.0 Call Rhino.EnableRedraw(False) For z = 0.0 To 5.0 Step 0.5 dblTwistAngle = dblTwistAngle + (pi/30) For a = 0.0 To 2*pi Step (pi/15) Dim x, y x = 5 * Sin(a + dblTwistAngle) y = 5 * Cos(a + dblTwistAngle) Call Rhino.AddSphere(Array(x,y,z), 0.5) Next Next Call Rhino.EnableRedraw(True) End Sub ``` The master loop increments the z variable from 0.0 to 5.0 with a default step size of 0.5. The z variable is used directly as the z-coordinate for all the sphere centers. For every iteration of the master loop, we also want to increment the twist angle with a fixed amount. We can only use the For…Loop to automatically increment a single variable, so we have to do this one ourselves on line 8. The master loop will run a total of ten times and the nested loop is designed to run 30 times. But because the nested loop is started every time the master loop performs another iteration, the code between lines 11 and 14 will be executed 10×30 = 300 times. Whenever you start nesting loops, the total number of operations your script performs will grow exponentially. The *rs.EnableRedraw()* calls before and after the master loop are there to prevent the viewport from updating while the spheres are inserted. The script completes much faster if it doesn't have to redraw 330 times. If you comment out the *rs.EnableRedraw()* call you can see the order in which spheres are added, it may help you understand how the nested loops work together. ## Next Steps Now it should be coming together on how Python works. Just a few more details. Leanr more about Python's advanced variables in [Arrays](/guides/rhinoscript/primer-101/6-arrays/). -------------------------------------------------------------------------------- # Make a Circle with RhinoCommon Source: https://developer.rhino3d.com/en/samples/rhinopython/make-circle-with-rhinocommon/ This sample creates a circle without using functions in the rhinoscript package. Rhino Python Sample - MakeCircleWithRhinoCommon The rhinoscript package is provided as a scripting "convenience" Python can use all of the classes and functions defined in RhinoCommon and the rest of the .NET Framework!!! This sample creates a circle without using functions in the rhinoscript package Rhino is the base namespace of the RhinoCommon SDK ```python import math import Rhino import scriptcontext # Use a GetPoint to prompt the user to select a point # If the user doesn't cancel, this function returns a new Circle # class instance centered at the selection point with a radius of 1 def GetCircleFromUser(): get_result = Rhino.Input.RhinoGet.GetPoint("Circle center", False) if( get_result[0] != Rhino.Commands.Result.Success ): print("error getting point") return None pt = get_result[1] print("Got a point at {}".format(pt)) # return a new Circle return Rhino.Geometry.Circle( pt, 1 ) # Add some points to the document that are on a circle def MakeCirclePoints( circle, count ): for i in range(count): #circles parameterized between 0 and 2Pi t = float(i) * 2 * math.pi / float(count) print(t) pt = circle.PointAt(t) scriptcontext.doc.Objects.AddPoint(pt) ###################################### # Functions have been defined above - let's execute some script # # Here we check to see if this file is being executed as the "main" python # script instead of being used as a module by some other python script # This allows us to use the module which ever way we want. if( __name__ == '__main__' ): print("Python sample script to make a circle curve and plop some points on it") circle = GetCircleFromUser() if circle == None: print("circle is none") else: print("got a circle") scriptcontext.doc.Objects.AddCircle(circle) MakeCirclePoints( circle, 10 ) # redraw everything so we can see what we got scriptcontext.doc.Views.Redraw() print("Script Complete") ``` -------------------------------------------------------------------------------- # 6 Arrays Source: https://developer.rhino3d.com/en/guides/rhinoscript/primer-101/6-arrays/ ## 6.1 My Favorite Things We've already been using arrays in examples and I've always told you not to worry about it. Those days are officially over. Now is the time to panic. Perhaps it's best if we just get the obvious stuff out of the way first: **An array is a list of variables** That's really all there is to it. Sometimes -in fact quite often- you want to store large or unknown amounts of variables. You could of course declare 15,000 different variables by hand but that is generally considered to be bad practise. The only thing about arrays which will seem odd at first is the way they count. Arrays start counting at zero, while we are used to start counting at one. Try it by counting the number of fingers on your right hand. Chances are you are someone who has just counted to five. Arrays would disagree with you, they would only have counted to four: It helps to refer to numbers as 'indices' when you use the zero-based counting system just to avoid confusion. So when we talk about the 'first element' of an array, we actually mean 'the element with index 0'. I know this all sounds like Teaching Granny To Suck Eggs, but zero-based counting systems habitually confuse even the most die-hard programmer. Arrays are just like other variables in VBScript with the exception that we have to use parenthesis to set and retrieve values: ```vb 'Normal variable declaration, assignment and retrieval Dim intNumber intNumber = 8 Call Rhino.Print(intNumber) 'Array declaration, assignment and retrieval Dim arrNumbers(2) arrNumbers(0) = 8 arrNumbers(1) = -5 arrNumbers(2) = 47 Call Rhino.Print(arrNumbers(0) & ", " & arrNumber(1) & ", " & arrNumbers(2)) ``` The example above shows how to declare an array which is capable of storing 3 numbers (indices 0, 1 and 2). In cases like this (when you know in advance how many and which numbers you want to assign) you can also use a shorthand notation in which case you have to omit the parenthesis in the variable declaration: ```vb Dim arrNumbers arrNumbers = Array(8, -5, 47) ``` The *Array()* function in VBScript takes any number of variables and turns them into an array. It is a bit of an odd function since it has no fixed signature, you can add as many arguments as you like. Note that in the above example there is nothing special about the array declaration, it could be any other variable type as well. This paragraph is called "My favourite things" not because arrays are my favourite things, but because of the example below which will teach you pretty much all there is to know about arrays except nesting: ```vb Sub MyFavouriteThings() Dim strPrompt, strAnswer Dim arrThings() Dim intCount intCount = 0 Do Select Case intCount Case 0 strPrompt = "What is your most favourite thing?" Case 1 strPrompt = "What is your second most favourite thing?" Case 2 strPrompt = "What is your third most favourite thing?" Case Else strPrompt = "What is your " & (intCount+1) & "th most favourite thing?" End Select strAnswer = Rhino.GetString(strPrompt) If IsNull(strAnswer) Then Exit Do ReDim Preserve arrThings(intCount) arrThings(intCount) = strAnswer intCount = intCount+1 Loop If intCount = 0 Then Exit Sub Call Rhino.Print("Your " & UBound(arrThings)+1 & " favourite things are:") For i = 0 To UBound(arrThings) Call Rhino.Print((i+1) & ". " & arrThings(i)) Next End Sub ```
Line Description
3 We do not know how many favourite things the user has, so there's no way we can set the array to a certain size in advance. Whenever an array is declared with parenthesis but without any number, it will be made dynamic. This means we can resize it during runtime.
4...5 We'll be using this variable for bookkeeping purposes. Although we could technically extract all information from the array itself, it's easier to keep an integer around so we can always quickly find the number of elements in the array.
22 We've just asked the user what his/her Nth favourite thing was, and he/she answered truthfully. This means we're going to have to store the last answer in our array, but it is not big enough yet to store additional data so the first thing we must do is increase the size of *arrThings*. We can change the size (the number of possible items it can store) of an array in four different ways: ReDim arrThings(5) ReDim Preserve arrThings(5) arrThings = Array(0.0, 5.8, 4.2, -0.1) Erase arrThings The first option will set the array to the indicated size while destroying its contents. To flog a horse which, if not at this point dead, is in mortal danger of expiring; an array with a size of five is actually capable of storing six elements (0,1,2,3,4,5). If you wish to retain the stored information -as we do in our example-, then you must add the keyword Preserve between ReDim and the array variable name. By simply assigning another array to the variable, you will also change the contents and size. This only works though if the array in question was declared without parenthesis. And finally, if you want to reset an array, you can use the Erase keyword. This will destroy all the stored data and dynamic array size will be set to zero. The array cannot contain any elements after an erase, you must first ReDim it again. If you erase a fixed size array, only the data will be destroyed.
23 Straightforward array assignment using an index between parenthesis. If you were to try to assign a value to an array at a non-existing index you will get a fatal error: Mountain View Unfortunately the message doesn't tell us anything about which array was queried and what its size is. We do know what index generated the error ( number: 6 ).
24 We increase the bookkeeping integer since the array is now one larger than before.
27 It is possible the user has not entered any String. If this is the case the intCount variable will still have a value of zero which we assigned on line 5. There is nothing for us to do in this case and we should abort the subroutine.
29 After the loop has completed and we've made certain the array contains some actual data, we print all the gathered information to the command history. First we will tell the user how many favourite things he/she entered. We could again use the intCount variable to retrieve this information, but it is also possible to extract that data directly from the array itself using the UBound() function. UBound is short for "Upper bound", which is the terminology used to indicate the highest possible index of an array. If the array is empty the upper bound is technically -1. However, if we attempt to use the UBound() function on an array which cannot contain any elements, there will be a fatal error: Dim arrList() Call Rhino.Print(UBound(arrList)) The above will fail..
30 This is a typical usage of the For…Next loop. Whenever we want to iterate through an array using index values, we use something like the following: For i = 0 To UBound(arrData) … Next It is customary to use a short variable name for iteration variables. This clashes with the prefix rules as defined on page 9 and is to be treated as a special case. Typically, for simple iteration variables i, j and k are used: For i = 1 To UBound(arrData) For j = 0 To i-1 For k = 0 To UBound(arrDifferentData) … Next Next Next
31 Standard array value retrieval.
## 6.2 Points and Vectors In RhinoScript, coordinates are defined as arrays of three numbers. Element 0 corresponds with x, element 1 with y and element 2 with z. This notation is used for both points and vectors. ```vb Sub PointSpiral() Dim arrPoint(2) Dim t, pi pi = Rhino.Pi() 'Call Rhino.EnableRedraw(False) For t = -5 To 5 Step 0.025 arrPoint(0) = t * Sin(5*t) arrPoint(1) = t * Cos(5*t) arrPoint(2) = t Call Rhino.Print(Rhino.Pt2Str(arrPoint, 3)) Call Rhino.AddPoint(arrPoint) Next 'Call Rhino.EnableRedraw(True) End Sub ``` The variable arrPoint is declared as a fixed size array on line 2 and the elements are assigned different values on lines 9 to 11 inside the body of the loop. On line 13 the array is converted to a String using the RhinoScript method Rhino.Pt2Str(). Pt2Str and Str2Pt (abbreviations for PointToString and StringToPoint respectively) can be used to convert points into Strings and vice versa. The regular VBScript function CStr() for casting variables into Strings will not work on arrays and cannot be used. The additional benefit of Pt2Str is that it takes optional formatting arguments. Vectors are a new concept in RhinoScript for Rhino4. Those of you who are familiar with the essentials of geometrical mathematics will have no problems with this concept... in fact you probably all are familiar with the essentials of geometrical mathematics or you wouldn't be learning how to program a 3D CAD platform. Vectors are indistinguishable from points. That is, they are both arrays of three doubles so there's absolutely no way of telling whether a certain array represents a point or a vector. There is a practical difference though; points are absolute, vectors are relative. When we treat an array of three doubles as a point it represents a certain coordinate in space, when we treat it as a vector it represents a certain direction. You see, a vector is an arrow in space which always starts at the world origin (0.0, 0.0, 0.0) and ends at the specified coordinate. The picture on the right shows two vector definitions; a purple and a blue one. The blue one happens to have all positive components while the purple one has only negative components. Both vectors have a different direction and a different length. When I say vectors are relative, I mean that they only indicate the difference between the start and end points of the arrow, i.e. vectors are not actual geometrical entities, they are only information. The blue vector could represent the tangent direction of the black curve at parameter {t}. If we also know the point value of the curve at parameter {t}, we know what the tangent of the curve looks like; we know where in space the tangent belongs. The vector itself does not contain this information; the orange and the blue vector are identical in every respect. The addition of vector definitions in RhinoScript is accompanied by a whole group of point/vector related methods which perform the basic operations of 'vector mathematics'. Addition, subtraction, multiplication, dot and cross products and so on and so forth. The table on the following page is meant as a reference table, do not waste your time memorizing it. I will be using standard mathematical notation: - A lowercase letter represents a number - A lowercase letter with a dot above it represents a point - A lowercase letter with an arrow above it represents a vector - Vertical bars are used to denote vector length
Notation Implementation Description Example
$$d =|\dot{p}-\dot{r}|$$ Distance(Pt1, Pt2) Compute the distance between two points.
$$\dot{r} = a \times \dot{p}$$ PointScale(Pt1, dblA) Multiply the components of the point by the specified factor. This operation is the equivalent of a 3DScaling around the world origin.
$$\dot{r} = \frac{\dot{p}}{a}$$ PointDivide(Pt1, dblA) Divide the components of the point by the specified factor. This is the equivalent of PointScale(Pt1, a-1).
$$? \dot{r} = \dot{p} \pm t$$ PointCompare(Pt1, Pt2, dblT) Check to see if two points are more or less identical. Two points are identical if the length of the vector between them is less than the specified tolerance.
$$\dot{r} = \dot{p} \times \mathbb{M}$$ PointTransform(Pt1, arrM) Transform the point using a linear transformation matrix.
$$\overrightarrow{w} = \left(\frac{1}{|\overrightarrow{v}|} \right) \times \overrightarrow{v}$$ VectorUnitize(Vec1) Divide all components by the inverse of the length of the vector. The resulting vector has a length of 1.0 and is called the unit-vector. Unitizing is sometimes referred to as "normalizing".
$$l = |\overrightarrow{v}|$$ VectorLength(Vec1) Compute the square root of the sum of the squares of all the components. Standard Pythagorean distance equation.
$$\overrightarrow{w} = -\overrightarrow{v}$$ VectorReverse(Vec1) Negate all the components of a vector to invert the direction. The length of the vector is maintained.
$$? \overrightarrow{w} = \overrightarrow{v} \pm t$$ VectorCompare(Vec1, Vec2, dblT) Check to see if two vectors are more or less identical. This is the equivalent of PointCompare().
$$\overrightarrow{w} =\frac{\overrightarrow{v}}{a}$$ VectorDivide(Vec1, dblA) Divide the components of the vector by the specified factor. This is the equivalent of *PointDivide()*.
$$\dot{r} = \dot{p} + \overrightarrow{v}$$ PointAdd(Pt1, Vec1) Add the components of the vector to the components of the point. Point-Vector summation is the equivalent of moving the point along the vector.
$$\dot{r} = \dot{p} - \overrightarrow{v}$$ PointSubtract(Pt1, Vec1) Subtract the components of the vector from the components of the point. Point-Vector subtraction is the equivalent of moving the point along the reversed vector.
$$\overrightarrow{v} = \dot{p} - \dot{r}$$ PointSubtract(Pt1, Vec1) Subtract the components of the vector from the components of the point. Point-Vector subtraction is the equivalent of moving the point along the reversed vector.
$$\overrightarrow{u} = \overrightarrow{v} + \overrightarrow{w}$$ VectorAdd(Vec1, Vec2) Add the components of Vec1 to the components of Vec2. This is equivalent to standard vector summation.
$$\overrightarrow{u} = \overrightarrow{v} - \overrightarrow{w}$$ VectorSubtract(Vec1, Vec2) Subtract the components of Vec1 from the components of Vec2. This is equivalent of *VectorAdd(Vec1, -Vec2)*.
$$\alpha = \overrightarrow{v} \times \overrightarrow{w}$$ VectorDotProduct(Vec1, Vec2) -or- VectorMultiply(Vec1, Vec2) Calculate the sum of the products of the corresponding components. In practical, everyday-life the DotProduct can be used to compute the angle between vectors since the DotProduct of two vectors v and w equals: |v||w| cos(a)
$$\overrightarrow{u} = \overrightarrow{v} \times \overrightarrow{w}$$ VectorCrossProduct(Vec1, Vec2) The cross-product of two vectors v and w, is a third vector which is perpendicular to both v and w.
$$\overrightarrow{u} = \overrightarrow{v} \times (\sphericalangle\alpha)\overrightarrow{w}$$ VectorRotate(Vec1, dblA, VecA) Rotate a vector a specified number of degrees around an axis-vector.
You probably feel like you deserved a break by now. It's not a bad idea to take a breather before we dive into the next example of vector mathematics. Not that the math is difficult, it's actually a lot easier than the above table would lead you to believe. In fact, it is so laughingly easy I thought it a good idea to add something extra... RhinoScript has no method for displaying vectors which is a pity since this would be very useful for visual feedback. I shall define a function here called AddVector() which we will use in examples to come. The function must be able to take two arguments; one vector definition and a point definition. If the point array is not defined the vector will be drawn starting at the world origin. Since this is a function which we will be using extensively we must make sure it is absolutely fool-proof. This is not an easy task since it could potentially choke on eleven different culprits. I'm not going to spell them all out since we'll be using a naughty trick to prevent this function from crashing. ```vb Function AddVector(ByVal vecDir, ByVal ptBase) On Error Resume Next AddVector = Null If IsNull(ptBase) Or Not IsArray(ptBase) Then ptBase = Array(0,0,0) End If Dim ptTip ptTip = Rhino.PointAdd(ptBase, vecDir) If Not (Err.Number = 0) Then Exit Function AddVector = Rhino.AddLine(ptBase, ptTip) If Not (Err.Number = 0) Then Exit Function If IsNull(AddVector) Then Exit Function Call Rhino.CurveArrows(AddVector, 2) End Function ```
Line Description
1 Standard function declaration. The function takes two arguments, if the first one does not represent a proper vector array the function will not do anything, if the second one does not represent a proper point array the function will draw the vector from the world origin.
2 This is the naughty bit. Instead of checking all the variables for validity we'll be using the VBScript error object. The On Error Resume Next statement will prevent the function from generating a run-time error when things start to go pear-shaped. Instead of aborting (crashing) the entire script it will simply march on, trying to make the best of a bad situation. We can still detect whether or not an error was generated and suppressed by reading the Number property of the Err object. Using the On Error Resume Next statement will reset the error object to default values.
3 Right now we're assigning the Null value to the function in case we need to abort prematurely. We want our function to either return a valid object ID on success or Null on failure. If we simply call the Exit Function statement before we assign anything to the AddVector variable, we will return a vbEmpty value which is the default for all variables.
5..7 In case the ptBase argument does not represent an array, we want to use the world origin instead.
9...10 Declare and compute the coordinate of the arrow tip. This will potentially fail if ptBase or vecDir are not proper arrays. However, the script will continue instead of crash due to the error trapping.
11 Since we just passed a dangerous bump in the code, we have to check the value of the error number. If it is still zero, no error has occurred and we're save to continue. Otherwise, we should abort.
13 Here we are calling the RhinoScript method Rhino.AddLine() and we're storing the return value directly into the AddVector variable. There are three possible scenarios at this point:
  1. The method completed successfully
  2. The method failed, but it didn't crash
  3. The method crashed
In the case of scenario 1, the AddVector variable now contains the object ID for a newly added line object. This is exactly what we want the function to return on success. In case of scenario #2, the AddVector will be set to Null. Of course it already was Null, so nothing actually changed. The last option means that there was no return value for AddLine() and hence AddVector will also be Null. But the error-number will contain a non-zero value now.
14...15 Check for scenario 2 and 3, abort if we find either one of them occurred.
17 Add an arrow-head to the line object.
18 Complete the function declaration. Once this line is executed the value of AddVector will be returned, whatever it is.
## 6.3 An AddVector() Example ```vb Option Explicit 'This script will compute a bunch of cross-product vector based on a pointcloud VectorField() Sub VectorField() Dim strCloudID strCloudID = Rhino.GetObject("Input pointcloud", 2, True, True) If IsNull(strCloudID) Then Exit Sub Dim arrPoints : arrPoints = Rhino.PointCloudPoints(strCloudID) Dim ptBase : ptBase = Rhino.GetPoint("Vector field base point") If IsNull(ptBase) Then Exit Sub Dim i For i = 0 To UBound(arrPoints) Dim vecBase vecBase = Rhino.VectorCreate(arrPoints(i), ptBase) Dim vecDir : vecDir = Rhino.VectorCrossProduct(vecBase, Array(0,0,1)) If Not IsNull(vecDir) Then vecDir = Rhino.VectorUnitize(vecDir) vecDir = Rhino.VectorScale(vecDir, 2.0) Call AddVector(vecDir, arrPoints(i)) End If Next End Sub ```
Line Description
10a We can use the colon to make the interpreter think that one line of code is actually two. Stacking lines of code like this can severely damage the readability of a file, so don't be overzealous. Personally, I only use colons to combine variable declaration/assignment on one line.
10b The arrPoints variable is an array which contains all the coordinates of a pointcloud object. This is an example of a nested array (see paragraph 6.4).
17 arrPoints(i) contains an array of three doubles; a standard Rhino point definition. We use that point to construct a new vector definition which points from the Base point to arrPoints(i).
19 The Rhino.VectorCrossProduct() method will return a vector which is perpendicular to vecBase and the world z-axis. If you feel like doing some homework, you can try to replace the hard-coded direction (Array(0,0,1)) with a second variable point a la ptBase.
21 Rhino.VectorCrossProduct() will fail if one of the input vectors is zero-length or if both input vectors are parallel. In those cases we will not add a vector to the document.
22...23 Here we make sure the vecDir vector is two units long. First we unitize the vector, making it one unit long, then we double the length..
25 Finally, place a call to the AddVector() function we defined on page 40. If you intend to run this script, you must also include the AddVector() function in the same script.
## 6.4 Nested Lists > I wonder why, I wonder why. > I wonder why I wonder. > I wonder why I wonder why. > I wonder why I wonder. > -Richard P. Feynman- Before we begin with nested arrays we need to take care of some house- keeping first: Nested arrays are not the same as two-dimensional arrays. Up to and including Rhino2, point lists in RhinoScript were stored in two-dimensional arrays. This system was changed to nested arrays in Rhino3. The only methods which still use two-dimensional arrays are the intersection and matrix methods. Now then, nested arrays. There's nothing to it. An array becomes nested when it is stored inside another array. The VectorField example on the previous page deals with an array of points (an array of arrays of three doubles). The image on the right is a visualization of such a structure. The left most column represents the base array, the one containing all coordinates. It can be any size you like, there's no limit to the amount of points you can store in a single array. Every element of this base array is a standard Rhino point array. In the case of point-arrays all the nested arrays are three elements long, but this is not a requisite, you can store anything you want into an array. Accessing nested arrays follows the same rules as accessing regular arrays. Using the VectorField example: ```vb Dim arrSeventhPoint, arrLastPoint arrSeventhPoint = arrPoints(6) arrLastPoint = arrPoints(UBound(arrPoints)) ``` This shows how to extract entire nested arrays. Assuming the illustration on this page represents *arrPoints*, *arrSeventhPoint* will be identical to *Array(0.3, -1.5, 4.9)*. If we want to access individual coordinates directly we can stack the indices: ```vb Dim dblSeventhPointHeight dblSeventhPointHeight = arrPoints(6)(2) ``` The above code will store the third element of the nested array stored in the seventh element of the base array in *dblSeventhPointHeight*. This corresponds with the orange block. Nested arrays can be parsed using nested loops like so: ```vb Dim i, j For i = 0 To UBound(arrPoints) For j = 0 To 2 Call Rhino.Print("Coordinate(" & i & ", " & j & ") = " & arrPoints(i)(j)) Next Next ``` Remember the scaling script from page 31? We're now going to take curve-length adjustment to the next level using nested arrays. The logic of this script will be the same, but the algorithm for shortening a curve will be replaced with the following one (the illustration shows the first eight iterations of the algorithm): Every control-point or 'vertex' of the original curve (except the ones at the end) will be averaged with its neighbours in order to smooth the curve. With every iteration the curve will become shorter and we will abort as soon a certain threshold length has been reached. The curve can never become shorter than the distance between the first and last control-point, so we need to make sure our goals are actually feasible before we start a potentially endless loop. Note that the algorithm is approximating, it may not be endless but it could still take a long time to complete. We will not support closed or periodic curves. We're going to put the vector math bit in a separate function. This function will compute the {vM} vector given the control points {pN-1; p; pN+1} and a smoothing factor {s}. Since this function is not designed to fail, we will not be adding any error checking, if the thing crashes we'll have to fix the bug. Instead of using VBScript variable naming conventions, I'll use the same codes as in the diagram: ```vb Function SmoothingVector(ByVal P, ByVal Pprev, ByVal Pnext, ByVal s) Dim Pm(2), i For i = 0 To 2 Pm(i) = (Pprev(i) + Pnext(i)) / 2.0 Next Dim Va, Vm Va = Rhino.VectorCreate(Pm, P) Vm = Rhino.VectorScale(Va, s) SmoothingVector = Vm End Function ```
Line Description
4..6 We'll use this loop to iterate through all the coordinates in the point arrays.
5 Compute the average value of the two components. {pm} is the halfway point between {pprev} and {pnext}.
9 Create the {va} vector.
10 Depending on the value of {s}, the smoothing will occur quickly or slowly. When {s} has a value of 1.0, it will have no effect on the algorithm since {vM} will be the same length as {vA}. Values higher than 1.0 are likely to make the smoothing operation overshoot. A value of 0.0 will stop the smoothing from taking place at all since {vM} will become a zero-length vector. Values lower than 0.0 will invert the smoothing. When we use this algorithm, we must make sure to set s to be something sensible, or the loop might become endless: 0.0 1 {s} # 1.0
We'll also put the entire curve-smoothing algorithm in a separate function. Since it's fairly hard to adjust existing objects in Rhino, we'll be adding a new curve and deleting the existing one: ```vb Function SmoothCurve(ByVal strCurveID, ByVal s) Dim arrCP : arrCP = Rhino.CurvePoints(strCurveID) Dim arrNewCP : arrNewCP = arrCP Dim i For i = 1 To UBound(arrCP) - 1 Dim Vm Vm = SmoothingVector(arrCP(i), arrCP(i-1), arrCP(i+1), s) arrNewCP(i) = Rhino.PointAdd(arrCP(i), Vm) Next Dim arrKnots : arrKnots = Rhino.CurveKnots(strCurveID) Dim intDegree : intDegree = Rhino.CurveDegree(strCurveID) Dim arrWeights: arrWeights = Rhino.CurveWeights(strCurveID) SmoothCurve = Rhino.AddNurbsCurve(arrNewCP, arrKnots, intDegree, arrWeights) If IsNull(SmoothCurve) Then Exit Function Call Rhino.DeleteObject(strCurveID) End Function ```
Line Description
2 Retrieve the nested array of curve control points.
3 We'll need a copy of the arrCP array since we need to create a new array for all the smoothed points while keeping the old array intact.
6 This loop will start at one and stop one short of the upper bound of the array. In other words, we're skipping the first and last items in the array.
8 Compute the smoothing vector using the current control point, the previous one (i-1) and the next one (i+1). Since we're omitting the first and last point in the array, every point we're dealing with has two neighbours.
9 Set the new control point position. The new coordinate equals the old coordinate plus the smoothing vector.
12...14 We'll be adding a new curve to the document which is identical to the existing one, but with different control point positions. A nurbs curve is defined by four different blocks of data: control points, knots, weights and degree (see paragraph 7.7 Nurbs Curves). We just need to copy the other bits from the old curve.
16 Create a new nurbs curve and store the object ID in the function variable.
19 Delete the original curve.
The top-level subroutine doesn't contain anything you're not already familiar with: ```vb Sub IterativeShortenCurve() Dim strCurveID : strCurveID = Rhino.GetObject("Open curve to smooth", 4, True) If IsNull(strCurveID) Then Exit Sub If Rhino.IsCurveClosed(strCurveID) Then Exit Sub Dim dblMin, dblMax, dblGoal dblMin = Rhino.Distance(Rhino.CurveStartPoint(strCurveID), Rhino.CurveEndPoint(strCurveID)) dblMax = Rhino.CurveLength(strCurveID) dblGoal = Rhino.GetReal("Goal length", 0.5*(dblMin + dblMax) , dblMin, dblMax) If IsNull(dblGoal) Then Exit Sub Do Until Rhino.CurveLength(strCurveID) < dblGoal Call Rhino.EnableRedraw(False) strCurveID = SmoothCurve(strCurveID, 0.1) If IsNull(strCurveID) Then Exit Do Call Rhino.EnableRedraw(True) Loop End Sub ``` ## Next Steps Array are very powerful in RhinoScript. Let's make a quick stop to learn about [geometry objects](/guides/rhinoscript/primer-101/7-geometry/). -------------------------------------------------------------------------------- # 7 Geometry Source: https://developer.rhino3d.com/en/guides/rhinoscript/primer-101/7-geometry/ ## 7.1 The openNURBS™ Kernel Now that you are familiar with the basics of scripting, it is time to start with the actual geometry part of RhinoScript. To keep things interesting we've used plenty of Rhino methods in examples before now, but that was all peanuts. Now you will embark upon that great journey which, if you survive, will turn you into a real 3D geek. As already mentioned in Chapter 3, Rhinoceros is build upon the openNURBS™ kernel which supplies the bulk of the geometry and file I/O functions. All plugins that deal with geometry tap into this rich resource and the RhinoScript plugin is no exception. Although Rhino is marketed as a "NURBS modeler for Windows", it does have a basic understanding of other types of geometry as well. Some of these are available to the general Rhino user, others are only available to programmers. As a RhinoScripter you will not be dealing directly with any openNURBS™ code since RhinoScript wraps it all up into an easy-to-swallow package. However, programmers need to have a much higher level of comprehension than users which is why we'll dig fairly deep. ## 7.2 Objects in Rhino All objects in Rhino are composed of a geometry part and an attribute part. There are quite a few different geometry types but the attributes always follow the same format. The attributes store information such as object name, colour, layer, isocurve density, linetype and so on and so forth. Not all attributes make sense for all geometry types, points for example do not use linetypes or materials but they are capable of storing this information nevertheless. Most attributes and properties are fairly straightforward and can be read and assigned to objects at will. This table lists most of the attributes and properties which are available to plugin developers. Most of these have been wrapped in the RhinoScript plugin, others are missing at this point in time and the custom user data element is special. We'll get to user data after we're done with the basic geometry chapters, but those of you who migrated from Rhino3 scripting might like to know that it is now possible to add user data to both the geometry and the attributes of objects. The following procedure displays some attributes of a single object in a dialog box. There is nothing exciting going on here so I'll refrain from providing a step-by-step explanation. ```vb Sub DisplayObjectAttributes(ByVal strObjectID) Dim arrSource : arrSource = Array("By Layer", "By Object", "By Parent") Dim strData : strData = "Object attributes for :" & strObjectID & vbCrLf strData = strData & "Description: " & Rhino.ObjectDescription(strObjectID) & vbCrLf strData = strData & "Layer: " & Rhino.ObjectLayer(strObjectID) & vbCrLf strData = strData & "LineType: " & Rhino.ObjectLineType(strObjectID) & vbCrLf strData = strData & "LineTypeSource: " & _ arrSource(Rhino.ObjectLineTypeSource(strObjectID)) & vbCrLf strData = strData & "MaterialSource: " & _ arrSource(Rhino.ObjectMaterialSource(strObjectID)) & vbCrLf Dim strName strName = Rhino.ObjectName(strObjectID) If IsNull(strName) Then strData = strData & "" & vbCrLf Else strData = strData & "Name: " & strName & vbCrLf End If Dim arrGroups arrGroups = Rhino.ObjectGroups(strObjectID) If IsArray(arrGroups) Then Dim i For i = 0 To UBound(arrGroups) strData = strData & "Group(" & i & "): " & arrGroups(i) & vbCrLf Next Else strData = strData & "" & vbCrLf End If Call Rhino.EditBox(strData, "Object attributes", "RhinoScript¹º¹") End Sub ``` ## 7.3 Points and Pointclouds Everything begins with points. A point is nothing more than a list of values called a coordinate. The number of values in the list corresponds with the number of dimensions of the space it resides in. Space is usually denoted with an R and a superscript value indicating the number of dimensions. (The 'R' stems from the world 'real' which means the space is continuous. We should keep in mind that a digital representation always has gaps (see paragraph 2.3.1), even though we are rarely confronted with them.) Points in 3D space, or R3 thus have three coordinates, usually referred to as {x,y,z}. Points in R2 have only two coordinates which are either called {x,y} or {u,v} depending on what kind of two dimensional space we're talking about. Points in R1 are denoted with a single value. Although we tend not to think of one-dimensional points as 'points', there is no mathematical difference; the same rules apply. One-dimensional points are often referred to as 'parameters' and we denote them with {t} or {p}. The image on the left shows the R3 world space, it is continuous and infinite. The x-coordinate of a point in this space is the projection (the red dotted line) of that point onto the x-axis (the red solid line). Points are always specified in world coordinates in Rhino, C-Plane coordinates are for siss... ehm users only. R2 world space (not drawn) is the same as R3 world space, except that it lacks a z-component. It is still continuous and infinite. R2 parameter space however is bound to a finite surface as shown in the center image. It is still continuous, I.e. hypothetically there is an infinite amount of points on the surface, but the maximum distance between any of these points is very much limited. R2 parameter coordinates are only valid if they do not exceed a certain range. In the example drawing the range has been set between 0.0 and 1.0 for both {u} and {v} directions, but it could be any finite domain. A point with coordinates {1.5, 0.6} would be somewhere outside the surface and thus invalid. Since the surface which defines this particular parameter space resides in regular R3 world space, we can always translate a parametric coordinate into a 3d world coordinate. The point {0.2, 0.4} on the surface for example is the same as point {1.8, 2.0, 4.1} in world coordinates. Once we transform or deform the surface, the R3 coordinates which correspond with {0.2, 0.4} will change. Note that the opposite is not true, we can translate any R2 parameter coordinate into a 3D world coordinate, but there are many 3D world coordinates that are not on the surface and which can therefore not be written as an R2 parameter coordinate. However, we can always project a 3D world coordinate onto the surface using the closest-point relationship. We'll discuss this in more detail later on. If the above is a hard concept to swallow, it might help you to think of yourself and your position in space. We usually tend to use local coordinate systems to describe our whereabouts; "I'm sitting in the third seat on the seventh row in the movie theatre", "I live in apartment 24 on the fifth floor", "I'm in the back seat". Some of these are variations to the global coordinate system (latitude, longitude, elevation), while others use a different anchor point. If the car you're in is on the road, your position in global coordinates is changing all the time, even though you remain in the same back seat 'coordinate'. Let's start with conversion from R1 to R3 space. The following script will add 500 coloured points to the document, all of which are sampled at regular intervals across the R1 parameter space of a curve object: ```vb Sub Main() Dim strCurveID strCurveID = Rhino.GetObject("Select a curve to sample", 4, True, True) If IsNull(strCurveID) Then Exit Sub Dim t Call Rhino.EnableRedraw(False) For t = 0.0 To 1.0 Step 0.002 Call AddPointAtR1Parameter(strCurveID, t) Next Call Rhino.EnableRedraw(True) End Sub Function AddPointAtR1Parameter(strCurveID, dblUnitParameter) AddPointAtR1Parameter = Null Dim crvDomain : crvDomain = Rhino.CurveDomain(strCurveID) If IsNull(crvDomain) Then Exit Function Dim dblR1Param dblR1Param = crvDomain(0) + dblUnitParameter * (crvDomain(1) - crvDomain(0)) Dim arrR3Point : arrR3Point = Rhino.EvaluateCurve(strCurveID, dblR1Param) If Not IsArray(arrR3Point) Then Exit Function Dim strPointID : strPointID = Rhino.AddPoint(arrR3Point) Call Rhino.ObjectColor(strPointID, ParameterColour(dblUnitParameter)) AddPointAtR1Parameter = strPointID End Function Function ParameterColour(dblParam) Dim RedComponent : RedComponent = 255 * dblParam If (RedComponent < 0) Then RedComponent = 0 If (RedComponent > 255) Then RedComponent = 255 ParameterColour = RGB(RedComponent, 0, 255 - RedComponent) End Function ``` For no good reason whatsoever, we'll start with the bottom most function:
Line Description
30 Standard out-of-the-box function declaration which takes a single double value. This function is supposed to return a colour which changes gradually from blue to red as dblParam changes from zero to one. Values outside of the range {0.0~1.0} will be clipped.
31 The red component of the colour we're going to return is declared here and assigned the naive value of 255 times the dblParam. Colour components must have a value between and including 0 and 255. If we attempt to construct a colour with lower or higher values, a run-time error will spoil the party.
32...33 Here's where we make sure the party can continue unimpeded.
35 Compute the colour gradient value. If dblParam equals zero we want blue (0,0,255) and if it equals one we want red (255,0,0). So the green component is always zero while blue and red see-saw between 0 and 255.
Now, on to function *AddPointAtR1Parameter()*. As the name implies, this function will add a single point in 3D world space based on the parameter coordinate of a curve object. In order to work correctly this function must know what curve we're talking about and what parameter we want to sample. Instead of passing the actual parameter which is bound to the curve domain (and could be anything) we're passing a unitized one. I.e. we pretend the curve domain is between zero and one. This function will have to wrap the required math for translating unitized parameters into actual parameters. Since we're calling this function a lot (once for every point we want to add), it is actually a bit odd to put all the heavy-duty stuff inside it. We only really need to perform the overhead costs of 'unitized parameter + actual parameter' calculation once, so it makes more sense to put it in a higher level function. Still, it will be very quick so there's no need to optimize it yet.
Line Description
14..15 Function declaration and default return value (Null in case things get fluffed and we need to abort).
17..18 Get the curve domain and check for Null. It will be Null if the ID does not represent a proper curve object. The Rhino.CurveDomain() method will return an array of two doubles which indicate the minimum and maximum t-parameters which lie on the curve.
21 Translate the unitized R1 coordinate into actual domain coordinates.
22 Evaluate the curve at the specified parameter. Rhino.EvaluateCurve() takes an R1 coordinate and returns an R3 coordinate.
25 Add the point, it will have default attributes.
26 Set the custom colour. This will automatically change the color-source attribute to By Object.
The distribution of R1 points on a spiral is not very enticing since it approximates a division by equal length segments in R3 space. When we run the same script on less regular curves it becomes easier to grasp what parameter space is all about: Let's take a look at an example which uses all parameter spaces we've discussed so far: ```vb Sub Main() Dim strSurfaceID strSurfaceID = Rhino.GetObject("Select a surface to sample", 8, True) If IsNull(strSurfaceID) Then Exit Sub Dim strCurveID strCurveID = Rhino.GetObject("Select a curve to measure", 4, True, True) If IsNull(strCurveID) Then Exit Sub Dim arrPts : arrPts = Rhino.DivideCurve(strCurveID, 500) Dim i Call Rhino.EnableRedraw(False) For i = 0 To UBound(arrPts) Call EvaluateDeviation(strSurfaceID, 1.0, arrPts(i)) Next Call Rhino.EnableRedraw(True) End Sub Function EvaluateDeviation(strSurfaceID, dblThreshold, arrSample) EvaluateDeviation = Null Dim arrR2Point arrR2Point = Rhino.SurfaceClosestPoint(strSurfaceID, arrSample) If IsNull(arrR2Point) Then Exit Function Dim arrR3Point : arrR3Point = Rhino.EvaluateSurface(strSurfaceID, arrR2Point) If IsNull(arrR3Point) Then Exit Function Dim dblDeviation : dblDeviation = Rhino.Distance(arrR3Point, arrSample) If dblDeviation <= dblThreshold Then EvaluateDeviation = True Exit Function End If Call Rhino.AddPoint(arrSample) Call Rhino.AddLine(arrSample, arrR3Point) EvaluateDeviation = False End Function ``` This script will compare a bunch of points on a curve to their projection on a surface. If the distance exceeds one unit, a line and a point will be added. First, the R1 points are translated into R3 coordinates so we can project them onto the surface, getting the R2 coordinate {u,v} in return. This R2 point has to be translated into R3 space as well, since we need to know the distance between the R1 point on the curve and the R2 point on the surface. Distances can only be measured if both points reside in the same number of dimensions, so we need to translate them into R3 as well. Told you it was a piece of cake...
Line Description
10 We're using the Rhino.DivideCurve() method to get all the R3 coordinates on the curve in one go. This saves us a lot of looping and evaluating.
24 Rhino.SurfaceClosestPoint() returns an array of two doubles representing the R2 point on the surface (in {u,v} coordinates) which is closest to the sample point.
27 Rhino.EvaluateSurface() in turn translates the R2 parameter coordinate into R3 world coordinates
30...38 Compute the distance between the two points and add geometry if necessary. This function returns True if the deviation is less than one unit, False if it is more than one unit and Null if something went wrong.
One more time just for kicks. We project the R1 parameter coordinate on the curve into 3D space (Step A), then we project that R3 coordinate onto the surface getting the R2 coordinate of the closest point (Step B). We evaluate the surface at R2, getting the R3 coordinate in 3D world space (Step C), and we finally measure the distance between the two R3 points to determine the deviation: Ok, that's it for now, time to go out and have a stiff drink. ## 7.4 Lines and Polylines You'll be glad to learn that (poly)lines are essentially the same as point-arrays. The only difference is that we treat the points as a series rather than an anonymous collection, which enables us to draw lines between them. There is some nasty stuff going on which might cause problems down the road so perhaps it's best to get it over with quick. There are several ways in which polylines can be manifested in openNURBS™ and thus in Rhino. There is a special polyline class which simply lists an array of ordered points. It has no overhead data so this is the simplest case. It's also possible for regular nurbs curves to behave as polylines when they have their degree set to 1. In addition, a polyline could also be a polycurve made up of line segments, polyline segments, degree=1 nurbs curves or a combination of the above. If you create a polyline using the _Polyline command, you will get a proper polyline objects as the Object Properties Details dialog on the left shows: The dialog claims an "Open polyline with 8 points". However, when we drag a control-point Rhino will automatically convert any curve to a Nurbs curve, as the image on the right shows. It is now an open nurbs curve of degree=1. From a geometric point of view, these two curves are identical. From a programmatic point of view, they are anything but. For the time being we will only deal with 'proper' polylines though; arrays of sequential coordinates. For purposes of clarification I've added two example functions which perform basic operations on polyline point-arrays. Compute the length of a polyline point-array: ```vb Function PolylineLength(ByRef arrVertices) PolylineLength = 0.0 Dim i For i = 0 To UBound(arrVertices)-1 PolylineLength = PolylineLength + Rhino.Distance(arrVertices(i), arrVertices(i+1)) Next End Function ``` Subdivide a polyline by adding extra vertices halfway all existing vertices: ```vb Function SubDividePolyline(ByRef arrV) Dim arrSubD() : ReDim arrSubD(2 * UBound(arrV)) Dim i For i = 0 To UBound(arrV)-1 'copy the original vertex location arrSubD(i * 2) = arrV(i) 'compute the average of the current vertex and the next one arrSubD(i * 2 + 1) = Array( (arrV(i)(0) + arrV(i+1)(0)) / 2.0, _ (arrV(i)(1) + arrV(i+1)(1)) / 2.0, _ (arrV(i)(2) + arrV(i+1)(2)) / 2.0) Next 'copy the last vertex (this is skipped by the loop) arrSubD(UBound(arrSubD)) = arrV(UBound(arrV)) SubDividePolyline = arrSubD End Function ``` I'm using the *ByRef* statement not because I want to tinker with the original point-arrays, but to avoid copying them whenever these functions are called. No rocket science yet, but brace yourself for the next bit... No rocket science yet, but brace yourself for the next bit... As you know, the shortest path between two points is a straight line. This is true for all our space definitions, from R1 to RN. However, the shortest path in R2 space is not necessarily the same shortest path in R3 space. If we want to connect two points on a surface with a straight line in R2, all we need to do is plot a linear course through the surface {u,v} space. (Since we can only add curves to Rhino which use 3D world coordinates, we'll need a fair amount of samples to give the impression of smoothness.) The thick red curve in the adjacent illustration is the shortest path in R2 parameter space connecting {A} and {B}. We can clearly see that this is definitely not the shortest path in R3 space. We can clearly see this because we're used to things happening in R3 space, which is why this whole R2/R3 thing is so thoroughly counter intuitive to begin with. The green, dotted curve is the actual shortest path in R3 space which still respects the limitation of the surface (I.e. it can be projected onto the surface without any loss of information). The following function was used to create the red curve; it creates a polyline which represents the shortest path from {A} to {B} in surface parameter space: ```vb Function GetR2PathOnSurface(strSurfaceID, intSegments, strPrompt1, strPrompt2) GetR2PathOnSurface = Null Dim ptStart, ptEnd ptStart = Rhino.GetPointOnSurface(strSurfaceID, strPrompt1) If IsNull(ptStart) Then Exit Function ptEnd = Rhino.GetPointOnSurface(strSurfaceID, strPrompt2) If IsNull(ptEnd) Then Exit Function If (Rhino.Distance(ptStart,ptEnd) = 0.0) Then Exit Function Dim uvA : uvA = Rhino.SurfaceClosestPoint(strSurfaceID, ptStart) Dim uvB : uvB = Rhino.SurfaceClosestPoint(strSurfaceID, ptEnd) Dim arrV() : ReDim arrV(intSegments) Dim i, t, u, v For i = 0 To intSegments t = i / intSegments u = uvA(0) + t*(uvB(0) - uvA(0)) v = uvA(1) + t*(uvB(1) - uvA(1)) arrV(i) = Rhino.EvaluateSurface(strSurfaceID, Array(u, v)) Next GetR2PathOnSurface = arrV End Function ```
Line Description
1 This function takes four arguments; the ID of the surface onto which to plot the shortest route, the number of segments for the path polyline and the prompts to use for picking the A and B point.
5 Prompt the user for the {A} point on the surface.
8 Prompt the user for the {B} point on the surface.
12...13 Project {A} and {B} onto the surface to get the respective R2 coordinates uvA and uvB.
15 Declare the array which is going to store all the polyline vertices..
17 Since this algorithm is segment-based, we know in advance how many vertices the polyline will have and thus how often we will have to sample the surface.
18 t is a value which ranges from 0.0 to 1.0 over the course of our loop
19...20 Use the current value of t to sample the surface somewhere in between uvA and uvB.
22 rs.EvaluateSurface() takes a {u} and a {v} value and spits out a 3D-world coordinate. This is just a friendly way of saying that it converts from R2 to R3.
We're going to combine the previous examples in order to make a real geodesic path routine in Rhino. This is a fairly complex algorithm and I'll do my best to explain to you how it works before we get into any actual code. First we'll create a polyline which describes the shortest path between {A} and {B} in R2 space. This is our base curve. It will be a very coarse approximation, only ten segments in total. We'll create it using the function on page 54. Unfortunately that function does not take closed surfaces into account. In the paragraph on nurbs surfaces we'll elaborate on this. Once we've got our base shape we'll enter the iterative part. The iteration consists of two nested loops, which we will put in two different functions in order to avoid too much nesting and indenting. We're going to write four functions in addition to the ones already discussed in this paragraph: 1. The main geodesic routine 2. ProjectPolyline() 3. SmoothPolyline() 4. GeodesicFit() The purpose of the main routine is the same as always; to collect the initial data and make sure the script completes as successfully as possible. Since we're going to calculate the geodesic curve between two points on a surface, the initial data consists only of a surface ID and two points in surface parameter space. The algorithm for finding the geodesic curve is a relatively slow one and it is not very good at making major changes to dense polylines. That is why we will be feeding it the problem in bite-size chunks. It is because of this reason that our initial base curve (the first bite) will only have ten segments. We'll compute the geodesic path for these ten segments, then subdivide the curve into twenty segments and recompute the geodesic, then subdivide into 40 and so on and so forth until further subdivision no longer results in a shorter overall curve. The *ProjectPolyline()* function will be responsible for making sure all the vertices of a polyline point-array are in fact coincident with a certain surface. In order to do this it must project the R3 coordinates of the polyline onto the surface, and then again evaluate that projection back into R3 space. This is called 'pulling'. The purpose of *SmoothPolyline()* will be to average all polyline vertices with their neighbours. This function will be very similar to the example on page 44, except it will be much simpler since we know for a fact we're not dealing with nurbs curves here. We do not need to worry about knots, weights, degrees and domains. *GeodesicFit()* is the essential geodesic routine. We expect it to deform any given polyline into the best possible geodesic curve, no matter how coarse and wrong the input is. The algorithm in question is a very naive solution to the geodesic problem and it will run much slower than Rhinos native _ShortPath command. The upside is that our script, once finished, will be able to deal with self-intersecting surfaces. The underlying theory of this algorithm is synonymous with the simulation of a contracting rubber band, with the one difference that our rubber band is not allowed to leave the surface. The process is iterative and though we expect every iteration to yield a certain improvement over the last one, the amount of improvement will diminish as we near the ideal solution. Once we feel the improvement has become negligible we'll abort the function. In order to simulate a rubber band we require two steps; smoothing and projecting. First we allow the rubber band to contract (it always wants to contract into a straight line between {A} and {B}). This contraction happens in R3 space which means the vertices of the polyline will probably end up away from the surface. We must then re-impose these surface constraints. These two operations have been hoisted into functions #2 and #3. The illustration depicts the two steps which compose a single iteration of the geodesic routine. The black polyline is projected onto the surface giving the red polyline. The red curve in turn is smoothed into the green curve. Note that the actual algorithm performs these two steps in the reverse order; smoothing first, projection second. We'll start with the simplest function: ```vb Sub ProjectPolyline(ByRef arrVertices, strSurfaceID) Dim arrProjPt, i For i = 1 To UBound(arrVertices)-1 arrProjPt = Rhino.BRepClosestPoint(strSurfaceID, arrVertices(i)) If Not IsNull(arrProjPt) Then arrVertices(i) = arrProjPt(0) End If Next End Sub ```
Line Description
1 Since we're going to deform the polyline which is passed to us, we might as well deform the original. That is why the arrVertices argument is declared ByRef. We will be changing the vertices directly. This sub is designed to fail, but if it crashes something is wrong elsewhere and we need to fix the bug there.
4 Since this is a specialized sub which we will only be using inside this script, we can skip projecting the first and last point. We can safely assume the polyline is open and that both endpoints will already be on the curve.
5 We ask Rhino for the closest point on the surface object given our polyline vertex coordinate. The reason why we do not use Rhino.SurfaceClosestPoint() is because BRepClosestPoint() takes trims into account. This is a nice bonus we can get for free. The native _ShortPath command does not deal with trims at all. We are of course not interested in aping something which already exists, we want to make something better.
6 If BRepClosestPoint() returned Null something went wrong after all. We cannot project the vertex in this case so we'll simply ignore it. We could of course short-circuit the whole operation after a failure like this, but I prefer to press on and see what comes out the other end.
7 The BRepClosestPoint() method returns a lot of information, not just the R2 coordinate. In fact it returns an array of data, the first element of which is the R3 closest point. This means we do not have to translate the uv coordinate into xyz ourselves. Huzzah! Assign it to the vertex and move on.
```vb Sub SmoothPolyline(ByRef arrVertices) Dim arrCopy : arrCopy = arrVertices Dim i, j For i = 1 To UBound(arrVertices)-1 For j = 0 To 2 arrVertices(i)(j) = (arrCopy(i-1)(j) + arrCopy(i)(j) + arrCopy(i+1)(j)) / 3.0 Next Next End Sub ```
Line Description
1 Since we need the original coordinates throughout the smoothing operation we cannot deform it directly. That is why we need to make a copy before we start messing about with coordinates. This code is fairly fool-proof so we're not even bothering to inform the caller whether or not it was a success. No return value means we'll have to use a subroutine instead of a function.
7 We iterate through all the internal vertices and also through the x,y and z components. Writing smaller functions will not make the code go faster, but it does means we just get to write less junk. Also, it means adjustments are easier to make afterwards since less code-rewriting is required. What we do here is average the x, y and z coordinates of the current vertex ('current' as defined by i) using both itself and its neighbours.
Time for the bit that sounded so difficult on the previous page, the actual geodesic curve fitter routine: ```vb Sub GeodesicFit(ByRef arrVertices, strSurfaceID, dblTolerance) Dim dblLength : dblLength = PolylineLength(arrVertices) Dim dblNewLength Do Call SmoothPolyline(arrVertices) Call ProjectPolyline(arrVertices, strSurfaceID) dblNewLength = PolylineLength(arrVertices) If (Abs(dblNewLength - dblLength) < dblTolerance) Then Exit Do dblLength = dblNewLength Loop End Sub ```
Line Description
1 Hah... that doesn't look so bad after all, does it? You'll notice that it's often the stuff which is easy to explain that ends up taking a lot of lines of code. Rigid mathematical and logical structures can typically be coded very efficiently. Again, ByRef for the actual coordinate array since we're mucking about with the thing directly. No use copying lists of points all over the place and back again. You'll notice this is a subroutine and thus lacks a return value which is perhaps a little odd. It certainly looks complex enough to deserve a return value. Still, since we're writing this script in one go we know that the function which uses this particular sub does not rely on return values. It simply evaluates the length of the polyline prior to and after calling this sub and decides where to go from there. If this subroutine completely and utterly fails, the polyline will not be changed which results in zero- difference lengths. That is the cue for the caller to abort anyway. This is arguably not a very good approach at writing code, since specialized functions like these are harder to re-use in other projects. I never blindly re-use any code ever, so this is does not concern me as an individual. But there is nothing that says I'm right and others are wrong. You are learning this from me and thus you are learning it my way. That's the best I can offer.
2...3 We'll be monitoring the progress of each iteration and once the curve no longer becomes noticeably shorter (where 'noticeable' is defined by the dblTolerance argument), we'll call the 'intermediate result' the 'final result' and return execution to the caller. In order to monitor this progress, we need to remember how long the curve was before we started; dblLength is created for this purpose.
5...13 Whenever you see a Do…Loop without any standard escape clause you should be on your toes. This is potentially an infinite loop. I have tested it rather thoroughly and have been unable to make it run more than 120 times. Experimental data is never watertight proof, the routine could theoretically fall into a stable state where it jumps between two solutions. If this happens, the loop will run forever. You are of course welcome to add additional escape clauses if you deem that necessary. You are of course welcome to add additional escape clauses if you deem that necessary.
6...7 Place the calls to the functions on page 56. These are the bones of the algorithm.
9 Compute the new length of the polyline.
10 Check to see whether or not it is worth carrying on.
12 Apparently it was, we need now to remember this new length as our frame of reference..
The main subroutine takes some explaining. It performs a lot of different tasks which always makes a block of code harder to read. It would have been better to split it up into more discrete chunks, but we're already using seven different functions for this script and I felt we are nearing the ceiling. Remember that splitting problems into smaller parts is a good way to organize your thoughts, but it doesn't actually solve anything. You'll need a find a good balance between splitting and lumping. ```vb Option Explicit Call GeodesicCurve() Sub GeodesicCurve() Dim strSurfaceID strSurfaceID = Rhino.GetObject("Select surface for geodesic curve solution", 8, True, True) If IsNull(strSurfaceID) Then Exit Sub Dim arrV arrV = GetPolylineOnSurface(strSurfaceID, 10, _ "Start of geodesic curve", "End of geodesic curve") If IsNull(arrV) Then Exit Sub Dim dblTolerance : dblTolerance = Rhino.UnitAbsoluteTolerance() / 10 Dim dblLength : dblLength = 1e300 Dim dblNewLength : dblNewLength = 0.0 Do Call Rhino.Prompt("Solving geodesic fit for " & UBound(arrV) & " samples") Call GeodesicFit(arrV, strSurfaceID, dblTolerance) dblNewLength = PolylineLength(arrV) If (Abs(dblNewLength - dblLength) < dblTolerance) Then Exit Do If (UBound(arrV) > 1000) Then Exit Do arrV = SubDividePolyline(arrV) dblLength = dblNewLength Loop Call Rhino.AddPolyline(arrV) Call Rhino.Print("Geodesic curve added with length: " & dblNewLength) End Sub ```
Line Description
5..7 Get the surface to be used in the geodesic routine.
9...13 Declare a variable which will store the polyline vertices. Even though this is an array, we do not declare it in that way, since the return value of GetPolylineOnSurface() is already an array so the conversion will happen automatically.
15 The tolerance used in our script will be 10% of the absolute tolerance of the document..
16...17 This loop also uses a length comparison in order to determine whether or not to continue. But instead of evaluating the length of a polyline before and after a smooth/project iteration, it measures the difference before and after a subdivide/geodesicfit iteration. The goal of this evaluation is to decide whether or not further elaboration will pay off. dblLength and dblNewLength are used in the same context as on the previous page.
20 Display a message in the command-line informing the user about the progress we're making. This script will run for quite some time so it's important not to let the user think the damn thing has crashed..
22 Place a call to the GeodesicFit() subroutine.
23...24 Compare the improvement in length, exit the loop when there's no progress of any value.
25 A safety-switch. We don't want our curve to become too dense.
27 A call to SubDividePolyline() will double the amount of vertices in the polyline. The newly added vertices will not be on the surface, so we must make sure to call GeodesicFit() at least once before we add this new polyline to the document.
31...32 Add the curve and print a message about the length.
## 7.5 Planes Planes are not genuine objects in Rhino, they are used to define a coordinate system in 3D world space. In fact, it's best to think of planes as vectors, they are merely mathematical constructs. Although planes are internally defined by a parametric equation, I find it easiest to think of them as a set of axes: A plane definition is an array of one point and three vectors, the point marks the origin of the plane and the vectors represent the three axes. There are some rules to plane definitions, I.e. not every combination of points and vectors is a valid plane. If you create a plane using one of the RhinoScript plane methods you don't have to worry about this, since all the bookkeeping will be done for you. The rules are as follows: 1. The axis vectors must be unitized (have a length of 1.0). 2. All axis vectors must be perpendicular to each other. 3. The x and y axis are ordered anti-clockwise. The illustration shows how rules #2 and #3 work in practise. ```vb Call PlaneExample() Sub PlaneExample() Dim ptOrigin : ptOrigin = Rhino.GetPoint("Plane origin") If IsNull(ptOrigin) Then Exit Sub Dim ptX : ptX = Rhino.GetPoint("Plane X-axis", ptOrigin) If IsNull(ptX) Then Exit Sub Dim ptY : ptY = Rhino.GetPoint("Plane Y-axis", ptOrigin) If IsNull(ptY) Then Exit Sub Dim dX : dX = Rhino.Distance(ptOrigin, ptX) Dim dY : dY = Rhino.Distance(ptOrigin, ptY) Dim arrPlane : arrPlane = Rhino.PlaneFromPoints(ptOrigin, ptX, ptY) Call Rhino.AddPlaneSurface(arrPlane, 1.0, 1.0) Call Rhino.AddPlaneSurface(arrPlane, dX, dY) End Sub ``` You will notice that all RhinoScript methods that require plane definitions make sure these demands are met, no matter how poorly you defined the input. The adjacent illustration shows how the *Rhino.AddPlaneSurface()* call on line 16 results in the red plane, while the *Rhino.AddPlaneSurface()* call on line 17 creates the yellow surface which has dimensions equal to the distance between the picked origin and axis points. We'll only pause briefly at plane definitions since planes, like vectors, are usually only constructive elements. In examples to come they will be used extensively so don't worry about getting the hours in. A more interesting script which uses the *Rhino.AddPlaneSurface()* method is the one below which populates a surface with so-called surface frames: ```vb Call WhoFramedTheSurface() Sub WhoFramedTheSurface() Dim idSurface : idSurface = Rhino.GetObject("Surface to frame", 8, True, True) If IsNull(idSurface) Then Exit Sub Dim intCount : intCount = Rhino.GetInteger("Number of iterations per direction", 20, 2) If IsNull(intCount) Then Exit Sub Dim uDomain : uDomain = Rhino.SurfaceDomain(idSurface, 0) Dim vDomain : vDomain = Rhino.SurfaceDomain(idSurface, 1) Dim uStep : uStep = (uDomain(1) - uDomain(0)) / intCount Dim vStep : vStep = (vDomain(1) - vDomain(0)) / intCount Dim u, v Dim pt Dim srfFrame Call Rhino.EnableRedraw(False) For u = uDomain(0) To uDomain(1) Step uStep For v = vdomain(0) To vDomain(1) Step vStep pt = Rhino.EvaluateSurface(idSurface, Array(u, v)) If Rhino.Distance(pt, Rhino.BrepClosestPoint(idSurface, pt)(0)) < 0.1 Then srfFrame = Rhino.SurfaceFrame(idSurface, Array(u, v)) Call Rhino.AddPlaneSurface(srfFrame, 1.0, 1.0) End If Next Next Call Rhino.EnableRedraw(True) End Sub ``` Frames are planes which are used to indicate geometrical directions. Both curves, surfaces and textured meshes have frames which identify tangency and curvature in the case of curves and {u} and {v} directions in the case of surfaces and meshes. The script above simply iterates over the {u} and {v} directions of any given surface and adds surface frame objects at all uv coordinates it passes.. On lines 9 to 12 we determine the domain of the surface in u and v directions and we derive the required stepsize from those limits. Line 19 and 20 form the main structure of the two-dimensional iteration. You can read such nested *For…Next* loops as "Iterate through all columns and inside every column iterate through all rows". Line 21 and 22 do something interesting which is not apparent in the adjacent illustration. When we are dealing with trimmed surfaces, those two lines prevent the script from adding planes in cut-away areas. By comparing the point on the (untrimmed) surface to it's projection onto the trimmed surface, we know whether or not the {uv} coordinate in question representsan actual point on the trimmed surface. The *Rhino.SurfaceFrame()* method returns a unitized frame whose axes point in the {u} and {v} directions of the surface. Note that the {u} and {v} directions are not necessarily perpendicular to each other, but we only add valid planes whose x and y axis are always at 90º, thus we ignore the direction of the v-component. ## 7.6 Circles, Ellipses, and Arcs Although the user is never confronted with parametric objects in Rhino, the openNURBS™ kernel has a certain set of mathematical primitives which are stored parametrically. Examples of these are cylinders, spheres, circles, revolutions and sum-surfaces. To highlight the difference between explicit (parametric) and implicit circles: When adding circles to Rhino through scripting, we can either use the Plane+Radius approach or we can use a 3-Point approach (which is internally translated into Plane+Radius). Now then, high time for a bit of math. Those of you who have not yet successfully repressed any childhood memory regarding math classes will remember that circles are tightly linked with sines and cosines; those lovable, undulating waves. We're going to create a script which packs circles with a predefined radius onto a sphere with another predefined radius. Now, before we start and I give away the answer, I'd like you to take a minute and think about this problem. Relax... take your time. The most obvious solution is to start stacking circles in horizontal bands and simply to ignore any vertical nesting which might take place. If you reached a similar solution and you want to keep feeling good about yourself I recommend you skip the following two sentences. This very solution has been found over and over again but for some reason Dave Rusin is usually given as the inventor. Even though Rusin's algorithm isn't exactly rocket science, it is worth discussing the mathematics in advance in order to prevent -or at least reduce- any confusion when I finally confront you with the code. Rusin's algorithm works as follows: 1. Solve how many circles you can evenly stack from north pole to south pole on the sphere. 2. For each of those bands, solve how many circles you can stack evenly around the sphere. 3. Do it. No wait, back up. The first thing to realize is how a sphere actually works. Only once we master spheres can be start packing them with circles. In Rhino, a sphere is a surface of revolution, which has two singularities and a single seam: The north pole (the black dot in the left most image) and the south pole (the white dot in the same image) are both on the main axis of the sphere and the seam (the thick edge) connects the two. In essence, a sphere is a rectangular plane bend in two directions, where the left and right side meet up to form the seam and the top and bottom edge are compressed into a single point each (a singularity). This coordinate system should be familiar since we use the same one for our own planet. However, our planet is divided into latitude and longitude degrees, whereas spheres are defined by latitude and longitude radians. The numeric domain of the latitude of the sphere starts in the south pole with -½π, reaches 0.0 at the equator and finally terminates with ½π at the north pole. The longitudinal domain starts and stops at the seam and travels around the sphere from 0.0 to 2π. Now you also know why it is called a 'seam' in the first place; it's where the domain suddenly jumps from one value to another, distant one. We cannot pack circles in the same way as we pack squares in the image above since that would deform them heavily near the poles, as indeed the squares are deformed. We want our circles to remain perfectly circular which means we have to fight the converging nature of the sphere. Assuming the radius of the circles we are about to stack is sufficiently smaller than the radius of the sphere, we can at least place two circles without thinking; one on the north- and one on the south pole. The additional benefit is that these two circles now handsomely cover up the singularities so we are only left with the annoying seam. The next order of business then, is to determine how many circles we need in order to cover up the seam in a straightforward fashion. The length of the seam is half of the circumference of the sphere (see yellow arrow in adjacent illustration). The total number of circles that fit between and including the two poles is the length of the seam divided by the diameter of the circles. This division however may yield a non-integer value and since we are not interested in stacking quarter circles, we need to round that value down to the nearest integer. This in turn probably means that we will not be able to cover up the seam entirely, but rest assured; if this was in fact the best of all possible worlds you would probably not be reading this primer to begin with. The image on the left shows the circles we've been able to stack so far and as you can see the seam and the poles are all covered up. Home stretch time, we've collected all the information we need in order to populate this sphere. The last step of the algorithm is to stack circles around the sphere, starting at every seam-circle. We need to calculate the circumference of the sphere at that particular latitude, divide that number by the diameter of the circles and once again find the largest integer value which is smaller than or equal to that result. The equivalent mathematical notation for this is: $$N_{count} = \left[\frac{2 \cdot R_{sphere} \cdot \cos{\phi}}{2 \cdot R_{circle}} \right]$$ in case you need to impress anyone… ```vb def DistributeCirclesOnSphere(): sphere_radius = rs.GetReal("Radius of sphere", 10.0, 0.01) if not sphere_radius: return circle_radius = rs.GetReal("Radius of circles", 0.05*sphere_radius, 0.001, 0.5*sphere_radius) if not circle_radius: return vertical_count = int( (math.pi*sphere_radius)/(2*circle_radius) ) rs.EnableRedraw(False) phi = -0.5*math.pi phi_step = math.pi/vertical_count while phi<0.5*math.pi: horizontal_count = int( (2*math.pi*math.cos(phi)*sphere_radius)/(2*circle_radius) ) if horizontal_count==0: horizontal_count=1 theta = 0 theta_step = 2*math.pi/horizontal_count while theta<2*math.pi-1e-8: circle_center = (sphere_radius*math.cos(theta)*math.cos(phi), sphere_radius*math.sin(theta)*math.cos(phi), sphere_radius*math.sin(phi)) circle_normal = rs.PointSubtract(circle_center, (0,0,0)) circle_plane = rs.PlaneFromNormal(circle_center, circle_normal) rs.AddCircle(circle_plane, circle_radius) theta += theta_step phi += phi_step rs.EnableRedraw(True) ```
Line Description
1...11 Collect all custom variables and make sure they make sense. We don't want spheres smaller than 0.01 units and we don't want circle radii larger than half the sphere radius.
14 Compute the number of circles from pole to pole. The Int() function in VBScript takes a double and returns only the integer part of that number. Hence it always rounds downwards as opposed to the CInt() (Convert to Integer) which rounds double values to the nearest integer.
16 phi and theta (Φ and Θ) are typically used to denote angles in spherical space and it's not hard to see why. I could have called them latitude and longitude respectively as well.
17 CircleCenter will be used to store the center point of the circles we're going to add. CircleNormal will be used to store the normal of the plane in which these circles reside. CirclePlane will be used to store the resulting plane definition.
20 The phi loop runs from -½π to ½π and we need to run it VerticalCount times.
21 This is where we calculate how many circles we can fit around the sphere on the current latitude. The math is the same as before, except we also need to calculate the length of the path around the sphere: 2π·R·Cos(Φ)
22 If it turns out that we can fit no circles at all at a certain latitude, we're going to get into trouble since we use the HorizontalCount variable as a denominator in the stepsize calculation on line 24. And even my mother knows you cannot divide by zero. However, we know we can always fit at least one circle.
24 This loop is essentially the same as the one on line 20, except it uses a different stepsize and a different numeric range ({0.0 <= theta < 2π} instead of {-½π <= phi <= +½π}). The more observant among you will have noticed that the domain of theta reaches from nought up to but not including two pi. If theta would go all the way up to 2π then there would be a duplicate circle on the seam. The best way of preventing a loop to reach a certain value is to subtract a fraction of the stepsize from that value, in this case I have simply subtracted a ludicrously small number (1e-8 = 0.00000001).
25 This is mathematically the most demanding line, and I'm not going to provide a full proof of why and how it works. This is the standard way of translating the spherical coordinates Φ and Θ into Cartesian coordinates x, y and z. Further information can be found on [MathWorld.com](http://mathworld.wolfram.com/)
29 Once we found the point on the sphere which corresponds to the current values of phi and theta, it's a piece of proverbial cake to find the normal of the sphere at that location. The normal of a sphere at any point on its surface is the inverted vector from that point to the center of the sphere. And that's what we do on line 29, we subtract the sphere origin (always (0,0,0) in this script) from the newly found {x,y,z} coordinate.
30...31 We can construct a plane definition from a single point on that plane and a normal vector and we can construct a circle from a plane definition and a radius value. Voila.
### 7.6.1 Ellipses Ellipses essentially work the same as circles, with the difference that you have to supply two radii instead of just one. Because ellipses only have two mirror symmetry planes and circles possess rotational symmetry (I.e. an infinite number of mirror symmetry planes), it actually does matter a great deal how the base-plane is oriented in the case of ellipses. A plane specified merely by origin and normal vector is free to rotate around that vector without breaking any of the initial constraints. The following example script demonstrates very clearly how the orientation of the base plane and the ellipse correspond. Consider the standard curvature analysis graph as shown on the left: It gives a clear impression of the range of different curvatures in the spline, but it doesn't communicate the helical twisting of the curvature very well. Parts of the spline that are near-linear tend to have a garbled curvature since they are the transition from one well defined bend to another. The arrows in the left image indicate these areas of twisting but it is hard to deduce this from the curvature graph alone. The upcoming script will use the curvature information to loft a surface through a set of ellipses which have been oriented into the curvature plane of the local spline geometry. The ellipses have a small radius in the bending plane of the curve and a large one perpendicular to the bending plane. Since we will not be using the strength of the curvature but only its orientation, small details will become very apparent. ```vb Call FlatWorm() Sub FlatWorm() Dim crvObject : crvObject = Rhino.GetObject("Pick a backbone curve", 4, True, False) If IsNull(crvObject) Then Exit Sub Dim intSamples : intSamples = Rhino.GetInteger("Number of cross sections", 100, 5) If IsNull(intSamples) Then Exit Sub Dim dblBendRadius : dblBendRadius = Rhino.GetReal("Bend plane radius", 0.5, 0.001) If IsNull(dblBendRadius) Then Exit Sub Dim dblPerpRadius : dblPerpRadius = Rhino.GetReal("Ribbon plane radius", 2.0, 0.001) If IsNull(dblPerpRadius) Then Exit Sub Dim crvDomain : crvDomain = Rhino.CurveDomain(crvObject) Dim t, N Dim arrCrossSections(), CrossSectionPlane Dim crvCurvature, crvPoint, crvTangent, crvPerp, crvNormal N = -1 For t = crvDomain(0) To crvDomain(1) + 1e-9 Step (crvDomain(1)-crvDomain(0))/intSamples N = N+1 crvCurvature = Rhino.CurveCurvature(crvObject, t) If IsNull(crvCurvature) Then crvPoint = Rhino.EvaluateCurve(crvObject, t) crvTangent = Rhino.CurveTangent(crvObject, t) crvPerp = Array(0,0,1) crvNormal = Rhino.VectorCrossProduct(crvTangent, crvPerp) Else crvPoint = crvCurvature(0) crvTangent = crvCurvature(1) crvPerp = Rhino.VectorUnitize(crvCurvature(4)) crvNormal = Rhino.VectorCrossProduct(crvTangent, crvPerp) End If CrossSectionPlane = Rhino.PlaneFromFrame(crvPoint, crvPerp, crvNormal) ReDim Preserve arrCrossSections(N) arrCrossSections(N) = Rhino.AddEllipse(CrossSectionPlane, dblBendRadius, dblPerpRadius) Next If N < 1 Then Exit Sub Call Rhino.AddLoftSrf(arrCrossSections) Call Rhino.DeleteObjects(arrCrossSections) End Sub ```
Line Description
15 crosssections is a list where we will store all our ellipse IDs. We need to remember all the ellipses we add since they have to be fed to the rs.AddLoftSrf() method. crosssectionplane will contain the base plane data for every individual ellipse, we do not need to remember these planes so we can afford to overwrite the old value with any new one. You'll notice I'm violating a lot of naming conventions from paragraph [2.3.5 Using Variables]. If you want to make something of it we can take it outside.
16 crvCurvature will be used to store all curvature data we receive from Rhino. crvPoint will be the point (R3) on the curve at the specified parameter t. crvTangent will be the tangent vector to the curve at t. crvPerp will be the vector that points in the direction of the curve bending plane. crvNormal will be the cross-product vector from crvTangent and crvPerp.
18 The variable N ("N" is often used as an integer counting variable) starts at minus one. This is a personal preference. Many programmers prefer to start N at zero and increment the value at the end of the loop, I prefer to start at -1 and increment at the start of the loop. My method is not better, or faster or less likely to crash. The only difference with the Start-With-Zero approach is that once the loop completes my N will indicate the upper-bound of the array rather than the upper-bound-plus-one.
19 We'll be walking along the curve with equal parameter steps. This is arguably not the best way, since we might be dealing with a polycurve which has wildly different parameterizations among its subcurves. This is only an example script though so I wanted to keep the code to a minimum. We're using the same trick as before in the header of the loop to ensure that the final value in the domain is included in the calculation. By extending the range of the loop by one billionth of a parameter we circumvent the 'double noise problem' which might result from multiple additions of doubles.
20 We're setting up N to be the correct upperbound indicator for our arrCrossSections array.
21 The Rhino.CurveCurvature() method returns a whole set of data to do with curvature analysis. However, it will fail on any linear segment (the radius of curvature is infinite on linear segments).
23...27 Hence, if it fails we have to collect the standard information in the old fashioned way. We also have to pick a crvPerp vector since none is available. We could perhaps use the last known one, or look at the local plane of the curve beyond the current -unsolvable- segment, but I've chosen to simply use a z-axis vector by default.
28...32 If the curve does have curvature at t, then we extract the required information directly from the curvature data.
35 Construct the plane for the ellipse.
36...37 Enlarge the array and store the new ellipse curve ID in the last element.
40...41 Create a lofted surface through all ellipses and delete the curves afterwards.
### 7.6.2 Arcs This section is called 'Circles, Ellipses and Arcs', which means we're still only two thirds of the way there. Medieval biblical triptychs typically depicted Paradise on the left, Earth in the middle and Hell on the right and the parallel is so overwhelming I cannot refrain from pointing it out. The bit about circles was about perfect (Paradise) stacking, the bit on ellipses was about finding imperfections (Earth) in curvature and the bit about arcs is going to be very hot indeed. We've reached that point in the process where words like "dotproduct" and "arccosine" can be found sharing the same sentence. Since the topic Arcs isn't much different from the topic Circles, I thought it would be a nice idea to drag in something extra. This something extra is what we programmers call "recursion" and it is without doubt the most exciting thing in our lives (we don't get out much). Recursion is the process of self-repetition. Like loops which are iterative and execute the same code over and over again, recursive functions call themselves and thus also execute the same code over and over again, but this process is hierarchical. It actually sounds harder than it is. One of the success stories of recursive functions is their implementation in binary trees which are the foundation for many search and classification algorithms in the world today. I'll allow myself a small detour on the subject of recursion because I would very much like you to appreciate the power that flows from the simplicity of the technique. Recursion is unfortunately one of those things which only become horribly obvious once you understand how it works. Imagine a box in 3D space which contains a number of points within its volume. This box exhibits a single behavioural pattern which is recursive. The recursive function evaluates a single conditional statement: {when the number of contained points exceeds a certain threshold value then subdivide into 8 smaller boxes, otherwise add yourself to the document}. It would be hard to come up with an easier If…Then…Else statement. Yet, because this behaviour is also exhibited by all newly created boxes, it bursts into a chain of recursion, resulting in the voxel spaces in the images below: The input in these cases was a large pointcloud shaped like the upper half of a sphere. There was also a dense spot with a higher than average concentration of points. Because of the approximating pattern of the subdivision, the recursive cascade results in these beautiful stacks. Trying to achieve this result without the use of recursion would entail a humongous amount of bookkeeping and many, many lines of code. Before we can get to the cool bit we have to write some of the supporting functions, which -I hate to say it- once again involve goniometry (the mathematics of angles). The problem: adding an arc using the start point, end point and start direction. As you will be aware there is a way to do this directly in Rhino using the mouse. In fact a brief inspection yields 14 different ways in which arcs can be drawn in Rhino manually and yet there are only two ways to add arcs through scripting: 1. Rhino.AddArc(Plane, Radius, Angle) 2. Rhino.AddArc3Pt(Point, Point, Point) The first way is very similar to adding circles using plane and radius values, with the added argument for sweep angle. The second way is also similar to adding circles using a 3-point system, with the difference that the arc terminates at the first and second point. There is no direct way to add arcs from point A to point B while constrained to a start tangent vector. We're going to have to write a function which translates the desired Start-End-Direction approach into a 3-Point approach. Before we tackle the math, let's review how it works: We start with two points {A} & {B} and a vector definition {D}. The arc we're after is the red curve, but at this point we don't know how to get there yet. Note that this problem might not have a solution if {D} is parallel or anti-parallel to the line from {A} to {B}. If you try to draw an arc like that in Rhino it will not work. Thus, we need to add some code to our function that aborts when we're confronted with unsolvable input. We're going to find the coordinates of the point in the middle of the desired arc {M}, so we can use the 3Point approach with {A}, {B} and {M}. As the illustration on the left indicates, the point in the middle of the arc is also on the line perpendicular from the middle {C} of the baseline. The halfway point on the arc also happens to lie on the bisector between {D} and the baseline vector. We can easily construct the bisector of two vectors in 3D space by process of unitizing and adding both vectors. In the illustration on the left the bisector is already pointing in the right direction, but it still hasn't got the correct length. We can compute the correct length using the standard "Sin-Cos-Tan right triangle rules": The triangle we have to solve has a 90º angle in the lower right corner, a is the angle between the baseline and the bisector, the length of the bottom edge of the triangle is half the distance between {A} and {B} and we need to compute the length of the slant edge (between {A} and {M}). The relationship between a and the lengths of the sides of the triangle is: $$\cos({\alpha})=\frac{0.5D}{?} \gg \frac{1}{\cos({\alpha})}=\frac{?}{0.5D} \gg \frac{0.5D}{\cos({\alpha})} = ?$$ We now have the equation we need in order to solve the length of the slant edge. The only remaining problem is cos(a). In the paragraph on vector mathematics (6.2 Points and Vectors) the vector dotproduct is briefly introduced as a way to compute the angle between two vectors. When we use unitized vectors, the arccosine of the dotproduct gives us the angle between them. This means the dotproduct returns the cosine of the angle between these vectors. This is a very fortunate turn of events since the cosine of the angle is exactly the thing we're looking for. In other words, the dotproduct saves us from having to use the cosine and arccosine functions altogether. Thus, the distance between {A} and {M} is the result of: ```vb (0.5 * Rhino.Distance(A, B)) / Rhino.VectorDotProduct(D, Bisector) ``` If you're really serious about this primer, it might be a good idea to try and write this function yourself before you sneak a peek at my version… just a thought. ```vb Function AddArcDir(ByVal ptStart, ByVal ptEnd, ByVal vecDir) AddArcDir = Null Dim vecBase : vecBase = Rhino.PointSubtract(ptEnd, ptStart) If Rhino.VectorLength(vecBase) = 0.0 Then Exit Function If Rhino.IsVectorParallelTo(vecBase, vecDir) Then Exit Function vecBase = Rhino.VectorUnitize(vecBase) vecDir = Rhino.VectorUnitize(vecDir) Dim vecBisector : vecBisector = Rhino.VectorAdd(vecDir, vecBase) vecBisector = Rhino.VectorUnitize(vecBisector) Dim dotProd : dotProd = Rhino.VectorDotProduct(vecBisector, vecDir) Dim midLength : midLength = (0.5 * Rhino.Distance(ptStart, ptEnd)) / dotProd vecBisector = Rhino.VectorScale(vecBisector, midLength) AddArcDir = Rhino.AddArc3Pt(ptStart, ptEnd, Rhino.PointAdd(ptStart, vecBisector)) End Function ```
Line Description
1 The ptStart argument indicates the start of the arc, ptEnd the end and vecDir the direction at ptStart. This function will behave just like the Rhino.AddArc3Pt() method, it takes a set of arguments and returns the identifier of the created curve object if successful. If no curve was added the function returns Null.
2 Set the return value to Null, in case we need to abort.
4 Create the baseline vector (from {A} to {B}), by subtracting {A} from {B}.
5 If {A} and {B} are coincident, no solution is possible. Actually, there is an infinite number of solutions so we wouldn't know which one to pick.
6 If vecDir is parallel (or anti-parallel) to the baseline vector, then no solution is possible at all.
8...9 Make sure all vector definitions so far are unitized.
11...12 Create the bisector vector and unitize it.
14 Compute the dotproduct between the bisector and the direction vector. Since the bisector is exactly halfway the direction vector and baseline vector (indeed, that is the point to its existence), we could just as well have calculated the dotproduct between it and the baseline vector.
15 Compute the distance between ptStart and the center point of the desired arc.
16 Resize the (unitized) bisector vector to match this length.
18 Create an arc using the start, end and midpoint arguments, return the ID.
We need this function in order to build a recursive tree-generator which outputs trees made of arcs. Our trees will be governed by a set of five variables but -due to the flexible nature of the recursive paradigm- it will be very easy to add more behavioural patterns. The growing algorithm as implemented in this example is very simple and doesn't allow a great deal of variation. The five base parameters are: 1. Propagation factor 2. Twig length 3. Twig length mutation 4. Twig angle 5. Twig angle mutation The propagation-factor is a numeric range which indicates the minimum and maximum number of twigs that grow at the end of every branch. This is a totally random affair, which is why it is called a "factor" rather than a "number". More on random numbers in a minute. The twig-length and twig-length-mutation variables control the -as you probably guessed- length of the twigs and how the length changes with every twig generation. The twig-angle and twig-angle-mutation work in a similar fashion. The actual recursive bit of this algorithm will not concern itself with the addition and shape of the twig-arcs. This is done by a supporting function which we have to write before we can start growing trees. The problem we have when adding new twigs, is that we want them to connect smoothly to their parent branch. We've already got the plumbing in place to make tangency continuous arcs, but we have no mechanism yet for picking the end-point. In our current plant-scheme, twig growth is controlled by two factors; length and angle. However, since more than one twig might be growing at the end of a branch there needs to be a certain amount of random variation to keep all the twigs from looking the same. The adjacent illustration shows the algorithm we'll be using for twig propagation. The red curve is the branch-arc and we need to populate the end with any number of twig-arcs. Point {A} and Vector {D} are dictated by the shape of the branch but we are free to pick point {B} at random provided we remain within the limits set by the length and angle constraints. The compete set of possible end-points is drawn as the yellow cone. We're going to use a sequence of Vector methods to get a random point {B} in this shape: 1. Create a new vector {T} parallel to {D} 2. Resize {T} to have a length between {Lmin} and {Lmax} 3. Mutate {T} to deviate a bit from {D} 4. Rotate {T} around {D} to randomize the orientation ```vb Function RandomPointInCone(ByVal Origin, ByVal Direction, _ ByVal MinDistance, ByVal MaxDistance, ByVal MaxAngle) Dim vecTwig vecTwig = Rhino.VectorUnitize(Direction) vecTwig = Rhino.VectorScale(vecTwig, MinDistance + Rnd() * (MaxDistance-MinDistance)) Dim MutationPlane MutationPlane = Rhino.PlaneFromNormal(Array(0,0,0), vecTwig) vecTwig = Rhino.VectorRotate(vecTwig, Rnd() * maxAngle, MutationPlane(1)) vecTwig = Rhino.VectorRotate(vecTwig, Rnd() * 360, Direction) RandomPointInCone = Rhino.PointAdd(Origin, vecTwig) End Function ```
Line Description
1 Origin is synonymous with point {A}. Direction is synonymous with vector {D}. MinDistance and MaxDistance indicate the length-wise domain of the cone. MaxAngle is a value which specifies the angle of the cone (in degrees, not radians)..
3...5 Create a new vector parallel to Direction and resize it to be somewhere between MinDistance and MaxDistance. I'm using the Rnd() function here which is a VBScript pseudo-random-number frontend. It always returns a random value between zero and one.
7...8 In order to mutate vecTwig, we need to find a parallel vector. since we only have one vector here we cannot directly use the Rhino.VectorCrossProduct() method, so we'll construct a plane and use its x-axis. This vector could be pointing anywhere, but always perpendicular to vecTwig.
10 Mutate vecTwig by rotating a random amount of degrees around the plane x-axis.
11 Mutate vecTwig again by rotating it around the Direction vector. This time the random angle is between 0 and 360 degrees.
12 Create the new point as inferred by Origin and vecTwig.
One of the definitions Wikipedia has to offer on the subject of recursion is: "In order to understand recursion, one must first understand recursion." Although this is obviously just meant to be funny, there is an unmistakable truth as well. The upcoming script is recursive in every definition of the word, it is also quite short, it produces visually interesting effects and it is quite clearly a very poor realistic plant generator. The perfect characteristics for exploration by trial-and-error. Probably more than any other example script in this primer this one is a lot of fun to play around with. Modify, alter, change, mangle, rape and bend it as you see fit and please send me any results you come up with. There is a set of rules to which any working recursive function must adhere. It must place at least one call to itself somewhere before the end and must have a way of exiting without placing any calls to itself. If the first condition is not met the function cannot be called recursive and if the second condition is not met it will call itself until time stops (or rather until the call-stack memory in your computer runs dry). Lo and behold! A mere 21 lines of code to describe the growth of an entire tree. ```vb Sub RecursiveGrowth(ByVal ptStart, ByVal vecDir, ByVal Props(), ByVal Generation) If Generation > Props(2) Then Exit Sub Dim ptGrow, vecGrow, newTwig Dim newProps : newProps = Props newProps(3) = Props(3) * Props(4) newProps(5) = Props(5) * Props(6) If newProps(5) > 90 Then newProps(5) = 90 Dim N, maxN maxN = CInt(Props(0) + Rnd() * (Props(1) - Props(0))) For N = 1 To maxN ptGrow = RandomPointInCone(ptStart, vecDir, 0.25*Props(3), Props(3), Props(5)) newTwig = AddArcDir(ptStart, ptGrow, vecDir) If Not IsNull(newTwig) Then vecGrow = Rhino.CurveTangent(newTwig, Rhino.CurveDomain(newTwig)(1)) Call RecursiveGrowth(ptGrow, vecGrow, newProps, Generation+1) End If Next End Sub ```
Line Description
1 A word on the function signature. Apart from the obvious arguments ptStart and vecDir, this function takes an array and a generation counter. The array contains all our growth variables. Since there are seven of them in total I didn't want to add them all as individual arguments. Also, this way it is easier to add parameters without changing function calls. The generation argument is an integer telling the function which twig generation it is in. Normally a recursive function does not need to know its depth in the grand scheme of things, but in our case we're making an exception since the number of generations is an exit threshold, which bring us to line #2.
2 Indeed so. If the current generation exceeds the generation limit (which is stored at the third element in the properties array) this function will abort without calling itself. Hence, it will take a step back on the recursive hierarchy. The properties array consists of the following items:
3 Declare a bunch of variables we'll be needing. ptGrow will store the end point of a particular twig. vecGrow will store the tangent at ptGrow for that new twig-arc and newTwig will store the ID of the newly added arc curve.
4 This is where we make a copy of the properties. You see, when we are going to grow new twigs, those twigs will be called with mutated properties, however we require the unmutated properties inside this function instance.
6...8 Mutate the copied properties. I.e. multiply the maximum-twig-length by the twig-length-mutation factor and do the same for the angle. We must take additional steps to ensure the angle doesn't go berserk so we're limiting the mutation to within the 90 degree realm.
11 maxN is an integer which indicated the number of twigs we are about to grow. maxN is randomly picked between the two allowed extremes (Props(0) and Props(1)). The Rnd() function generates a number between zero and one which means that maxN can become any value between and including the limits.
14 This is where we pick a point at random using the unmutated properties. The length constraints we're using is hard coded to be between the maximum allowed length and a quarter of the maximum allowed length. There is nothing in the universe which suggests a factor of 0.25, it is purely arbitrary. It does however have a strong effect on the shape of the trees we're growing. It means it is impossible to accurately specify a twig length. There is a lot of room for experimentation and change here.
15 We create the arc that belongs to this twig.
16 If the distance between ptStart and ptGrow was 0.0 or if vecDir was parallel to ptStart » ptGrow then the arc could not be added. We need to catch this problem in time.
17 We need to know the tangent at the end of the newly created arc curve. The domain of a curve consists of two values (a lower and an upper bound). Rhino.CurveDomain(newTwig)(1) will return the upper bound of the domain. This is the same as calling: Dim crvDomain : crvDomain = Rhino.CurveDomain(newTwig) vecGrow = Rhino.CurveTangent(newTwig, crvDomain(1))
18 Awooga! Awooga! A function calling itself! This is it! We made it! The thing to realize is that the call is now different. We're putting in different arguments which means this new function instance behaves differently than the current function instance.
Well, that's it. The show is over. You don't have to go home but you can't stay here. Oh, one last thing. It would have been possible to code this tree-generator in an iterative (using only For…Next loops) fashion. The tree would look the same even though the code would be very different (probably a lot more lines). The order in which the branches are added would very probably also have differed. The trees below are archetypal, digital trees, the one on the left generated using iteration, the one on the right generated using recursion. Note the difference in branch order. If you look carefully at the recursive function on the previous page you'll probably be able to work out where this difference comes from... A small comparison table for different setting combinations. Please note that the trees have a very high random component. ## 7.7 NURBS Curves Circles and arcs are all fine and dandy, but they cannot be used to draw freeform shapes. For that you need splines. The worlds most famous spline is probably the Bézier curve, which was developed in 1962 by the French engineer *Pierre Bézier* while he was working for Renault. Most splines used in computer graphics these days are variations on the Bézier spline, and they are thus a surprisingly recent arrival on the mathematical scene. Other ground-breaking work on splines was done by *Paul de Casteljau* at Citroën and *Carl de Boor* at General Motors. The thing that jumps out here is the fact that all these people worked for car manufacturers. With the increase in engine power and road quality, the automobile industry started to face new problems halfway through the twentieth century, one of which was aerodynamics. New methods were needed to design mass-production cars that had smooth, fluent curves as opposed to the tangency and curvature fractured shapes of old. They needed mathematically accurate, freely adjustable geometry. Enter splines. Before we start with NURBS curves (the mathematics of which are a bit too complex for a scripting primer) I'd like to give you a sense of how splines work in general and how Béziers work in particular. I'll explain the *de Casteljau* algorithm which is a very straightforward way of evaluating properties of simple splines. In practice, this algorithm will rarely be used since its performance is worse than alternate approaches, but due to its visual appeal it is easier to 'get a feel' for it. Splines limited to four control points were not the end of the revolution of course. Soon, more advanced spline definitions were formulated one of which is the NURBS curve. (Just to set the record straight; NURBS stands for Non-Uniform Rational [Basic/Basis] Spline and not Bézier-Spline as some people think. In fact, the Rhino help file gets it right, but I doubt many of you have read the glossary section, I only found out just now.) Bézier splines are a subset of NURBS curves, meaning that every Bézier spline can be represented by a NURBS curve, but not the other way around. Other curve types still in use today (but not available in Rhino) are Hermite, Cardinal, Catmull-Rom, Beta and Akima splines, but this is not a complete list. Hermite curves for example are used by the Bongo animation plug-in to smoothly transform objects through a number of keyframes. In addition to control point locations, NURBS curves have additional properties such as the degree, knot-vectors and weights. I'm going to assume that you already know how weight factors work (if you don't, it's in the Rhino help file under [NURBS About]) so I won't discuss them here. Instead, we'll continue with the correlation between degrees and knot-vectors. Every NURBS curve has a number associated with it which represents the degree. The degree of a curve is always a positive integer between and including 1 and 11. The degree of a curve is written as *DN*. Thus *D1* is a degree one curve and *D3* is a degree three curve. The table on the next page shows a number of curves with the exact same control-polygon but with different degrees. In short, the degree of a curve determines the range of influence of control points. The higher the degree, the larger the range. Degrees may be easy to understand, but I vividly remember having a hard time with the knot-vector concept when I first started programming. The first clear moment was when I realized that the knot-vector isn't a vector at all, it's in fact an array of numbers. The terminology is confusing, especially to non-math-PhDs, so whenever you see "knot vector" don't think "twisted-arrows-in-space", but think "list-of-numbers". As you will recall from the beginning of this section, a quadratic Bézier curve is defined by four control points. A quadratic NURBS curve however can be defined by any number of control points (any number larger than three that is), which in turn means that the entire curve consists of a number of connected pieces. The illustration below shows a *D3* curve with 10 control points. All the individual pieces have been given a different colour. As you can see each piece has a rather simple shape; a shape you could approximate with a traditional, four-point Bézier curve. Now you know why NURBS curves and other splines are often described as "piece-wise curves". The shape of the red piece is entirely dictated by the first four control points. In fact, since this is a *D3* curve, every piece is defined by four control points. So the second (orange) piece is defined by points {A; B; C; D}. The big difference between these pieces and a traditional Bézier curve is that the pieces stop short of the local control polygon. Instead of going all the way to {D}, the orange piece terminates somewhere in the vicinity of {C} and gives way to the green piece. Due to the mathematical magic of spline curves, the orange and green pieces fit perfectly, they have an identical position, tangency and curvature at point 4. As you may or may not have guessed at this point, the little circles between pieces represent the knot-vector of this curve. This *D3* curve has ten control points and twelve knots (0~11). This is not a coincidence, the number of knots follows directly from the number of points and the degree: $$K_N = P_N + (D-1)$$ Where $${K_N}$$ is the knot count, $${P_N}$$ is the point count and $${D}$$ is the degree. In the image on the previous page, the red and purple pieces do in fact touch the control polygon at the beginning and end, but we have to make some effort to stretch them this far. This effort is called "clamping", and it is achieved by stacking a lot of knots together. You can see that the number of knots we need to collapse in order to get the curve to touch a control-point is the same as the degree of the curve: A clamped curve always has a bunch of knots at the beginning and end (periodic curves do not, but we'll get to that later). If a curve has knot clusters on the interior as well, then it will touch one of the interior control points and we have a kinked curve. There is a lot more to know about knots, but I suggest we continue with some simple nurbs curves and let Rhino worry about the knot vector for the time being. ### 7.7.1 Control-Point Curves The _FilletCorners command in Rhino puts filleting arcs across all sharp kinks in a polycurve. Since fillet curves are tangent arcs, the corners have to be planar. All flat curves though can always be filleted as the image to the right shows. The input curve {A} has nine G0 corners (filled circles) which qualify for a filleting operation and three G1 corners (empty circles) which do not. Since each segment of the polycurve has a length larger than twice the fillet radius, none of the fillets overlap and the result is a predictable curve {B}. Since blend curves are freeform they are allowed to twist and curl as much as they please. They have no problem with non-planar segments. Our assignment for today is to make a script which inserts blend corners into polylines. We're not going to handle polycurves (with freeform curved segments) since that would involve quite a lot of math and logic which goes beyond this simple curve introduction. This unfortunately means we won't actually be making non-planar blend corners, but such is life. Full of disappointments. Especially for programmers. The logic of our BlendCorners script is simple: - Iterate though all segments of the polyline. - From the beginning of the segment $${A}$$, place an extra control point $${W_1}$$ at distance $${R}$$. - From the end of the segment $${B}$$, place an extra control point $${W_2}$$ at distance $${R}$$. - Put extra control-points halfway between $${A; W_1; W_2; B}$$. - Insert a $$D^5$$ nurbs curve using those new control points. Or, in graphic form: The first image shows our input curve positioned on a unit grid. The shortest segment has a length of 1.0, the longest segment a length of 6.0. If we're going to blend all corners with a radius of 0.75 (the circles in the second image) we can see that one of the edges has a conflict of overlapping blend radii. The third image shows the original control points (the filled circles) and all blend radius control points (the empty circles), positioned along every segment with a distance of {R} from its nearest neighbour. The two red control points have been positioned 0.5 units away (half the segment length) from their respective neighbours. Finally, the last image shows all the control points that will be added in between the existing control points. Once we have an ordered array of all control points (ordered as they appear along the original polyline) we can create a $$D^5$$ curve using *Rhino.AddCurve()*. ```vb Sub BlendCorners() Dim idPolyline : idPolyline = Rhino.GetObject("Polyline to blend", 4, True, True) Dim arrV, newV() : arrV = Rhino.PolylineVertices(idPolyline) Dim dblRadius : dblRadius = Rhino.GetReal("Blend radius", 1.0, 0.0) Dim A, B, W1, W2, i, N : N = -1 Dim vecSegment For i = 0 To UBound(arrV)-1 A = arrV(i) B = arrV(i+1) vecSegment = Rhino.PointSubtract(B, A) vecSegment = Rhino.VectorUnitize(vecSegment) If dblRadius < (0.5*Rhino.Distance(A, B)) Then vecSegment = Rhino.VectorScale(vecSegment, dblRadius) Else vecSegment = Rhino.VectorScale(vecSegment, 0.5 * Rhino.Distance(A, B)) End If W1 = Rhino.VectorAdd(A, vecSegment) W2 = Rhino.VectorSubtract(B, vecSegment) ReDim Preserve newV(N+6) newV(N+1) = A newV(N+2) = Between(A, W1) newV(N+3) = W1 newV(N+4) = Between(W1, W2) newV(N+5) = W2 newV(N+6) = Between(W2, B) N = N+6 Next ReDim Preserve newV(N+1) newV(N+1) = arrV(UBound(arrV)) Call Rhino.AddCurve(newV, 5) Call Rhino.DeleteObject(idPolyline) End Sub Function Between(ByVal A, ByVal B) Between = Array((A(0)+B(0))/2, (A(1)+B(1))/2, (A(2)+B(2))/2) End Function ```
Line Description
2...4 I've removed all IsNull() checks to reduce the number of coded lines. Only I (David Rutten) am allowed to be this careless..
6 Declare all variables that will be used to store control point locations and indices. Names are identical to the images on the previous page. i and N are iteration variables.
7 vecSegment is a scaled vector that points from A to B with a length of dblRadius.
9 Begin a loop for each segment in the polyline.
10...11 Store A and B coordinates for easy reference.
13...20 Calculate the vecSegment vector. Typically this vector has length dblRadius, but if the current polyline segment is too short to contain two complete radii, then adjust the vecSegment accordingly.
22...23 Calculate W1 and W2.
25...32 Resize the newV() array and store all points (except B).
35...36 Append the last point of the polyline to the newV() array. We've omitted B everywhere because the A of the next segment has the same location and we do not want coincident control-points. The last segment has no next segment, so we need to make sure B is included this time.
38 Create a new D5 nurbs curve.
42...44 A utility function which averages two 3D point coordinates.
### 7.7.2 Interpolated Curves When creating control-point curves it is very difficult to make them go through specific coordinates. Even when tweaking control-points this would be an arduous task. This is why commands like* _HBar* are so important. However, if you need a curve to go through many points, you're better off creating it using an interpolated method rather than a control-point method. The *_InterpCrv* and * _InterpCrvOnSrf* commands allow you to create a curve that intersects any number of 3D points and both of these methods have an equivalent in RhinoScript. To demonstrate, we're going to create a script that creates iso-distance-curves on surfaces rather than the standard iso-parameter-curves, or "isocurves" as they are usually called. If you're an old-time Rhino user you might recall that before Rhino3 "isocurves" were referred to as "isoparms" which is short for "isoparameters" ("iso" originates from the Greek (Isos) and it means "equal", as in *isotherm*: equal temperature, isobar: equal pressure and isochromatic: equal colour). Isocurves thus connect all the points in surface space that share a similar u or v value. Because the progression of the domain of a surface is not linear (it might be compressed in some places and stretched in others, especially near the edges where the surface has to be clamped), the distance between isocurves is not guaranteed to be identical either. The description of our algorithm is very straightforward, but I promise you that the actual script itself will be the hardest thing you've ever done. Enough foreplay. Our script will take any base surface (image A) and extract a number of isocurves (image B). Then, every isocurve is trimmed to a specific length (image C) and the end-points are connected to give the iso-distance-curve (the red curve in image D). Note that we are using isocurves in the v-direction to calculate the iso-distance-curve in the u-direction. This way, it doesn't matter much that the spacing of isocurves isn't distributed equally. Also note that this method is only useful for offsetting surface edges as opposed to *_OffsetCrvOnSrf* which can offset any curve. We can use the RhinoScript methods *Rhino.ExtractIsoCurve()* and *Rhino.AddInterpCrvOnSrf()* for steps B and D, but step C is going to take some further thought. It is possible to divide the extracted isocurve using a fixed length, which will give us a whole array of points, the second of which marks the proper solution: In the example above, the curve has been divided into equal length segments of 5.0 units each. The red point (the second item in the collection) is the answer we're looking for. All the other points are of no use to us, and you can imagine that the shorter the distance we're looking for, the more redundant points we get. Under normal circumstances I would not think twice and simply use the *Rhino.DivideCurveLength()* method and damn the expense. However, these are not normal circumstances and I've got a reputation to consider. That is why I'll take this opportunity to introduce you to one of the most ubiquitous, popular and prevalent algorithms in the field of programming today: binary searching. Imagine you have an array of integers which is -say- ten thousand items long and you want to find the number closest to sixteen. If this array is unordered (as opposed to sorted) , like so: > {-2, -10, 12, -400, 80, 2048, 1, 10, 11, -369, 4, -500, 1548, 8, … , 13, -344} you have pretty much no option but to compare every item in turn and keep a record of which one is closest so far. If the number sixteen doesn't occur in the array at all, you'll have to perform ten thousand comparisons before you know for sure which number was closest to sixteen. This is known as a worst-case performance, the best-case performance would be a single comparison since sixteen might just happen to be the first item in the array... if you're lucky. The method described above is known as a list-search and it is a pretty inefficient way of searching a large dataset and since searching large datasets is something that we tend to do a lot in computational science, plenty research has gone into speeding things up. Today there are so many different search algorithms that we've had to put them into categories in order to keep a clear overview. However, pretty much all efficient searching algorithms rely on the input list being sorted, like so: > {-500, -400, -369, -344, -10, -2, 1, 4, 8, 10, 11, 12, 13, 80, … , 1548, 2048} Once we have a sorted list it is possible to improve our worst case performance by orders of magnitude. For example, consider a slightly more advanced list-search algorithm which aborts the search once results start to become worse. Like the original list-search it will start at the first item {-500}, then continue to the second item {-400}. Since {-400} is closer to sixteen than {-500}, there is every reason to believe that the next item in the list is going to be closer still. This will go on until the algorithm has hit the number thirteen. Thirteen is already pretty close to sixteen but there is still some wiggle room so we cannot be absolutely sure ({14; 15; 16; 17; 18} are all closer and {19} is equally close). However, the next number in the array is {80} which is patently a much, much worse result than thirteen. Now, since this array is sorted we can be sure that every number after {80} is going to be worse still so we can safely abort our search knowing that thirteen is the closest number. Now, if the number we're searching for is near the beginning of the array, we'll have a vast performance increase, if it's near the end, we'll have a small performance increase. On average though, the sorted-list-search is twice as fast as the old-fashioned-list-search. Binary-searching eats sorted-list-searching algorithms for breakfast. Let us return to our actual problem to see how binary-searching works; find the point on a curve that marks a specific length along the curve. In the image below, the point we are looking for has been indicated with a small yellow tag, but of course we don't know where it is when we begin our search. Instead of starting at the beginning of the curve, we start halfway between {tmin} and {tmax} (halfway the domain of the curve). Since we can ask Rhino what the length is of a certain curve subdomain we can calculate the length from {tmin} to {1}. This happens to be way too much, we're looking for something less than half this length. Thus we divide the bit the between {tmin} and {1} in half yet again, giving us {2}. We again measure the distance between {tmin} and {2}, and see that again we're too high, but this time only just. We keep on dividing the remainder of the domain in half until we find a value {6} which is close enough for our purposes: This is an example of the simplest implementation of a binary-search algorithm and the performance of binary searching is *O*(log n) which is a fancy way of saying that it's fast. Really, really fast. And what's more, when we enlarge the size of the collection we're searching, the time taken to find an answer doesn't increase in a similar fashion (as it does with list-searching). Instead, it becomes relatively faster and faster as the size of the collection grows. For example, if we double the size of the array we're searching to 20.000 items, a list-search algorithm will take twice as long to find the answer, whereas a binary-searcher only takes ~1.075 times as long. More on algorithm performances in the chapter on optimization. The theory of binary searching might be easy to grasp (maybe not right away, but you'll see the beauty eventually), any practical implementation has to deal with some annoying, code-bloating aspects. For example, before we start a binary search operation, we must make sure that the answer we're looking for is actually contained within the set. In our case, if we're looking for a point {P} on the curve {C} which is 100.0 units away from the start of {C}, there exists no answer if {C} is shorter than 100.0 itself. Also, since we're dealing with a parameter domain as opposed to a list of integers, we do not have an actual array listing all the possible values. This array would be too big to fit in the memory of your computer. Instead, all we have is the knowledge that any number between and including {tmin} and {tmax} is theoretically possible. Finally, there might not exist an exact answer. All we can really hope for is that we can find an answer within tolerance of the exact length. Many operations in computational geometry are tolerance bound, sometimes because of speed issues (calculating an exact answer would take far too long), sometimes because an exact answer cannot be found (there is simply no math available, all we can do is make a set of guesses each one progressively better than the last). At any rate, here's the binary-search script I came up with, I'll deal with the inner workings afterwards: ```vb Function BSearchCurve(ByVal idCrv, ByVal Length, ByVal Tolerance) BSearchCurve = Null Dim crvLength : crvLength = Rhino.CurveLength(idCrv) If crvLength < Length Then Exit Function Dim tmin : tmin = Rhino.CurveDomain(idCrv)(0) Dim tmax : tmax = Rhino.CurveDomain(idCrv)(1) Dim t0, t1, t t0 = tmin t1 = tmax Dim dblLocalLength Do t = 0.5 * (t0+t1) dblLocalLength = Rhino.CurveLength(idCrv, , Array(tmin, t)) If Abs(dblLocalLength - Length) < Tolerance Then Exit Do If dblLocalLength < Length Then t0 = t Else t1 = t End If Loop BSearchCurve = t End Function ```
Line Description
1 Note that this is not a complete script, it is only the search function. The complete script is supplied in the article archive. This function takes a curve ID, a desired length and a tolerance. The return value is Null if no solution exists (i.e. if the curve is shorter than Length) or otherwise the parameter that marks the desired length.
2 Set the default return value.
4 Ask Rhino for the total curve length.
5 Make sure the curve is longer than Length. If it isn't, abort.
7...8 Store the minimum and maximum parameters of this curve domain. If you're confused about me calling the Rhino.CurveDomain() function twice instead of just once and store the resulting array, you may congratulate yourself. It would indeed be faster to not call the same method twice in a row. However, since lines 7 and 8 are not inside a loop, they will only execute once which reduces the cost of the penalty. 99% of the time spend by this function is because of lines 16~25, if we're going to be zealous about speed, we should focus on this part of the code.
10 t0, t1 and t will be the variables used to define our current subdomain. t0 will mark the lower bound and t1 the upper bound. t will be halfway between t0 and t1.
11...12 We need to start with the whole curve in mind, so t0 and t1 will be similar to tmin and tmax.
15 Since we do not know in advance how many steps our binary searcher is going to take, we have to use an infinite loop.
16 Calculate t (always exactly in the middle of {t0, t1}
18 Calculate the length of the subcurve from the start of the curve (tmin) to our current parameter (t).
19 If this length is close enough to the desired length, then we are done and we can abort the infinite loop. Abs() -in case you were wondering- is a VBScript function that returns the absolute (non-negative) number. This means that the Tolerance argument works equally strong in both directions, which is what you'd usually want.
21...25 This is the magic bit. Looks harmless enough doesn't it? What we do here is adjust the subdomain based on the result of the length comparison. If the length of the subcurve {tmin, t} is shorter than Length, then we want to restrict ourself to the lower half of the old subdomain. If, on the other hand, the subcurve length is shorter than Length, then we want the upper half of the old domain. Notice how much more compact programming code is compared to English?
28 Return the solved t-parameter.
I have unleashed this function on a smooth curve with a fairly well distributed parameter space (i.e. no sudden jumps in parameter "density") and the results are listed below. The length of the total curve was 200.0 mm and I wanted to find the parameter for a subcurve length of 125.0 mm. My tolerance was set to 0.0001 mm. As you can see it took 18 refinement steps in the *BSearchCurve()* function to find an acceptable solution. Note how fast this algorithm homes in on the correct value, after just 6 steps the remaining error is less than 1%. Ideally, with every step the accuracy of the guess is doubled, in practise however you're unlikely to see such a neat progression. In fact, if you closely examine the table, you'll see that sometimes the new guess overshoots the solution so much it actually becomes worse than before (like between steps #9 and #10). I've greyed out the subdomain bound parameters that remained identical between two adjacent steps. Just like the image on page 79 you can see that sometimes multiple steps in the same direction are required. Now for the rest of the script as outlines on page 78: ```vb Sub EquiDistanceOffset() Dim idSrf : idSrf = Rhino.GetObject("Pick surface to offset", 8, True, True) If IsNull(idSrf) Then Exit Sub Dim dblOffset : dblOffset = Rhino.GetReal("Offset distance", 1.0, 0.0) If IsNull(dblOffset) Then Exit Sub Dim uDomain uDomain = Rhino.SurfaceDomain(idSrf, 0) Dim uStep, u, t uStep = (uDomain(1) - uDomain(0)) / 50 'This means we'll create 50 isocurves Dim arrOffsetVertices() Dim VertexCount VertexCount = -1 Dim idIsoCurves, idIsoCurve Call Rhino.EnableRedraw(False) For u = uDomain(0) To uDomain(1) + (0.5*uStep) Step uStep 'Rhino.ExtractIsoCurves() returns an array, but in our case it is always just one item idIsoCurves = Rhino.ExtractIsoCurve(idSrf, Array(u, 0), 1) If Not IsNull(idIsoCurves) Then idIsoCurve = idIsoCurves(0) 'Use only the first curve in the set t = BSearchCurve(idIsoCurve, dblOffset, 0.001) 'Call our binary searcher If Not IsNull(t) Then 'If we have a solution, append it to the vertex-array VertexCount = VertexCount+1 ReDim Preserve arrOffsetVertices(VertexCount) arrOffsetVertices(VertexCount) = Rhino.EvaluateCurve(idIsoCurve, t) End If 'Clean up the isocurves Call Rhino.DeleteObjects(idIsoCurves) End If Next If VertexCount > 0 Then 'If we have more than one point, we can add a curve Call Rhino.AddInterpCrvOnSrf(idSrf, arrOffsetVertices) End If Call Rhino.EnableRedraw(True) End Sub ``` If I've done my job so far, the above shouldn't require any explanation. All of it is out-of-the-box, run-of-the-mill, garden-variety, straight-laced scripting code. The image on the right shows the result of the script, where offset values are all multiples of 10. The dark green lines across the green strip (between offsets 80.0 and 90.0) are all exactly 10.0 units long. ### 7.7.3 Geometric Curve Properties Since curves are geometric objects, they possess a number of properties or characteristics which can be used to describe or analyze them. For example, every curve has a starting coordinate and every curve has an ending coordinate. When the distance between these two coordinates is zero, the curve is closed. Also, every curve has a number of control-points, if all these points are located in the same plane, the curve as a whole is planar. Some properties apply to the curve as a whole, others only apply to specific points on the curve. For example, planarity is a global property while tangent vectors are a local property. Also, some properties only apply to some curve types. So far we've dealt with lines, polylines, circles, ellipses, arcs and nurbs curves: The last available curve type in Rhino is the polycurve, which is nothing more than an amalgamation of other types. A polycurve can be a series of line curves for example, in which case it behaves similarly to a polyline. But it can also be a combination of lines, arcs and nurbs curves with different degrees. Since all the individual segments have to touch each other (G0 continuity is a requirement for polycurve segments), polycurves cannot contain closed segments. However, no matter how complex the polycurve, it can always be represented by a nurbs curve. *All* of the above types can be represented by a nurbs curve. The difference between an actual circle and a nurbs-curve-that-looks-like-a-circle is the way it is stored. A nurbs curve doesn't have a *Radius* property for example, nor a *Plane* in which it is defined. It is possible to reconstruct these properties by evaluating derivatives and tangent vector and frames and so on and so forth, but the data isn't readily available. In short, nurbs curves lack some global properties that other curve types do have. This is not a big issue, it's easy to remember what properties a nurbs curve does and doesn't have. It is much harder to deal with local properties that are not continuous. For example, imagine a polycurve which has a zero-length line segment embedded somewhere inside. The *t*-parameter at the line beginning is a different value from the t-parameter at the end, meaning we have a curve subdomain which has zero length. It is impossible to calculate a normal vector inside this domain: This polycurve consists of five curve segments (a nurbs-curve, a zero-length line-segment, a proper line-segment, a 90° arc and another nurbs-curve respectively) all of which touch each other at the indicated t-parameters. None of them are tangency continuous, meaning that if you ask for the tangent at parameter {t3}, you might either get the tangent at the end of the purple segment or the tangent at the beginning of the green segment. However, if you ask for the tangent vector halfway between {t1} and {t2}, you get nothing. The curvature data domain has an even bigger hole in it, since both line-segments lack any curvature: When using curve properties such as tangents, curvature or perp-frames, we must always be careful to not blindly march on without checking for property discontinuities. An example of an algorithm that has to deal with this would be the *_CurvatureGraph* in Rhino. It works on all curve types, which means it must be able to detect and ignore linear and zero-length segments that lack curvature. One thing the *_CurvatureGraph* command does not do is insert the curvature graph objects, it only draws them on the screen. We're going to make a script that inserts the curvature graph as a collection of lines and interpolated curves. We'll run into several issues already outlined in this paragraph. In order to avoid some G continuity problems we're going to tackle the problem span by span. In case you haven't suffered left-hemisphere meltdown yet; the shape of every knot-span is determined by a certain mathematical function known as a polynomial and is (in most cases) completely smooth. A span-by-span approach means breaking up the curve into its elementary pieces, as shown on the left: This is a polycurve object consisting of seven pieces; lines {A; C; E}, arcs {B; D} and nurbs curves {F; G}. When we convert the polycurve to a nurbs representation we get a degree 5 nurbs curve with 62 pieces (knot-spans). Since this curve was made by joining a bunch of other curves together, there are kinks between all individual segments. A kink is defined as a grouping of identical knots on the interior of a curve, meaning that the curve actually intersects one of its interior control-points. A kink therefore has the potential to become a sharp crease in an otherwise smooth curve, but in our case all kinks connect segments that are G1 continuous. The kinks have been marked by white circles in the image on the right. As you can see there are also kinks in the middle of the arc segments {B; D}, which were there before we joined the curves together. In total this curve has ten kinks, and every kink is a grouping of five similar knot parameters (this is a D5 curve). Thus we have a sum-total of 40 zero-length knot-spans. Never mind about the math though, the important thing is that we should prepare for a bunch of zero-length spans so we can ignore them upon confrontation. The other problem we'll get is the property evaluation issue I talked about on the previous page. On the transition between knots the curvature data may jump from one value to another. Whenever we're evaluating curvature data near knot parameters, we need to know if we're coming from the left or the right. I'm sure all of this sounds terribly complicated. In fact I'm sure it is terribly complicated, but these things should start to make sense. It is no longer enough to understand how scripts work under ideal circumstances, by now, you should understand why there are no ideal circumstances and how that affects programming code. Since we know exactly what we need to do in order to mimic the *_CurvatureGraph* command, we might as well bite the bullet and start at the bottom. The first thing we need is a function that creates a curvature graph on a subcurve, then we can call this function with the knot parameters as sub-domains in order to generate a graph for the whole curve: Our function will need to know the ID of the curve in question, the subdomain {t0; t1}, the number of samples it is allowed to take in this domain and the scale of the curvature graph. The return value should be a collection of object IDs which were inserted to make the graph. This means all the perpendicular red segments and the dashed black curve connecting them. ```vb Function AddCurvatureGraphSection(ByVal idCrv, ByVal t0, ByVal t1, ByVal Samples, ByVal Scale) AddCurvatureGraphSection = Null If (t1 - t0) <= 0.0 Then Exit Function Dim arrA() : ReDim arrA(Samples) Dim arrB() : ReDim arrB(Samples) Dim arrObjects : ReDim arrObjects(Samples+1) Dim cData, cVector Dim t, tStep, N N = -1 tStep = (t1-t0) / Samples For t = t0 To (t1 + (0.5*tStep)) Step tStep If (t >= t1) Then t = (t1 - 1e-10) N = N+1 cData = Rhino.CurveCurvature(idCrv, t) If IsNull(cData) Then arrA(N) = Rhino.EvaluateCurve(idCrv, t) arrB(N) = arrA(N) arrObjects(N) = "" Else cData(4) = Rhino.VectorScale(cData(4), Scale) arrA(N) = cData(0) arrB(N) = Rhino.VectorSubtract(cData(0), cData(4)) arrObjects(N) = Rhino.AddLine(arrA(N), arrB(N)) End If Next arrObjects(Samples+1) = Rhino.AddInterpCurve(arrB) AddCurvatureGraphSection = arrObjects End Function ```
Line Description
3 Check for a null span, this happens inside kinks.
5...6 arrA() and arrB() will hold the start and end points of the perpendicular segments.
7 arrObjects() will hold the IDs of the perpendicular lines, and the connecting curve.
13 Determine a step size for our loop (Subdomain length / Sample count)
14 Define the loop and make sure we always process the final parameter by increasing the threshold with half the step size.
15 Make sure t does not go beyond t1, since that might give us the curvature data of the next segment.
20...22 In case of a curvature data discontinuity, do not add a line segment but append an empty ID instead.
24...27 Compute the A and B coordinates, append them to the appropriate array and add the line segment.
Now, we need to write a utility function that applies the previous function to an entire curve. There's no rocket science here, just an iteration over the knot-vector of a curve object: ```vb Function AddCurvatureGraph(ByVal idCrv, ByVal SpanSamples, ByVal Scale) Dim allGeometry, tmpGeometry Dim i, K allGeometry = Array() K = Rhino.CurveKnots(idCrv) For i = 0 To UBound(K)-1 tmpGeometry = AddCurvatureGraphSection(idCrv, K(i), K(i+1), SpanSamples, Scale) If Not IsNull(tmpGeometry) Then allGeometry = Rhino.JoinArrays(allGeometry, tmpGeometry) End If Next Call Rhino.AddObjectsToGroup(allGeometry, Rhino.AddGroup()) AddCurvatureGraph = allGeometry End Function ```
Line Description
2 allGeometry will be a list of all IDs generated by repetitive calls to AddCurvatureGraphSection()
3 knots is the knot vector of the nurbs representation of idCrv.
5 Since we're going to append a bunch of arrays to allObjects, we must make sure that it is an empty array before we start
8 We want to iterate over all knot spans, meaning we have to iterate over all (except the last) knot in the knot vector. Hence the minus one at the end.
9 Place a call to AddCurvatureGraphSection() and store all resulting IDs in tmpGeometry.
11 If the result of AddCurvatureGraphSection() is not Null, then append all items in tmpGeometry to allGeometry using Rhino.JoinArrays().
16 Put all created objects into a new group.
The last bit of code we need to write is a bit more extensive then we've done so far. Until now we've always prompted for a number of values before we performed any action. It is actually far more user-friendly to present the different values as options in the command line while drawing a preview of the result. UI code tends to be very beefy, but it rarely is complex. It's just irksome to write because it always looks exactly the same. In order to make a solid command-line interface for your script you have to do the following: 1. Reserve a place where you store all your preview geometry 2. Initialize all settings with sensible values 3. Create all preview geometry using the default settings 4. Display the command line options 5. Parse the result (be it escape, enter or an option or value string) 6. Select case through all your options 7. If the selected option is a setting (as opposed to options like "Cancel" or "Accept") then display a prompt for that setting 8. Delete all preview geometry 9. Generate new preview geometry using the changed settings. ```vb Sub CreateCurvatureGraph() Dim idCurves : idCurves = Rhino.GetObjects("Curves for curvature graph", 4, False, True, True) If IsNull(idCurves) Then Exit Sub Dim bResult, i Dim intSamples : intSamples = 10 Dim dblScale : dblScale = 1.0 Dim arrPreview() : ReDim arrPreview(UBound(idCurves)) Do Call Rhino.EnableRedraw(False) For i = 0 To UBound(arrPreview) If IsArray(arrPreview(i)) Then Rhino.DeleteObjects(arrPreview(i)) Next For i = 0 To UBound(arrPreview) arrPreview(i) = AddCurvatureGraph(idCurves(i), intSamples, dblScale) Next Call Rhino.EnableRedraw(True) bResult = Rhino.GetString("Curvature settings", "Accept", _ Array("Samples", "Scale", "Accept")) If IsNull(bResult) Then For i = 0 To UBound(arrPreview) If IsArray(arrPreview(i)) Then Rhino.DeleteObjects(arrPreview(i)) Next Exit Sub End If Select Case UCase(bResult) Case "ACCEPT" Exit Do Case "SAMPLES" bResult = Rhino.GetInteger("Number of samples per knot-span", intSamples, 3, 100) If Not IsNull(bResult) Then intSamples = bResult Case "SCALE" bResult = Rhino.GetReal("Scale of the graph", dblScale, 0.01, 1000.0) If Not IsNull(bResult) Then dblScale = bResult End Select Loop End Sub ```
Line Description
2 Prompt for any number of curves, we do not want to limit our script to just one curve..
6...7 Our default values are a scale factor of 1.0 and a span sampling count of 10.
9 arrPreview() is an array that contains arrays of IDs. One for each curve in idCurves.
11 Since users are allowed to change the settings an infinite number of times, we need an infinite loop around our UI code.
13...15 First of all, delete all the preview geometry, if present.
17..19 Then, insert all the new preview geometry.
22 Once the new geometry is in place, display the command options. The array at the end of the Rhino.GetString() method is a list of command options that will be visible..
24...29 If the user aborts (pressed Escape), we have to delete all preview geometry and exit the sub.
31...40 If the user clicks on an option, bResult will be the option name. It's best to use a Select…Case statement to determine which option was clicked.
32 In the case of "Accept", all we have to do is exit the sub without deleting the preview geometry.
34...36 If the picked option was "Samples", then we have to ask the user for a new sample count. If the user pressed Escape during this nested prompt, we do not abort the whole script (typical Rhino behaviour would dictate this), but instead return to the base prompt..
## 7.8 Meshes Instead of Nurbs surfaces (which would be the next logical step after nurbs curves), this chapter is about meshes. I figured you could use a break from t-parameters, knots and degrees so I'm going to take this opportunity to introduce you to a completely different class of geometry -officially called "polygon meshes"- which represents a radically different approach to shape. Instead of treating a surface as a deformation of a rectangular nurbs patch, meshes are defined locally, which means that a single mesh surface can have any topology it wants. A mesh surface can even be a disjoint (not connected) compound of floating surfaces, something which is absolutely impossible with Rhino nurbs surfaces. Because meshes are defined locally, they can also store more information directly inside the mesh format, such as colours, texture-coordinates and normals. The tantalizing image below indicates the local properties that we can access via RhinoScript. Most of these properties are optional or have default values. The only essential ones are the vertices and the faces. It is important to understand the pros and cons of meshes over alternative surface paradigms, so you can make an informed decision about which one to use for a certain task. Most differences between meshes and nurbs are self-evident and flow from the way in which they are defined. For example, you can delete any number of polygons from the mesh and still have a valid object, whereas you cannot delete knot spans without breaking apart the nurbs geometry. There's a number of things to consider which are not implied directly by the theory though. 1. Coordinates of mesh vertices are stored as single precision numbers in Rhino in order to save memory consumption. Meshes are therefore less accurate entities than nurbs objects. This is especially notable with objects that are very small, extremely large or very far away from the world origin. Mesh objects go hay-wire sooner than nurbs objects because single precision numbers have larger gaps between them than double precision numbers (see page 6). 2. Nurbs cannot be shaded, only the isocurves and edges of nurbs geometry can be drawn directly in the viewport. If a nurbs surface has to be shaded, then it has to fall back on meshes. This means that inserting nurbs surfaces into a shaded viewport will result in a significant (sometimes very significant) time lag while a mesh representation is calculated. 3. Meshes in Rhino can be non-manifold, meaning that more than two faces share a single edge. Although it is not technically impossible for nurbs to behave in this way, Rhino does not allow it. Non-manifold shapes are topologically much harder to deal with. If an edge belongs to only a single face it is an exterior edge (naked), if it belongs to two faces it is considered interior. ### 7.8.1 Geometry vs. Topology As mentioned before, only the vertices and faces are essential components of the mesh definition. The vertices represent the geometric part of the mesh definition, the faces represent the topological part. Chances are you have no idea what I'm talking about... allow me to explain. According to MathWorld.com topology is "*the mathematical study of the properties that are preserved through deformations, twistings, and stretchings of objects.*" In order words, topology doesn't care about size, shape or smell, it only deals with the platonic properties of objects, such as "how many holes does it have?", "how many naked edges are there?" and "how do I get from Paris to Lyon without passing any tollbooths?". The field of topology is partly common-sense (everybody intuitively understands the basics) and partly abstract-beyond-comprehension. Luckily we're only confronted with the intuitive part here (more on topology in the chapter on B-Rep objects). If you look at the images above, you'll see a number of surfaces that are topologically identical (except {E}) but geometrically different. You can bend shape {A} and end up with shape {B}; all you have to do is reposition some of the vertices. Then if you bend it even further you get {C} and eventually {D} where the right edge has been bend so far it touches the edge on the opposite side of the surface. It is not until you merge the edges (shape {E}) that this shape suddenly changes its platonic essence, i.e. it goes from a shape with four edges to a shape with only two edges (and these two remaining edges are now closed loops as well). Do note that shapes {D} and {E} are geometrically identical, which is perhaps a little surprising. The vertices of a mesh object are an array of 3D point coordinates. They can be located anywhere in space and they control the size and form of the mesh. The faces on the other hand do not contain any coordinate data, they merely indicate how the vertices are to be connected: Here you see a very simple mesh with sixteen vertices and nine faces. Commands like *_Scale*, *_Move* and *_Bend* only affect the vertex-array, commands like *_TriangulateMesh* and *_SwapMeshEdge* only affect the face-array, commands like *_ReduceMesh* and *_MeshTrim* affect both arrays. Note that the last face {I} has its corners defined in a clockwise fashion, whereas all the other faces are defined counter-clockwise. Although this makes no geometric difference, it does affect how the mesh normals are calculated and one should generally avoid creating meshes that are cw/ccw inconsistent. Now that we know what meshes essentially consist of, we can start making mesh shapes from scratch. All we need to do is come up with a set of matching vertex/face arrays. We'll start with the simplest possible shape, a mesh plane consisting of a grid of vertices connected with quads. Just to keep matters marginally interesting, we'll mutate the z-coordinates of the grid points using a user-specified mathematical function in the form of: $$f(x, y, \Theta, \Delta) = ...$$ Where the user is allowed to specify any valid mathematical function using the variables *x*, *y*, *Θ* and *Δ*. Every vertex in the mesh plane has a unique combination of *x* and *y* values which can be used to determine the *z* value of that vertex by evaluating the custom function (*Θ* and *Δ* are the polar coordinates of *x* and *y*). This means every vertex {A} in the plane has a coordinate {B} associated with it which shares the *x* and *y* components, but not the *z* component. We'll run into four problems while writing this script which we have not encountered before, but only two of these have to do with mesh geometry/topology: It's easy enough to generate a grid of points, we've done similar looping already on page 33 where a nested loop was used to generate a grid wrapped around a cylinder. The problem this time is that it's not enough to generate the points. We also have to generate the face-array, which is highly dependent on the row and column dimensions of the vertex array. It's going to take a lot of logic insight to get this right, and I don't mind telling you that I can never solve this particular problem without making a schematic of the mesh. First, let us turn to the problem of generating the vertex coordinates, which is a straightforward one: ```vb Function CreateMeshVertices(ByVal strFunction, ByVal fDomain(), ByVal iResolution) Dim xStep : xStep = (fDomain(1) - fDomain(0)) / iResolution Dim yStep : yStep = (fDomain(3) - fDomain(2)) / iResolution Dim V(), N N = -1 Dim x, y, z For x = fDomain(0) To fDomain(1) + (0.5*xStep) Step xStep For y = fDomain(2) To fDomain(3) + (0.5*yStep) Step yStep z = SolveEquation(strFunction, x, y) N = N+1 ReDim Preserve V(N) V(N) = Array(x, y, z) Next Next CreateMeshVertices = V End Function ```
Line Description
1 This function is to be part of the finished script. It is a very specific function which merely combines the logic of nested loops with other functions inside the same script (functions which we haven't written yet, but since we know how they are supposed to work we can pretend as though they are available already). This function takes three arguments:
  1. A String variable which contains the format of the function {f(x,y,Θ,Δ) = …}
  2. An array of four doubles, indicating the domain of the function in x and y directions
  3. An integer which tells us how many samples to take in each direction
2...3 The fDomain() argument has four doubles, arranged like this: (0) Minimum x-value (1) Maximum x-value (2) Minimum y-value (3) Maximum y-value We can access those easily enough, but since the step size in x and y direction involves so much math, it's better to cache those values, so we don't repeat the same calculation over and over again.
8 Begin at the lower end of the x-domain and step through the entire domain until the maximum value has been reached. We can refer to this loop as the row-loop.
9 Begin at the lower end of the y-domain and step through the entire domain until the maximum value has been reached. We can refer to this loop as the column-loop.
10 This is where we're calling an -as of yet- non-existent function. However, I think the signature is straightforward enough to not require further explanation now.
11...13 Append the new vertex to the V list. Note that vertices are stored as a one-dimensional list, which makes accessing items at a specific (row, column) coordinate slightly cumbersome.
Once we have our vertices, we can create the face array that connects them. Since the face-array is topology, it doesn't matter where our vertices are in space, all that matters is how they are organized. The image on the right is the mesh schematic that I always draw whenever confronted with mesh face array logic. The image shows a mesh with twelve vertices and six quad faces, which has the same vertex sequence logic as the vertex array created by the function on the previous page. The vertex counts in x and y direction are four and three respectively (*Nx=4*, *Ny=3*). Now, every quad face has to link the four vertices in a counter-clockwise fashion. You may have noticed already that the absolute differences between the vertex indices on the corners of every quad are identical. In the case of the lower left quad {*A=0*; *B=3*; *C=4*; *D=1*}. In the case of the upper right quad {*A=7*; *B=10*; *C=11*; *D=8*}. We can define these numbers in a simpler way, which reduces the number of variables to just one instead of four: {*A=?*; *B=(A+Ny*); *C=(B+1)*; *D=(A+1)*}, where Ny is the number of vertices in the y-direction. Now that we know the logic of the face corner numbers, all that is left is to iterate through all the faces we need to define and calculate proper values for the A corner: ```vb Function CreateMeshFaces(ByVal iResolution) Dim Nx : Nx = iResolution Dim Ny : Ny = iResolution Dim F() : ReDim F(Nx * Ny - 1) Dim N : N = -1 Dim baseIndex Dim i, j For i = 0 To Nx-1 For j = 0 To Ny-1 N = N+1 baseIndex = i*(Ny+1) + j F(N) = Array(baseIndex, baseIndex+1, baseindex+Ny+2, baseIndex+Ny+1) Next Next CreateMeshFaces = F End Function ```
Line Description
2...3 Cache the {Nx} and {Ny} values, they are the same in our case because we do not allow different resolutions in {x} and {y} direction.
4 We know exactly how many faces we're going to add when we start this function, so there's no need to ReDim the array whenever we're adding a new one. The iResolution indicates the number of faces along each axis (not the number of vertices), so the total number of faces in the mesh will be the resolution in the x-direction times the resolution in the y-direction and since these are the same that amounts to the resolution squared.
9...10 These two nested loops are used to iterate over the grid and define a face for each row/column combo. I.e. the two values i and j are used to define the value of the A corner for each face.
13 Instead of the nondescript "A", we're using the variable name baseIndex. This value depends on the values of both i and j. The i value determines the index of the current column and the j value indicates the current offset (the row index).
14 Define the new quad face corners using the logic stated above.
Writing a tool which works usually isn't enough when you write it for other people. Apart from just working, a script should also be straightforward to use. It shouldn't allow you to enter values that will cause it to crash (come to think of it, it shouldn't crash at all), it should not take an eternity to complete and it should provide sensible defaults. In the case of this script, the user will have to enter a function which is potentially very complex, and also four values to define the numeric domain in {x} and {y} directions. This is quite a lot of input and chances are that only minor adjustments will be made during successive runs of the script. It therefore makes a lot of sense to remember the last used settings, so they become the defaults the next time around. There's a number of ways of storing persistent data when using scripts, each with its own advantages: We'll be using the *.ini file to store our data since it involves very little code and it survives a Rhino restart. An *.ini file is a textfile (with the extension "ini" (short for "initialization") instead of "txt") which stores a number of Strings in a one-level hierarchical format. This means that every setting in the *.ini file has a name, a category and a value. The registry works in much the same way, with the exception that you can nest categories and thus get a much more intricate settings structure. RhinoScript offers a number of methods that allow you to write and read *.ini settings without having to manage your own file objects. Writing data to an *.ini file works as follows: ```vb Sub SaveFunctionData(ByVal strFunction, ByVal fDomain(), ByVal Resolution) Dim iSettingFile : iSettingFile = Rhino.InstallFolder() & "MeshFunction_XY.ini" Call Rhino.SaveSettings(iSettingFile, "Function", "format", strFunction) Call Rhino.SaveSettings(iSettingFile, "Domain", "xMin", fDomain(0)) Call Rhino.SaveSettings(iSettingFile, "Domain", "xMax", fDomain(1)) Call Rhino.SaveSettings(iSettingFile, "Domain", "yMin", fDomain(2)) Call Rhino.SaveSettings(iSettingFile, "Domain", "yMax", fDomain(3)) Call Rhino.SaveSettings(iSettingFile, "Domain", "Resolution", Resolution) End Sub ```
Line Description
1 This is a specialized function written specifically for this script. The signature consists only of the data it has to store. Internally this function is using the *.ini calls, but that is unknown to whatever function is calling this one. This is another example of encapsulation, we could change this function later on to use the registry for example, and the script would keep on running.
2 Since an *.ini file is an actual file on the harddisk, it needs a path. We need to know this path if we intent to append settings. In our case we're using the Rhino.InstallFolder() method to get a folder for the file. Generally it is better to pick a location which is available to non-administator users, but that would involve a lot more code.
4...9 Write all settings successively to the file. As you can see each setting has a category and name property.
The contents of the \*.txt file should look something like this: Reading data from an *.ini file is slightly more involved, because there is no guarantee the file exists yet. Indeed, the first time you run this script there won't be a settings file yet and we need to make sure we supply sensible defaults: ```vb Sub LoadFunctionData(ByRef strFunction, ByRef fDomain(), ByRef Resolution) Dim iSettingFile : iSettingFile = Rhino.InstallFolder() & "MeshFunction_XY.ini" strFunction = Rhino.GetSettings(iSettingFile, "Function", "format") If IsNull(strFunction) Then strFunction = "Cos( Sqr(x^2 + y^2) )" fDomain(0) = -10.0 fDomain(1) = +10.0 fDomain(2) = -10.0 fDomain(3) = +10.0 Resolution = 50 Exit Sub End If fDomain(0) = CDbl(Rhino.GetSettings(iSettingFile, "Domain", "xMin")) fDomain(1) = CDbl(Rhino.GetSettings(iSettingFile, "Domain", "xMax")) fDomain(2) = CDbl(Rhino.GetSettings(iSettingFile, "Domain", "yMin")) fDomain(3) = CDbl(Rhino.GetSettings(iSettingFile, "Domain", "yMax")) Resolution = CInt(Rhino.GetSettings(iSettingFile, "Domain", "Resolution")) End Sub ```
Line Description
1 Since this function has to set a whole bunch of values, using the single return value isn't going to be enough. Rather, we pass in all variables by reference and have them set directly.
2 Obviously we need the exact same path to the *.ini file.
4 This is where we read the function string from the *.ini file. If the file doesn't exist (or if the category/name isn't found) then strFunction will be Null.
6...11 If strFunction is Null we can safely assume that the other settings won't be there either. So these will be our defaults.
15...19 If strFunction was read correctly, we can read the other settings as well. Note that our approach here is not entirely safe. If the *.ini file has become corrupted this function might not work properly.
We've now dealt with two out of four problems (mesh topology, saving and loading persistent settings) and it's time for the big ones. In our *CreateMeshVertices()* procedure we've placed a call to a function called *SolveEquation()* eventhough it didn't exist yet. *SolveEquation()* has to evaluate a user-defined function for a specific {x,y} coordinate which is something we haven't done before yet. It is very easy to find the answer to the question: "What is the value of {Sin(x) + Sin(y)} for {x=0.5} and {y=2.7} ?" However, this involves manually writing the equation inside the script and then running it. Our script has to evaluate custom equations which are not known until after the script starts. This means in turn that the equation is stored as a String variable. VBScript does not execute Strings, they are inert. If we want to treat a String variable as a bit of source code, we have to use a little-known feature which is so exotic it doesn't even appear in the VBScript helpfile (though you can find it online of course). The *Execute* statement runs a script inside a script. It isn't even a proper function or subroutine, it is instead referred to as a Statement, much like the *Dim* statement or the *Erase* statement. The *Execute* statement takes a single String and attempts to run it as a bit of code, but nested inside the current scope. That means that you can refer to local variables inside an *Execute*. This bit of magic is exactly what we need in order to evaluate expressions stored in Strings. We only need to make sure we set up our *x*, *y*, *Θ* and *Δ* variables prior to using *Execute*. The fourth big problem we need to solve has to do with nonsensical users (a certain school of thought popular among programmers claims that all users should be assumed to be nonsensical). It is possible that the custom function is not valid VBScript syntax, in which case the Execute statement will not be able to parse it. This could be because of incomplete brackets, or because of typos in functions or a million other problems. But even if the function is syntactically correct it might still crash because of incorrect mathematics. For example, if you try to calculate the value of *Sqr(-4.0)*, the script crashes with the "Invalid procedure call or argument" error message. The same applies to *Log(-4.0)*. These functions crash because there exists no answer for the requested value. Other types of mathematical problems arise with large numbers. *Exp(1000)* for example results in an "Overflow" error because the result falls outside the double range. Another favourite is the "Division by zero" error. The following table lists the most common errors that occur in the VBScript engine: As you can see there's quite a lot that can go wrong. We should be able to prevent this script from crashing, even though we do not control the entire process. We could of course try to make sure that the user input is valid and will not cause any computational problems, but it is much easier to just let the script fail and recover from a crash after it happened. We've used the error catching mechanism on page 40, but back then we were just lazy, now there is no other solution. As soon as we use the On Error Resume Next statement though, debugging becomes much harder because there are no more crashes. If the script fails to function in a manner expected, where do we start looking for the error? So it is always a good idea to write your scripts in such a way so as to make it easy to temporarily disable any error catching. Once the On Error Resume Next statement is executed by the script, the error data is wiped from the system (i.e. clean slate). Then, once an error occures that would otherwise have caused the script to crash, the error data will be filled out. We thus need to actively check whether or not an error has occured whenever something might have gone wrong. We can retieve the error data through the *Err* object, which is available at all times: ```vb Function SolveEquation(ByVal strFunction, ByVal x, ByVal y) Dim z Dim D, A, AngleData D = Rhino.Hypot(x, y) AngleData = Rhino.Angle(Array(0,0,0), Array(x,y,0)) If IsNull(AngleData) Then A = 0.0 Else A = Rhino.ToRadians(AngleData(0)) End If On Error Resume Next Execute("z = " & strFunction) If err.Number = 0 Then SolveEquation = z Else SolveEquation = 0.0 End If End Function ``` The amount of stuff the above bit of magic does is really quite impressive. It converts the {x;vy} coordinates into polar coordinates {A; D} (for Angle and Distance), makes sure the angle is an actual value, in case both {x} and {y} turn out to be zero. It solves the equation to find the z-coordinate, and sets {z} to zero in case a the equation was unsolvable. Now that all the hard work is done, all that is left is to write the overarching function that provides the interface for this script, which I don't think needs further explanation: ```vb Sub MeshFunction_XY() Dim zFunc, fDomain(3), iResolution Call LoadFunctionData(zFunc, fDomain, iResolution) zFunc = Rhino.StringBox("Specify a function f(x,y[,D,A])", zFunc, "Mesh function") If IsNull(zFunc) Then Exit Sub Dim strPrompt, bResult Do strPrompt = "Function domain " & _ "x{" & fDomain(0) & ", " & fDomain(1) & "} " & _ "y{" & fDomain(2) & ", " & fDomain(3) & "} " & _ "@ " & iResolution bResult = Rhino.GetString(strPrompt, "Insert", Split("xMin;xMax;yMin;yMax;Res;Insert", ";")) If IsNull(bResult) Then Exit Sub Select Case UCase(bResult) Case "XMIN" bResult = Rhino.GetReal("X-Domain start", fDomain(0)) If Not IsNull(bResult) Then fDomain(0) = bResult Case "XMAX" bResult = Rhino.GetReal("X-Domain end", fDomain(1)) If Not IsNull(bResult) Then fDomain(1) = bResult Case "YMIN" bResult = Rhino.GetReal("Y-Domain start", fDomain(2)) If Not IsNull(bResult) Then fDomain(2) = bResult Case "YMAX" bResult = Rhino.GetReal("Y-Domain end", fDomain(3)) If Not IsNull(bResult) Then fDomain(3) = bResult Case "RES" bResult = Rhino.GetInteger("Resolution of the graph", iResolution) If Not IsNull(bResult) Then iResolution = bResult Case "INSERT" Exit Do End Select Loop Dim V : V = CreateMeshVertices(zFunc, fDomain, iResolution) Dim F : F = CreateMeshFaces(iResolution) Call Rhino.AddMesh(V, F) Call SaveFunctionData(zFunc, fDomain, iResolution) End Sub ``` The default function *Cos(Sqr(x^2 + y^2))* is already quite pretty, but here are some other functions to play with as well. Note that you can use all VBScript and RhinoScript functions and you don't even have to prepend Rhino. in front of the function call if you don't want to:
Notation Syntax result
$$\cos\left(\sqrt{x^2 + y^2}\right)$$ Cos(Sqr(x^2 + y^2))
$$\sin(x) + \sin(y)$$ Sin(x) + Sin(y)
$$\sin(D + A)$$ Sin(D+A)
$$Atn\left(\sqrt{x^2 + y^2}\right)$$ Atn(x^2 + y^2) -or- Atn(D)
$$\sqrt{|x|} + \sin(y)^{16}$$ Sqr(Abs(x))+Sin(y)^16
$$\sin\left(\sqrt{\min(x^2, y^2)}\right)$$ Sin(Min(Array(x^2, y^2))^0.5)
$$\left[\sin(x) + \sin(y) + x + y\right]$$ CInt(Sin(x) + Sin(y) + x + y)
$$\log\left(\sin(x) + \sin(y) + 2.01\right)$$ Log(Sin(x) + Sin(y)+2.01)
### 7.8.2 Shape vs. Image The vertex and face arrays of a mesh object define its form (geometry and topology) but meshes can also have local display attributes. Colours and Texture-coordinates are two of these that we can control via RhinoScript. The colour array (usually referred to as 'False-Colors') is an optional mesh property which defines individual colours for every vertex in the mesh. The only Rhino commands that I know of that generate meshes with false-color data are the analysis commands (*_DraftAngleAnalysis*, *_ThicknessAnalysis*, *_CurvatureAnalysis* and so on and so forth) but unfortunately they do not allow you to export the analysis meshes. Before we do something useful with False-Color meshes, let's do something simple, like assigning random colours to a mesh object: ```python Sub RandomMeshColours() Dim idMesh : idMesh = Rhino.GetObject("Mesh to randomize", 32, True, True) If IsNull(idMesh) Then Exit Sub Dim V : V = Rhino.MeshVertices(idMesh) Dim F : F = Rhino.MeshFaceVertices(idMesh) Dim C() : ReDim C(UBound(V)) Dim i For i = 0 To UBound(V) C(i) = RGB(Rnd*255, Rnd*255, Rnd*255) Next Call Rhino.AddMesh(V, F, , , C) Call Rhino.DeleteObject(idMesh) End Sub ```
Line Description
7 The False-Color array is optional, but there are rules to using it. If we decide to specify a False-Color array, we have to make sure that it has the exact same number of elements as the vertex array, after all, every vertex needs its own colour. We must also make sure that every element in the False-Color array represents a valid colour. Colours in VBScript are defined as integers which store the red, green and blue channels. The channels are defined as numbers in the range {0; 255}, and they are mashed together into a bigger number where each channel is assigned its own niche. The advantage of this is that all colours are just numbers instead of more complex data-types, but the downside is that these numbers are usually meaningless for mere mortals: 1 Lowest possible value 2 Highest possible value
Random colours may be pretty, but they are not useful. All the Rhino analysis commands evaluate a certain geometrical local property (curvature, verticality, intersection distance, etc), but none of them take surroundings into account. Let's assume we need a tool that checks a mesh and a (poly)surface for proximity. There is nothing in Rhino that can do that out of the box. So this is actually going to be a useful script, plus we'll make sure that the script is completely modular so we can easily adjust it to analyze other properties. We'll need a function who's purpose it is to generate an array of numbers (one for each vertex in a mesh) that define some kind of property. These numbers are then in turn translated into a gradient (red for the lowest number, white for the highest number in the set) and applied as the False-Color data to a new mesh object. In our case the property is the distance from a certain vertex to the point on a (poly)surface which is closest to that vertex: Vertex {A} on the mesh has a point associated with it {Acp} on the box and the distance between these two {DA} is a measure for proximity. This measure is linear, which means that a vertex which is twice as far away gets a proximity value which is twice as high. A linear distribution is indicated by the red line in the adjacent graph. It actually makes more intuitive sense to use a logarithmic scale (the green line), since it is far better at dealing with huge value ranges. Imagine we have a mesh whose sorted proximity value set is something like: {0.0; 0.0; 0.0; 0.1; 0.2; 0.5; 1.1; 1.8; 2.6; … ; 9.4; 1000.0} As you can see pretty much all the variation is within the {0.0; 10.0} range, with just a single value radically larger. Now, if we used a linear approach, all the proximity values would resolve to completely red, except for the last one which would resolve to completely white. This is not a useful gradient. When you run all the proximity values through a logarithm you end up with a much more natural distribution: There is just one snag, the logarithm function returns negative numbers for input between zero and one. In fact, the logarithm of zero is minus-infinity, which plays havoc with all mathematics down the road since infinity is way beyond the range of numbers we can represent using doubles. And since the smallest possible distance between two points in space is zero, we cannot just apply a logarithm and expect our script to work. The solution is a simple one, add 1.0 to all distance values prior to calculating the logarithm, and all our results are nice, positive numbers. ```vb Function VertexValueArray(ByVal pts, ByVal id) Dim arrD() : ReDim arrD(UBound(pts)) Dim i For i = 0 To UBound(pts) arrD(i) = DistanceTo(pts(i), id) Next VertexDistanceArray = arrD End Function Function DistanceTo(ByVal pt, ByVal id) DistanceTo = Null Dim ptCP : ptCP = Rhino.BrepClosestPoint(id, pt) If IsNull(ptCP) Then Exit Function Dim D : D = Rhino.Distance(pt, ptCP(0)) D = Log(D + 1.0) DistanceTo = D End Function ```
Line Description
1...8 The VertexValueArray() function is the one that creates a list of numbers for each vertex. We're giving it the mesh vertices (an array of 3D points) and the object ID of the (poly)surface for the proximity analysis. This function doesn't do much, it simply instantiates a new array and then fills it up using the DistanceTo() function.
4...8 DistanceTo() calculates the distance from pt to the projection of pt onto id. Where pt is a single 3D coordinate and id if the identifier of a (poly)surface object. It also perform the logarithmic conversion, so the return value is not the actual distance.
And the master Sub containing all the front end and color magic: ```vb Sub ProximityAnalysis() Dim idMesh, idBRep idMesh = Rhino.GetObject("Target mesh for proximity analysis", 32, True, True) If IsNull(idMesh) Then Exit Sub idBRep = Rhino.GetObject("(Poly)surface for proximity analysis", 8+16, False, True) If IsNull(idBRep) Then Exit Sub Dim arrV, arrF, arrD arrV = Rhino.MeshVertices(idMesh) arrF = Rhino.MeshFaceVertices(idMesh) arrD = VertexValueArray(arrV, idBRep) Dim minD : minD = Rhino.Min(arrD) Dim maxD : maxD = Rhino.Max(arrD) Dim proxFactor, i Dim arrC() : ReDim arrC(UBound(arrV)) For i = 0 To UBound(arrV) proxFactor = (arrD(i) - minD) / (maxD - minD) arrC(i) = RGB(255, 255 * proxFactor, 255 * proxFactor) Next Call Rhino.AddMesh(arrV, arrF, ,, arrC) Call Rhino.DeleteObject(idMesh) End Sub ```
Line Description
15...16 Find the lowest and highest values in the arrD array.
18 Create the False-Color array.
21 Calculate the position on the {Red~White} gradient for the current value.
22 Cook up a colour based on the proxFactor.
## 7.9 Surfaces At this point you should have a fair idea about the strengths and flexibility of mesh based surfaces. It is no surprise that many industries have made meshes their primary means of surfacing. However, meshes also have their disadvantages and this is where other surfacing paradigms come into play. In fact, meshes (like nurbs) are a fairly recent discovery whose rise to power depended heavily on demand from the computer industry. Mathematicians have been dealing with different kinds of surface definitions for centuries and they have come up with a lot of them; surfaces defined by explicit functions, surfaces defined by implicit equations, minimal area surfaces, surfaces of revolutions, fractal surfaces and many more. Most of these types are far too abstract for your every-day modeling job, which is why most CAD packages do not implement them.
Schwarz D surface, a triply periodic minimal surface which divides all of space between here and the edge of creation into two equal chunks. Easy to define mathematically, hard to model manually.
Apart from a few primitive surface types such as spheres, cones, planes and cylinders, Rhino supports three kinds of freeform surface types, the most useful of which is the Nurbs surface. Similar to curves, all possible surface shapes can be represented by a Nurbs surface, and this is the default fall-back in Rhino. It is also by far the most useful surface definition and the one we will be focusing on. ### 7.9.1 NURBS Surfaces Nurbs surfaces are very similar to Nurbs curves. The same algorithms are used to calculate shape, normals, tangents, curvatures and other properties, but there are some distinct differences. For example, curves have tangent vectors and normal planes, whereas surfaces have normal vectors and tangent planes. This means that curves lack orientation while surfaces lack direction. This is of course true for all curve and surface types and it is something you'll have to learn to live with. Often when writing code that involves curves or surfaces you'll have to make assumptions about direction and orientation and these assumptions will sometimes be wrong. In the case of NURBS surfaces there are in fact two directions implied by the geometry, because NURBS surfaces are rectangular grids of {u} and {v} curves. And even though these directions are often arbitrary, we end up using them anyway because they make life so much easier for us. But lets start with something simple which doesn't actually involve NURBS surface mathematics on our end. Luckily this something simple has a really difficult sounding name so you won't have to feel bad about yourself when people find out what it is you've been up to in your spare time. The problem we're about to be confronted with is called *Surface Fitting* and the solution is called *Error Diffusion*. You have almost certainly come across this term in the past, but probably not in the context of surface geometry. Typically the words "error diffusion" are only used in close proximity to the words "color", "pixel" and "dither", but the wide application in image processing doesn't limit error diffusion algorithms to the 2D realm. The problem we're facing is a mismatch between a given surface and a number of points that are supposed to be on it. We're going to have to change the surface so that the distance between it and the points is minimized. Since we should be able to supply a large amount of points (and since the number of surface control-points is limited and fixed) we'll have to figure out a way of deforming the surface in a non-linear fashion (i.e. translations and rotations alone will not get us there). Take a look at the images below which are a schematic representation of the problem: For purposes of clarity I have unfolded a very twisted nurbs patch so that it is reduced to a rectangular grid of control-points. Actually, I'm making this up as I go along, I haven't really unfolded anything but I need you to realize that the diagram you're looking at is drawn in {uvw} space rather than world {xyz} space. The actual surface might be contorted in any number of ways, but we're only interested in the simplified {uvw} space. The surface has to pass through point {S}, but currently the two entities are not intersecting. The projection of {S} onto the surface {S'} is a certain distance away from {S} and this distance is the error we're going to diffuse. As you can see, {S'} is closer to some control points than others. Especially {F} and {G} are close, but {B; C; J; K} can also be considered adjacent control points. Rather than picking a fixed number of nearby control points and moving those in order to reduce the distance between {S} and {S'}, we're going to move all the points, but not in equal amounts. The images on the right show the amount of motion we assign to each control point based on its distance to {S'}. You may have noticed a problem with the algorithm description so far. If a nurbs surface is flat, the control-points lie on the surface itself, but when the surface starts to buckle and bend, the control points have a tendency to move away from the surface. This means that the distance between the control points {uvw} coordinate and {S'} is less meaningful. So instead of control points, we'll be using Greville points. Both nurbs curves and nurbs surfaces have a set of Greville points (or "edit points" as they are known in Rhino), but only curves expose this in the Rhino interface. As scripters we also get access to surface Greville points, which is useful because there is a 1:1 mapping between control and Greville points and the latter are guaranteed to lie on the surface. Greville points can therefore be expressed in {uv} coordinates only, which means we can also evaluate surface properties (such as tangency, normals and curvature) at these exact locations. The only thing left undecided at this point is the equation we're going to use to determine the amount of motion we're going to assign to a certain control point based on its distance from {S'}. It seems obvious that all the control points that are "close" should be affected much more than those which are farther away. The minimum distance between two points in space is zero (negative distance only makes sense in certain contexts, which we'll get to shortly) and the maximum distance is infinity. This means we need a graph that goes from zero to infinity on the x-axis and which yields a lower value for {y} for every higher value of {x}. If the graph ever goes below zero it means we're deforming the surface with a negative error. This is not a bad thing per se, but let's keep it simple for the time being. Our choices are already pretty limited by these contraints, but there are still some worthy contestants. If this were a primer about mathematics I'd probably have gone for a gaussian distribution, but instead we'll use an extremely simple equation known as a hyperbola. If we define the diffusion factor of a Greville point as the inverse of its distance to {S'}, we get this hyperbolic function: $$f(y)=\frac{1}{x}$$ As you can see, the domain of the graph goes from zero to infinity, and for every higher value of {x} we get a lower value of {y}, without {y} ever becoming zero. There's just one problem, a problem which only manifests itself in programming. For very small values of {x}, when the Greville point is very close to {S'}, the resulting {y} is very big indeed. When this distance becomes zero the weight factor becomes infinite, but we'll never get even this far. Even though the processor in your computer is in theory capable of representing numbers as large as 1.8 × 10308 (which isn't anywhere near infinity by any stretch of the imagination), when you start doing calculations with numbers approaching the extremes chances are you are going to cross over into binary no man's land and crash your pc. And that's not even to mention the deplorable mathematical accuracy at these levels of scale. Clearly, I'd be giving you good advice if I told you to steer clear of very big and very small numbers altogether. It's an easy fix in our case, we can simply limit the {x} value to the domain {+0.01; +∞}, meaning that {y} can never get bigger than 100. We could make this threshold much, much smaller without running into problems. Even if we limit {x} to a billionth of a unit (0.00000001) we're still comfortably in the clear. I think that's about enough psychobabble for one chapter introduction, high time for some coding. The first thing we need to do is write a function that takes a surface and a point in {xyz} coordinates and translates it into {uvw} coordinates. We can use the *Rhino.SurfaceClosestPoint()* method to get the {u} and {v} components, but the {w} is going to take some thinking. First of all, a surface is a 2D entity meaning it has no thickness and thus no "real" {z} or {w} component. But a surface does have normal vectors that point away from it and which can be used to emulate a "depth" dimension. In the adjacent illustration you can see a point in {uvw} coordinates, where the value of {w} is simply the distance between the point and the start of the line. It is in this respect that negative distance has meaning, because negative distance denotes a {w} coordinate on the other side of the surface. Although this is a useful way of describing coordinates in surface space, you should at all times remember that the {u} and {v} components of such a coordinate are expressed in surface parameter space while the {w} component is expressed in world units. We are using mixed coordinate systems which means that we cannot blindly use distances or angles between these points because those properties are meaningless now. In order to find the coordinates in surface {S} space of a point {P}, we need to find the projection {P'} of {P} onto {S}. Then we need to find the distance between {P} and {P'} so we know the magnitude of the {w} component and then we need to figure out on which side of the surface {P} is in order to figure out the sign of {w} (positive or negative). Since our script will be capable of fitting a surface to multiple points, we might as well make our function array-capable: ```vb Function ConvertToUVW(ByVal idSrf, ByRef pXYZ()) Dim pUVW() : ReDim pUVW(UBound(pXYZ)) Dim Suv, Sxyz, Snormal Dim Sdist, dirPos, dirNeg Dim i For i = 0 To UBound(pXYZ) Suv = Rhino.SurfaceClosestPoint(idSrf, pXYZ(i)) Sxyz = Rhino.EvaluateSurface(idSrf, Suv) Snormal = Rhino.SurfaceNormal(idSrf, Suv) dirPos = Rhino.PointAdd(Sxyz, Snormal) dirNeg = Rhino.PointSubtract(Sxyz, Snormal) Sdist = Rhino.Distance(Sxyz, pXYZ(i)) If (Rhino.Distance(pXYZ(i), dirPos) > Rhino.Distance(pXYZ(i), dirNeg)) Then Sdist = -Sdist End If pUVW(i) = Array(Suv(0), Suv(1), Sdist) Next ConvertToUVW = pUVW End Function ```
Line Description
1 pXYZ() is an array of points expressed in world coordinates. It is passed ByRef to avoid copying.
9...11 Find the {uv} coordinate of {P'}, the {xyz} coordinates of {P'} and the surface normal vector at {P'}
13...14 Add and subtract the normal to the {xyz} coordinates of {P'} to get two points on either side of {P'}.
18...20 If {P} is closer to the dirNeg point, we know that {P} is on the "downside" of the surface and we need to make {w} negative.
We need some other utility functions as well (it will become clear how they fit into the grand scheme of things later) so let's get it over with quickly: ```vb Function GrevilleNormals(ByVal idSrf) Dim uvGreville : uvGreville = Rhino.SurfaceEditPoints(idSrf, True, True) Dim srfNormals() : ReDim srfNormals(UBound(uvGreville)) Dim i For i = 0 To UBound(uvGreville) srfNormals(i) = Rhino.SurfaceNormal(idSrf, uvGreville(i)) Next GrevilleNormals = srfNormals End Function ``` This function takes a surface and returns an array of normal vectors for every Greville point. There's nothing special going on here, you should be able to read this function without even consulting help files at this stage. The same goes for the next function, which takes an array of vector and an array of numbers and divides each vector with the matching number. This function assumes that *Vectors* and *Factors* are arrays of equal size. ```vb Sub DivideVectorArray(ByRef Vectors, ByRef Factors) Dim i For i = 0 To UBound(Vectors) Vectors(i) = Rhino.VectorDivide(Vectors(i), Factors(i)) Next End Sub ``` The next subroutine isn't so straightforward, mostly because we are now using the ByRef keyword to accomplish something different. Our eventual algorithm will keep track of both motion vectors and weight factors for each control point on the surface (for reasons I haven't explained yet), and we need to instantiate these arrays with default values. Even though that is pretty simple stuff, I decided to move the code into a separate procedure anyway in order to keep all the individual procedures small. The problem is that we need to instantiate 2 arrays with default values and a function can only return a single blob of data. Instead of making 2 functions (one for each array), I made a single subroutine which does not return a value, but which takes to *ByRef* arguments instead. *ByRef* means this procedure gets to poke the variables in some other procedure directly. So instead of assigning the return value of a function to a variable, we now give a pre-existing variable to a function and allow it to change it. This way, we can mess around with as many variables as we like. ```vb Sub InstantiateForceLists(ByRef Forces(), ByRef Factors(), ByVal Bound) ReDim Forces(Bound) ReDim Factors(Bound) Dim i For i = 0 To Bound Forces(i) = Array(0,0,0) Factors(i) = 0.0 Next End Sub ```
Line Description
1 Forces and Factors are ByRef arguments and they have also been declared as arrays. Bound is just a single integer indicating the size of both arrays. We are not interested in changing the value of Bound which is why it is passed ByVal.
2...3 Resize both referenced arrays. At this point, both arrays will have the proper length but they are both filled with *Empty* values.
6...9 Iterate through both arrays and assign default values (a zero-length vector in the case of Forces and zero in the case of Factors)
We've now dealt with all the utility functions. I know it's a bit annoying to deal with code which has no obvious meaning yet, and at the risk of badgering you even more I'm going to take a step back and talk some more about the error diffusion algorithm we've come up with. For one, I'd like you to truly understand the logic behind it and I also need to deal with one last problem... If we were to truly move each control point based directly on the inverse of its distance to {P'}, the hyperbolic diffusion decay of the sample points would be very noticeable in the final surface. Let's take a look at a simple case, a planar surface {Base} which has to be fitted to four points {A; B; C; D}. Three of these points are above the surface (positive distance), one is below the surface (negative distance): On the left you see the four individual hyperbolas (one for each of the sample points) and on the right you see the result of a fitting operation which uses the hyperbola values directly to control control-point motion. Actually, the hyperbolas aren't drawn to scale, in reality they are much (much) thinner, but drawing them to scale would make them almost invisible since they would closely hug the horizontal and vertical lines. We see that the control points that are close to the projections of {A; B; C; D} on {Base} will be moved a great deal (such as {S}), whereas points in between (such as {T}) will hardly be moved at all. Sometimes this is useful behaviour, especially if we assume our original surface is already very close to the sample points. If this is not the case (like in the diagram above) then we end up with a flat surface with some very sharp tentacles poking out. Lets assume our input surface is not already 'almost' good. This means that our algorithm cannot depend on the initial shape of the surface which in turn means that moving control points small amounts is not an option. We need to move all control points as far as necessary. This sounds very difficult, but the mathematical trick is a simple one. I won't provide you with a proof of why it works, but what we need to do is divide the length of the motion vector by the value of the sum of all the hyperbolas. Have a close look at control points {S} and {T} in the illustration above. {S} has a very high diffusion factor (lots of yellow above it) whereas {T} has a low diffusion factor (thin slivers of all colours on both sides). But if we want to move both {S} and {T} substantial amounts, we need to somehow boost the length of the motion vector for {T}. If you divide the motion vector by the value of the summated hyperbolas, you sort of 'unitize' all the motion vectors, resulting in the following section: which is a much smoother fit. The sag between {B} and {C} is not due to the shape of the original surface, but because between {B} and {C}, the other samples start to gain more relative influence and dragging the surface down towards them. Let's have a look at the code: ```vb Function FitSurface(ByVal idSrf, ByRef Samples(), ByRef dTranslation, ByRef dProximity) Dim P : P = Rhino.SurfacePoints(idSrf) Dim G : G = Rhino.SurfaceEditPoints(idSrf, True, True) Dim N : N = GrevilleNormals(idSrf) Dim S : S = ConvertToUVW(idSrf, Samples) Dim Forces(), Factors() Call InstantiateForceLists(Forces, Factors, UBound(P)) Dim i, j Dim LocalDist, LocalFactor, LocalForce dProximity = 0.0 dTranslation = 0.0 For i = 0 To UBound(S) dProximity = dProximity + Abs(S(i)(2)) For j = 0 To UBound(P) LocalDist = (S(i)(0) - G(j)(0))^2 + (S(i)(1) - G(j)(1))^2 If (LocalDist < 0.01) Then LocalDist = 0.01 LocalFactor = 1 / LocalDist LocalForce = Rhino.VectorScale(N(j), LocalFactor * S(i)(2)) Forces(j) = Rhino.VectorAdd(Forces(j), LocalForce) Factors(j) = Factors(j) + LocalFactor Next Next Call DivideVectorArray(Forces, Factors) For i = 0 To UBound(P) P(i) = Rhino.PointAdd(P(i), Forces(i)) dTranslation = dTranslation + Rhino.VectorLength(Forces(i)) Next Dim srf_N : srf_N = Rhino.SurfacePointCount(idSrf) Dim srf_K : srf_K = Rhino.SurfaceKnots(idSrf) Dim srf_W : srf_W = Rhino.SurfaceWeights(idSrf) Dim srf_D(1) srf_D(0) = Rhino.SurfaceDegree(idSrf, 0) srf_D(1) = Rhino.SurfaceDegree(idSrf, 1) FitSurface = Rhino.AddNurbsSurface(srf_N, P, srf_K(0), srf_K(1), srf_D, srf_W) End Function ```
Line Description
1 This is another example of a function which returns more than one value. This function properly returns the identifier of the surface it creates, but the last two arguments dTranslation and dProximity are ByRef and will be changed. When this function completes, dTranslation will contain a number that represents the total motion of all control points and dProximity will contain the total error (the sum of all distances between the surface and the samples). Since it is unlikely our algorithm will generate a perfect fit right away, we somehow need to keep track of how effective a certain iteration is. If it turns out that the function only moved the control points a tiny bit, we can abort in the knowledge we have achieved a high level of accuracy.
2...5 P, G, N and S are arrays that contain the surface control points (in {xyz} space), Greville points (in {uv} space), normal vectors at every greville point and all the sample coordinates (in {uvw} space). You have every right to be outraged by the names of the variables I've chosen, they're ludicrous. But they're short.
8 The function we're calling here has been dealt with on page 104.
16 First, we iterate over all Sample points.
19 Then, we iterate over all Control points.
20 LocalDist is the distance in {uv} space between the projection of the current sample point and the current Greville point.
21 This is where we limit the distance to some non-zero value in order to prevent extremely small numbers from entering the algorithmic meat-grinder.
22 Run the LocalDist through the hyperbola equation in order to get the diffusion factor for the current Control point and the current sample point.
24 LocalForce is a vector which temporarily caches the motion caused by the current Sample point. This vector points in the same direction as the normal, but the magnitude (length) of the vector is the size of the error times the diffusion factor we've calculated on line 22.
26 Every Control point is affected by all Sample points, meaning that every Control point is tugged in a number of different directions. We need to combine all these forces so we end up with a final, resulting force. Because we're only interested in the final vector, we can simply add the vectors together as we calculate them.
27 We also need to keep a record of all the Diffusion factors along with all vectors, so we can divide them later and unitize the motion (as discussed on page 105).
31 Divide all vectors with all factors (function explained on page 104)
33...36 Apply the motion we've calculated to the {xyz} coordinates of the surface control points.
38...45 Instead of changing the existing surface, we're going to add a brand new one. In order to do this, we need to collect all the NURBS data of the original such as knot vectors, degrees, weights and so on and so forth.
The procedure on the previous page has no interface code, thus it is not a top-level procedure. We need something that asks the user for a surface, some points and then runs the *FitSurface()* function a number of times until the fitting is good enough: ```vb Sub DistributedSurfaceFitter() Dim idSrf : idSrf = Rhino.GetObject("Surface to fit", 8, True, True) If IsNull(idSrf) Then Exit Sub Dim pts : pts = Rhino.GetPointCoordinates("Points to fit to", False) If IsNull(pts) Then Exit Sub Dim N, nSrf Dim dProx, dTrans For N = 1 To 1000 Call Rhino.EnableRedraw(False) nSrf = FitSurface(idSrf, pts, dTrans, dProx) Call Rhino.DeleteObject(idSrf) Call Rhino.EnableRedraw(True) Call Rhino.Prompt("Translation = " & Round(dTrans, 2) & " Deviation = " & Round(dProx, 2)) If (dTrans < 0.1) Or (dProx < 0.01) Then Exit For idSrf = nSrf Next Call Rhino.Print("Final deviation = " & Round(dProx, 4)) End Sub ```
Line Description
11 Rather than using an infinite loop (Do…Loop) we limit the total amount of fitting iterations to one thousand. That should be more than enough, and if we still haven't found a good solution by then it is unlikely we ever will. The variable N is known as a "chicken int" in coding slang. "Int" is short for "Integer" and "chicken" is because you're scared the loop might go on forever.
12...15 Disable the viewport, create a new surface, delete the old one and switch the redraw back on
17 Inform the user about the efficiency of the current iteration
19 If the total translation is negligible, we might as well abort since nothing we can do will make it any better. If the total error is minimal, we have a good fit and we should abort.
### 7.9.2 Surface Curvature Curve curvature is easy to grasp intuitively. You simply fit a circle to a short piece of curve as best you can (this is called an "osculating circle") and the radius and center of this circle tell you all you need to know about the local curvature. We've dealt with this already on page 84. Points {A; B; C; D; E} have a certain curvature associated with them. The radius of the respective circles is a measure for the curvature (in fact, the curvature is the inverse of the radius), and the direction of the vectors is an indication of the curve plane. If we were to scale the curve to 50% of its original size the curvature circles also become half as big, effectively doubling the curvature values. Point {C} is special in that it has zero-curvature (i.e. the radius of the osculating circle is infinite). Points where the curvature value changes from negative to positive are known as inflection points. If we have multiple inflection points next to each other, we are dealing with a linear segment in the curve. Surface curvature is not so straightforward, for one, there are multiple definitions of curvature in the case of surfaces and volumes and it depends on our particular script which one suits us best. The most obvious way of evaluating surface curvature would be to slice it with a straight section through the point {P} we are interested in and then simply revert to curve curvature algorithms. But, as mentioned before, surfaces lack direction and it is thus not at all clear at which angle we should dissect the surface (we could use {u} and {v} directions, but those will not necessarily give you meaningful answers). Still, this approach is useful every now and again and it goes under the name of "normal curvature". As you can see in the illustration below, there are an infinite number of sections through point {P} and thus an infinite number of answers to the question "what is the normal curvature at {P}?" When you look at the complete set of all possible normal curvatures, you'll find that the surface is most flat in one direction and most bend in another. These two directions are therefore special and they constitute the "principal curvatures" of a surface. The two principal curvature directions are always perpendicular to each other and thus they are completely independent of {u} and {v} directions ({u} and {v} are not necessarily perpendicular). Actually, things are more complicated since there might be multiple directions which yield lowest or highest normal curvature so there's a bit of additional magic required to get a result at all in some cases. Spheres for example have the same curvature in all directions so we cannot define principal curvature directions at all. This is not the end of the story. Starting with the set of all normal curvatures, we extracted definitions of the principal curvatures. Principal curvatures always come in pairs (minimum and maximum) and they are both values and directions. We are more often than not only interested in how much a surface bends, not particularly in which direction. One of the reasons for this is that the progression of principal curvature directions across the surface is not very smooth: The illustration on the left shows the directions of the maximum principal curvatures. As you can see there are threshold lines on the surface at which the principal direction suddenly makes 90º turns. The overall picture is chaotic and overly complex. We can use a standard tensor-smoothing algorithm to average each direction with its neighbours, resulting in the image on the right, which provides us with an already much more useful distribution (e.g. for patterning purposes), but now the vectors have lost their meaning. This is why the principal curvature directions are not a very useful surface property in every day life. Instead of dealing with the directions, the other aforementioned surface curvature definitions deal only with the scalar values of the curvature; the osculating circle radius. The most famous among surface curvature definitions are the Gaussian and Mean curvatures. Both of these are available in the *_CurvatureAnalysis* command and RhinoScript. The great German mathematician Carl Friedrich Gauss figured out that by multiplying the principal curvature radii you get another, for some purposes much more useful indicator of curvature: $$K_{Gauss}=K_{min} \cdot K_{max}$$ Where JGauss is the Gaussian curvature and lmin and lmax are the principal curvatures. Assuming you are completely comfortable with the behaviour of multiplications, we can identify a number of specific cases: From this we can conclude that any surface which has zero Gaussian curvature everywhere can be unrolled into a flat sheet and any surface with negative Gaussian curvature everywhere can be made by stretching elastic cloth. The other important curvature definition is Mean curvature ("average"), which is essentially the sum of the principal curvatures: $$K_{Mean}=\frac{K_{min} + K_{max}}{2}$$ As you know, summation behaves very different from multiplication, and Mean curvature can be used to analyze different properties of a surface because it has different special cases. If the minimum and maximum principal curvatures are equal in amplitude but have opposing signs, the average of both is zero. A surface with zero Mean curvature is not merely anticlastic, it is a very special surface known as a *minimal* or *zero-energy* surface. It is the natural shape of a soap film with equal atmospheric pressure on both sides. These surfaces are extremely important in the field of tensile architecture since they spread stress equally across the surface resulting in structurally strong geometry. ### 7.9.3 Vector and Tensor Spaces On the previous page I mentioned the words "tensor", "smoothing" and "algorithm" in one breath. Even though you most likely know the latter two, the combination probably makes little sense. Tensor smoothing is a useful tool to have in your repertoire so I'll deal with this specific case in detail. Just remember that most of the script which is to follow is generic and can be easily adjusted for different classes of tensors. But first some background information... Imagine a surface with no singularities and no stacked control points, such as a torus or a plane. Every point on this surface has a normal vector associated with it. The collection of all these vectors is an example of a vector space. A vector space is a continuous set of vectors over some interval. The set of all surface normals is a two-dimensional vector space (sometimes referred to as a vector field), just as the set of all curve tangents is a one-dimensional vector space, the set of all air-pressure components in a turbulent volume over time is a four-dimensional vector space and so on and so forth. When we say "vector", we usually mean little arrows; a list of numbers that indicate a direction and a magnitude in some sort of spatial context. When things get more complicated, we start using "tensor" instead. Tensor is a more general term which has fewer connotations and is thus preferred in many scientific texts. For example, the surface of your body is a two-dimensional tensor space (embedded in four dimensional space-time) which has many properties that vary smoothly from place to place; hairiness, pigmentation, wetness, sensitivity, freckliness and smelliness to name just a few. If we measure all of these properties in a number of places, we can make educated guesses about all the other spots on your body using interpolation and extrapolation algorithms. We could even make a graphical representation of such a tensor space by using some arbitrary set of symbols. We could visualize the wetness of any piece of skin by linking it to the amount of blue in the colour of a box, and we could link freckliness to green, or to the width of the box, or to the rotational angle. All of these properties together make up the tensor class. Since we can pick and choose whatever we include and ignore, a tensor is essentially whatever you want it to be. Let's have a more detailed look at the tensor class mentioned on the previous page, which is a rather simple one... I created a vector field of maximum-principal curvature directions over the surface (sampled at a certain custom resolution), and then I smoothed them out in order to get rid of the sudden jumps in direction. Averaging two vectors is easy, but averaging them while keeping the result tangent to a surface is a bit harder. In this particular case we end up with a two-dimensional tensor space, where the tensor class T consist of a vector and a tangent plane: Since we're sampling the surface at regular parameter intervals in {u} and {v} directions, we end up with a matrix of tensors (a table of rows and columns). We can represent this easily with a two-dimensional array. We'll need two of these in our script since we need to store two separate data-entities; vectors and planes. This progression of smoothing iterations clearly demonstrates the usefulness of a tensor-smoothing algorithm; it helps you to get rid of singularities and creases in any continuous tensor space. I'm not going to spell the entire script out here, I'll only highlight the key functions. You can find the complete script (including comments) in the Script folder. ```vb Sub SurfaceTensorField(ByVal idSrf, ByVal Nu, ByVal Nv, ByRef T, ByRef K) Dim uDomain : uDomain = Rhino.SurfaceDomain(idSrf, 0) Dim vDomain : vDomain = Rhino.SurfaceDomain(idSrf, 1) ReDim T(Nu, Nv) ReDim K(Nu, Nv) Dim localCurvature Dim i, j, u, v For i = 0 To Nu u = uDomain(0) + (i/Nu)*(uDomain(1) - uDomain(0)) For j = 0 To Nv v = vDomain(0) + (j/Nv)*(vDomain(1) - vDomain(0)) T(i,j) = Rhino.SurfaceFrame(idSrf, Array(u,v)) localCurvature = Rhino.SurfaceCurvature(idSrf, Array(u,v)) If (IsNull(localCurvature)) Then K(i,j) = T(i,j)(1) Else K(i,j) = Rhino.SurfaceCurvature(idSrf, Array(u,v))(3) End If Next Next End Sub ```
Line Description
1 This procedure has to create all the arrays that define our tensor class. In this case one array with vectors and an array with planes. Since we cannot return more than one value from a function, we need to use ByRef arguments again.
5...6 Since we know exactly how many tensors we need to make, we can resize the arrays directly. There's no need for a ReDim statement inside the loop.
11 This looks imposing, but it is a very standard piece of logic. The problem here is a common one: how to remap a number from one scale to another. We know how many samples the user wants (some whole number) and we know the limits of the surface domain (two doubles of some arbitrary value). We need to figure out which parameter on the surface domain matches with the Nth sample number. Observe the diagram below for a schematic representation of the problem Our sample count (the topmost bar) goes from {A} to {B}, and the surface domain includes all values between {C} and {D}. We need to figure out how to map numbers in the range {A~B} to the range {C~D}. In our case we need a linear mapping function meaning that the value halfway between {A} and {B} gets remapped to another value halfway between {C} and {D}. Line 11 (and line 14) contain an implementation of such a mapping algorithm. I'm not going to spell out exactly how it works, if you want to fully understand this script you'll have to look into that by yourself.
16 Retrieve the surface Frame at {u,v}. This is part of our Tensor class.
17 Retrieve all surface curvature information at {u,v}. This includes principal, mean and Gaussian curvature values and vectors.
20 In case the surface has no curvature at {u,v}, use the x-axis vector of the Frame instead.
22 If the surface has a valid curvature at {u,v}, we can use the principal curvature direction which is stored in the 4th element of the curvature data array
This function takes two *ByRef* arrays (both arrays together are a complete description of our Tensor class) and it modifies the originals. The return value (a boolean indicating success or failure) is merely cosmetic. This function is a typical box-blur algorithm. It averages the values in every tensor with all neighbouring tensors using a 3×3 blur matrix. ```vb Function SmoothTensorField(ByRef T, ByRef K) SmoothTensorField = False Dim K_copy : K_copy = K Dim Ub1 : Ub1 = UBound(T, 1) Dim Ub2 : Ub2 = UBound(T, 2) Dim i, j, x, y, xm, ym Dim k_dir, k_tot For i = 0 To Ub1 For j = 0 To Ub2 k_tot = Array(0,0,0) For x = i-1 To i+1 xm = (x+Ub1) Mod Ub1 For y = j-1 To j+1 ym = (y+Ub2) Mod Ub2 k_tot = Rhino.VectorAdd(k_tot, K_copy(xm,ym)) Next Next k_dir = Rhino.PlaneClosestPoint(T(i,j), Rhino.VectorAdd(T(i,j)(0), k_tot)) k_tot = Rhino.VectorSubtract(k_dir, T(i,j)(0)) k_tot = Rhino.VectorUnitize(k_tot) K(i,j) = k_tot Next Next SmoothTensorField = True End Functio ```
Line Description
3 Since we'll be modifying the original, we need to make a copy first. We do this to prevent using already changed values in our averaging scheme. We're only changing the vectors in the K array, so we don't have to copy the planes in the T array as well.
10...11 Since our tensor-space is two-dimensional, we need 2 nested loops to iterate over the entire set.
14-18
Now that we're dealing with each tensor individually (the first two loops) we need to deal with each tensors neighbours as well (the second set of nested loops). We can visualize the problem at hand with a simple table graph. The green area is a corner of the entire two-dimensional tensor space, the dark green lines delineating each individual tensor. The dark grey square is the tensor we're currently working on. It is located at {u,v}. The eight white squares around it are the adjacent tensors which will be used to blur the tensor at {u,v}. We need to make 2 more nested loops which iterate over the 9 coordinates in this 3×3 matrix. We also need to make sure that all these 9 coordinates are in fact on the 2D tensor space and not teetering over the edge. We can use the Mod operator to make sure a number is "remapped" to belong to a certain numeric domain.
19 Once we have the mx and my coordinates of the tensor, we can add it to the k_tot summation vector.
23...26 Make sure the vector is projected back onto the tangent plane and unitized.
## Last Step Congratulations, you have made it through the [RhinoScript 101 Primer](/guides/rhinoscript/primer-101). -------------------------------------------------------------------------------- # Writing Custom Eto forms in Python Source: https://developer.rhino3d.com/en/guides/rhinopython/eto-forms-python/ Using the Eto dialog framework to create custom dialogs. ## Overview [Eto is an open source cross-platform user-interface framework](https://github.com/picoe/Eto/wiki) available in Rhino 6. Eto can be used in Rhino plug-ins, Grasshopper components, and Python scripts to create dialog boxes and other user-interface features. Rhino.Python comes with a series of [pre-defined user interface dialogs](/guides/rhinopython/python-user-input/) which can be used for the times a simple dialog box is needed. But, if the pre-defined dialogs above do not provide enough functionality, then creating a custom dialog box using Eto may be the right solution. For example, here is a custom, collapsing dialog that uses many controls: The Eto framework allows creation of the dialog box, the controls, and the actions and events required to make the form functional. Eto is powerful, full-features user-interface toolkit. Understanding how best to write, organize and use Eto dialogs will take some work. This guide will cover the basics and best practices of creating Eto dialogs in Rhino.Python. Some of the syntax may seem a little onerous, but in practice the following methods allow Eto code to efficiently be managed in Rhino.Python scripts. ## The Eto framework Conceptually, an Eto dialog can be thought of as a set of layers: Learning how each code each of these layers is key to learning Eto: - [**Custom Dialog Class**](#custom-dlalog-class) - Extending the Eto Dialog/Form class is the best way to create a dialog. - [**The Dialog Form**](#the-dialog-form) - The Dialog/Form is the base container. - [**The Controls**](#eto-controls) - Controls, such as labels, buttons and edit boxes, can be created and then placed in a layout. - [**The Layout**](#the-layout) - Within each form, a layout is used to position the controls. - [**Control delegates**](#control-delegate) - Delegate actions are the methods that are executed when a control is click, edited or changed. Any delegate actions must be bound to specific controls to specify what methods are run at the time of control events. Thinking about theses dialog parts as layers can help keep the code is organized. As an example of the layered approach of a dialog, here is a simple Eto dialog with few controls. The rest of this guide will cover the sections of the code in much more detail: ```python #! python3 # Imports import Rhino import scriptcontext import System import Rhino.UI import Eto.Drawing as drawing import Eto.Forms as forms # SampleEtoRoomNumber dialog class class SampleEtoRoomNumberDialog(forms.Dialog[bool]): # Dialog box Class initializer def __init__(self): super().__init__() # Initialize dialog box self.Title = 'Sample Eto: Room Number' self.Padding = drawing.Padding(10) self.Resizable = False # Create controls for the dialog self.m_label = forms.Label() self.m_label.Text = 'Enter the Room Number:' self.m_textbox = forms.TextBox() self.m_textbox.Text = "" # Create the default button self.DefaultButton = forms.Button() self.DefaultButton.Text ='OK' self.DefaultButton.Click += self.OnOKButtonClick # Create the abort button self.AbortButton = forms.Button() self.AbortButton.Text ='Cancel' self.AbortButton.Click += self.OnCloseButtonClick # Create a table layout and add all the controls layout = forms.DynamicLayout() layout.Spacing = drawing.Size(5, 5) layout.AddRow(self.m_label, self.m_textbox) layout.AddRow(None) # spacer layout.AddRow(self.DefaultButton, self.AbortButton) # Set the dialog content self.Content = layout # Start of the class functions # Get the value of the textbox def GetText(self): return self.m_textbox.Text # Close button click handler def OnCloseButtonClick(self, sender, e): self.m_textbox.Text = "" self.Close(False) # OK button click handler def OnOKButtonClick(self, sender, e): if self.m_textbox.Text == "": self.Close(False) else: self.Close(True) ## End of Dialog Class ## # The script that will be using the dialog. def RequestRoomNumber(): dialog = SampleEtoRoomNumberDialog(); rc = dialog.ShowModal(Rhino.UI.RhinoEtoApp.MainWindow) if (rc): print(dialog.GetText()) #Print the Room Number from the dialog control ########################################################################## # Check to see if this file is being executed as the "main" python # script instead of being used as a module by some other python script # This allows us to use the module which ever way we want. if __name__ == "__main__": RequestRoomNumber() ``` This script is split into 3 main sections. 1. The `import` section to include all the assemblies needed for the script. 2. The dialog class definition `SampleRoomNumberDialog()` 3. The script itself `RequestRoomNumber()` ## Imports for Eto Eto is a large assembly. For readabilities sake, you need only `import` the most important portions: ```python import Rhino.UI import Eto.Drawing as drawing import Eto.Forms as forms ``` The `Rhino.UI` assembly is used to interface between Rhino and Eto. When using `dialog.ShowModal` method, using a `Rhino.UI.RhinoEtoApp.MainWindow` class allows the dialog to show as a child of the Rhino application. Eto is a large namespace. The next two `import` lines access the most referenced portions of Eto, the `Eto.Drawing` namespace and `Eto.Forms`. The `Eto.Drawing` namespace contains specific classes that help with the graphic properties of objects. The `Eto.Forms` namespace contains the dialogs, layouts, and controls for a dialog. Using Python's renaming feature, the namespaces are shortened to `drawing` and `forms`. Along the left column of the Python editor, the methods within this Eto Assembly are listed. For a detailed view of all the methods the Eto can be found in the [Eto.Forms API Documentation](http://api.etoforms.picoe.ca/html/R_Project_EtoForms.htm) ## Custom Dialog Class The next section of the code creates a new class definition that extends the `Dialog(T)` class. [Creating classes in Python](/guides/rhinopython/primer-101/7-classes/) requires some very specific syntax. While it may seems little more complicated to create a class, the ability to reuse, import and interact with class based dialog in Python scripts is well worth the practice. A class will contain the default information about the default layouts and actions of the class controls. The class will also be used to store all the values of the controls for the while the script is running. contain the values of A dialog class is started with these lines: ```python class SampleEtoViewRoomNumber(forms.Dialog[bool]): ``` In this case the new class will be named `SampleEtoRoomNumberDialog` and inherits the Eto class `Eto.Froms.Dialog[bool]`. The `bool` argument shows that a Boolean value is expected back from the dialog. This Boolean value can be used to tell if the `OK` or `Cancel` button was hit when the dialog was exited. If more return values then True/False are need back from a dialog then a `Dialog[int]` or `Dialog[string]` might be needed. Note, this guide will only cover the creation of model dialogs, which require the user to respond before continuing the program. The other dialog types, semi-modal and modeless, are beyond the scope of this guide, but may be useful in future projects. ## The Dialog Form Once the new class is declared, then the *init* instantiation operation to assign the defaults to the new dialog object when created. Python uses the *self* variable in class declarations to reference the class members in the *init*. This of *self* as a placeholder for the class name once the class is actually created in the script. ```python class SampleEtoViewRoomNumber(forms.Dialog[bool]): def __init__(self): super().__init__() # Initialize dialog box self.Title = 'Sample Eto: Room Number' self.Padding = drawing.Padding(10) self.Resizable = False ``` The first section of the *init* is a few common properties that all dialogs have: 1. `super().__init__()` - Intialize the base class. 1. `self.Title` - Sets the title of the dialog. This is a standard string. 2. `self.Padding` - Set a blank border area within which any child content will be placed. This requires the creation of a [Eto.Drawing.Padding](http://api.etoforms.picoe.ca/html/T_Eto_Drawing_Padding.htm) structure. 3. `self.Resizable` - Whether the dialog box is resizable by dragging with the mouse. This is a True/False Boolean. There are a few `Padding` formats that are accepted by [Eto.Drawing.Padding](http://api.etoforms.picoe.ca/html/T_Eto_Drawing_Padding.htm). These match the margin and padding formats of standard CSS styling: - `Eto.Drawing.Padding(10)` - 10 pixel padding around all 4 sides. - `Eto.Drawing.Padding(10, 20)` - A padding of 10 to the left and right and a padding of 20 on top and bottom. - `Eto.Drawing.Padding(10, 20, 30, 40)` - A padding of 10 to the left, 20 to the top, 30 to the right and 40 to the bottom. By default the dialog will automatically adjust its size to the contents it contains. But an addition line can be added to set an initial size to the dialog using `self.ClientSize`: ```python self.ClientSize = drawing.Size(300, 400) #sets the (Width, Height) ``` The ClientSize property takes a `Eto.Drawing.Size` structure. After we create the controls and a layout the contents can be placed within the dialog using the `self.Content` class, as is done on line 39: ```python self.Content = layout ``` A dialog class is will show up on the screen as [*modal*](https://en.wikipedia.org/wiki/Modal_window). To close the dialog a button will be pressed. To close a dialog, use the `self.Close` method. It is common to do little data checking before closing the dialog: ``` # Close button click handler def OnCloseButtonClick(self, sender, e): self.m_textbox.Text = "" self.Close(False) ``` The `self.Close` method is also returning a `False` because the Cancel button was pressed to cause this event. The script will continue on based on the return value of the dialog. Also, because we are using a new class object to create the dialog, even after the dialog is closed the dialog will still be in memory. This means the methods and values within the dialog will continue to be available within the scope of the script as the script may need to reference those values. After creating the dialog framework, we will start to create some controls for the dialog. ## The Controls The business end of a dialog is the user-interface controls it displays. Controls may include Labels, Buttons, Edit boxes and Sliders. In Eto, there are [more then 35 different controls](https://github.com/picoe/Eto/wiki/Controls) that can be created. For details information on these controls, go to the [Eto Controls in Python](/guides/rhinopython/eto-controls-python/) guide. Controls normally need to be setup properly in a layout before they are added to a dialog. ### Label Control The simplest control is the Label control. It is simply a piece of text that normally is used to create a prompt, or label for another control. ```python self.m_label = forms.Label() self.m_label.Text = 'Enter the Room Number:' ``` As with many controls, the line above create a name for the control `m_label`. Then the main property of a Label is the text it shows by setting the Text Property of the label. Normally this is as complex as a label needs to be, but a label also has many more properties in addition to `Text`. Additonal properties include `VerticalAlignment`, `Horizontal Alignment`, `TextAlignment`, `Wrap`, `TextColor`, and `Font`. Properties can be added to the Text Property by using a comma(`,`): ```python self.m_label = forms.Label() self.m_label.Text = 'Enter the Room Number:' self.m_label.VerticalAlignment = `VerticalAlignment.Center` ``` For a complete list of properties and events of the Label class, see the [Eto Label Class](http://api.etoforms.picoe.ca/html/T_Eto_Forms_Label.htm) documentation. ### TextBox Control A TextBox is used to enter a string into the dialog. To check the contents of the textbox in the script, the textbox control must have a name to reference it. ```python self.m_textbox = forms.TextBox() ``` In this case the name `m_textbox` can be used to reference the control later in the class method starting on line 44: ``` # Get the value of the textbox def GetText(self): return self.m_textbox.Text ``` Just creating a new `Eto.Forms.TextBox()` is common. There are a number of additional properties of a TextBox which can be used to control the input. These properties include `MaxLength`, `PlaceholderText`, `InsertMode` and many more that can be seen in the [Eto TextBox Class](http://api.etoforms.picoe.ca/html/T_Eto_Forms_TextBox.htm). ### Button Controls Buttons are placed on almost every dialog. Buttons are created, then bound through their `.Click` event to run a method when the button is clicked. Buttons can be assigned to any name. Along with the name, the `Text` property can be set to display on the button: ```python # Create the default button self.DefaultButton = forms.Button() self.DefaultButton.Text ='OK' self.DefaultButton.Click += self.OnOKButtonClick ``` Once created, the button then can be bound to an event method (`OnOKButtonClick`) through `.Click` class using the `+=` syntax as follows: ```python # Create the default button self.DefaultButton = forms.Button() self.DefaultButton.Text ='OK' self.DefaultButton.Click += self.OnOKButtonClick ``` The bound method is run if the button is clicked on. The bound method is declared in the methods section later in the class: ```python # Close button click handler def OnOKButtonClick(self, sender, e): if self.m_textbox.Text == "": self.Close(False) else: self.Close(True) ``` In this case the button is clicked and the bound method `OnOKButtonClick` checks the Text of the `m_textbox` to to determine if anything has been entered. Then the method closes the dialog, returning either `True` or `False`. The *Eto.Dialog class* has two special reserved names, the `DefaultButton` and the `AbortButton`. The `DefaultButton` name will create a button that is a standard button and also will receive a click event if the `Enter Key` is used. The `AbortButton` is a button that recieves a `.Click` event if the `ESC` key is used. These buttons are simple assigned through the name of the control, using `self` syntax. This guide review the most basic controls. To understand how to create and control more controls with Python see the Eto Controls in Python guide (TODO). Once all the controls for the dialog are created then they can be placed in a layout to be positioned on a dialog. ## The Layout Layouts are used to size and place controls in a logical way in a dialog. They can generally be thought of as grid controls that adjusts based on their contents. The sample code a new Layout is created on line 30 in this section of the code: ```python # Create a table layout and add all the controls layout = forms.DynamicLayout() layout.Spacing = drawing.Size(5, 5) layout.AddRow(self.m_label, self.m_textbox) layout.AddRow(None) # spacer layout.AddRow(self.DefaultButton, self.AbortButton) # Set the dialog content self.Content = layout ``` The code for the layout comes further down in the class definition, because it seems to make sense to create the controls first before placing them in a layout. In this a case a new dynamic layout object is created at: ```python layout = forms.DynamicLayout() ``` The [DynamicLayout](https://github.com/picoe/Eto/wiki/DynamicLayout) is one of [5 layout types](https://github.com/picoe/Eto/wiki/Containers) supported by Eto. The Dynamic layout is a virtual grid that can organized controls both vertically and horizontally. For a detailed look at layouts, go to the [Eto Layouts in Python](/guides/rhinopython/eto-layouts-python/) guide. The spacing between controls in the layout is set by `layout.Spacing` on the line: ```python layout.Spacing = drawing.Size(5, 5) ``` The `Eto.Drawing.Size(5, 5)` sets the horizontal spacing and vertical spacing of the controls to 5 pixels between the controls. ### Placing Rows in Layouts Once the layout type has been setup then controls can be placed. Control are placed into *Rows* in the layout. ```python layout.AddRow(self.m_label, self.m_textbox) layout.AddRow(self.DefaultButton, self.AbortButton) ``` Each row can be added to the newly created `Eto.Forms.Layout` object using the `.AddRow` method. Each control that is added in each row is given a cell on the row added. So if two controls are added, the row will contain two cells that control the placement of the control. The controls will stretch to fill up the cells. The `Eto.Forms.DynamicLayout` can positioned controls vertically and horizontally. Each vertical set of controls can be aligned with controls in previous horizontal sections, giving a very easy way to build forms. For more information see the [Eto DynamicLayout documenation](https://github.com/picoe/Eto/wiki/DynamicLayout) ### Using *None* in a Layout Sometimes blank spacers are needed within a layout to help controls align properly or help to align the number of cells from above. Or a blank form may be needed to allow the height of the layout to fill up the vertical space of the dialog. In Eto, using the `None` value will allow for spacers in dialogs. In the sample above a blank row is added between the controls: ```python layout.AddRow(None) # spacer ``` If the dialog box gets vertically taller, then the `None` row will expand to fill up the needed space. `None` can also be used in a Row as a horizontal spacer. For instance the buttons could be dynamically justified to the right of the row by adding a `None` spacer at the start of the row: ```python layout.AddRow(None, self.DefaultButton, self.AbortButton) ``` The `None` cell will expand and contract to justify the buttons to the right. There are many options when using Layout, Rows and Cells with Eto to place controls. For more information on the details of using Layouts see the Eto Layout advanced Options with Python (TODO) ## Control Delegates and Events The last section of the Dialog class, in the example, is a series of class methods: 1. Methods used to access the class members 2. Method actions for binding to control events. A common practice is to create a function that returns the value of a control you might want to get or set: ```python # Get the value of the textbox def GetText(self): return self.m_textbox.Text ``` There is an unusual syntax here in the method declaration with the inclusion of `(self)` as an argument of the function. This is done in functions that are a member of a class. As we learned before the *self* variable is like a placeholder for the class name that this method is a member. To use this method in a script the following syntax is used: ```python dialog = SampleEtoRoomNumberDialog(); rc = dialog.ShowModal(RhinoEtoApp.MainWindow) if (rc): print dialog.GetText() #Print the Room Number from the dialog control ``` In this case, a new dialog is created with the name `dialog`. The dialog is shown and the return value is assigned to the `rc` variable. Then, based on the result of `rc` the `GetText` method is used to get the value of the Textbox in the dialog using the `dialog.GetText()` method, even though the dialog has already been closed. Class methods also need to be created to handle events that may happen with controls in the dialog. Here is a function that will be used for the `OK` button: ```python # OK button click handler def OnOKButtonClick(self, sender, e): if self.m_textbox.Text == "": self.Close(False) else: self.Close(True) ``` Again here is an unusual syntax for the method declaration: `(self, sender, e)`. This is the standard argument declaration for any function that will be bound to a control action. This `OnOKButtonClick()` method will be bound to the OK button click with this code: ```python self.DefaultButton.Click += self.OnOKButtonClick ``` So now on every click the method will be called. There are many more events that methods may be bound to on controls such as [`TextChanged`](http://api.etoforms.picoe.ca/html/E_Eto_Forms_TextControl_TextChanged.htm), [`CheckedChanged`](http://api.etoforms.picoe.ca/html/E_Eto_Forms_CheckBox_CheckedChanged.htm), [`AddValue`](http://api.etoforms.picoe.ca/html/E_Eto_Forms_EnumDropDown_1_AddValue.htm), etc.. Review the [Eto APi documentation](http://api.etoforms.picoe.ca/html/N_Eto_Forms.htm) for specific events supported by each control. ## Using Eto Dialogs in Scripts Once the class definition is set, the dialog is ready to be used in a script: ```python # The script that will be using the dialog. def RequestRoomNumber(): dialog = SampleEtoRoomNumberDialog(); rc = dialog.ShowModal(Rhino.UI.RhinoEtoApp.MainWindow) if (rc): print dialog.GetText() #Print the Room Number from the dialog control ``` First a new class instance of the dialog is created: ```python dialog = SampleEtoRoomNumberDialog(); ``` Once create then the dialog needs to shown as a child of the Rhino application: ```python rc = dialog.ShowModal(Rhino.UI.RhinoEtoApp.MainWindow) ``` Because the dialog is modal, the script will continue to the next lines only after the dialog is closed. When `dialog.Close` is called the dialog will also return a value that is assigned to `rc` . The script continues along, checking the return `rc` value and also referencing the `dialog.GetText()` value. Remember, if if the dialog is closed the values of the dialog controls are still available. ## Sample Dialogs Now with some understanding of Eto Dialogs in Python, take a look at some of the Sample dialogs in the [Python Developer Samples Repo](https://github.com/mcneel/rhino-developer-samples/blob/master/rhinopython): 1. [A very simple dialog](https://github.com/mcneel/rhino-developer-samples/blob/master/rhinopython/SampleEtoDialog.py) 2. [Rebuild curve Dialog](https://github.com/mcneel/rhino-developer-samples/blob/master/rhinopython/SampleEtoRebuildCurve.py) 3. [Capture a view dialog](https://github.com/mcneel/rhino-developer-samples/blob/master/rhinopython/SampleEtoViewCaptureDialog.py) 4. [Collapsable controls on a Dialog](https://github.com/mcneel/rhino-developer-samples/blob/master/rhinopython/SampleEtoCollapsibleDialog.py) ## Related Topics - [Reading and Writing files with Python](/guides/rhinopython/python-reading-writing) - [RhinoScriptSyntax User interface methods](/api/RhinoScriptSyntax/win/#userinterface) - [Eto Layouts in Python](/guides/rhinopython/eto-layouts-python/) guide - [Eto Controls in Python](/guides/rhinopython/eto-controls-python/) guide -------------------------------------------------------------------------------- # Eto Controls in Python Source: https://developer.rhino3d.com/en/guides/rhinopython/eto-controls-python/ Using the Eto dialog framework to create interface controls. [Eto is an open source cross-platform dialog box framework](https://github.com/picoe/Eto/wiki) available in Rhino 6. This guide demonstrates the syntax required to create the most common Eto controls in Rhino.Python. Eto controls include labels, buttons, edit boxes and sliders. In Eto there are [more than 35 different controls](https://github.com/picoe/Eto/wiki/Controls) that can be created. For details on creating a complete Eto Dialog in Rhino.Python go to the [Getting Started with Eto Guide](/guides/rhinopython/eto-forms-python/). The samples in this guide can be added to the controls section of the Basic dialog framework covered in the [Getting Started Guide](/guides/rhinopython/eto-forms-python/). ## Buttons Buttons are placed on almost every dialog. Creating a new button is simple. Use the `forms.Button` and specify the `Text` that is shown on the button face. In addition to creating the new button, an action is commonly attached through the `.Click` event. Use the `+=` syntax as shown below to bind the action to button. ```python self.m_button = forms.Button(Text = 'OK') self.m_button.Click += self.OnButtonClick ``` The bound method, listed later in the calss definition is run if the button is clicked. ```python # Close button click handler def OnOKButtonClick(self, sender, e): if self.m_textbox.Text == "": self.Close(False) else: self.Close(True) ``` In this specific case the button is clicked and the bound method `OnOKButtonClick` checks the Text of the `m_textbox` to to determine if anything has been entered. Then the method closes the dialog, returning either `True` or `False`. The *Eto.Dialog class* has two special reserved names for buttons, the `DefaultButton` and the `AbortButton`. The `DefaultButton` name will create a button that is a standard button and also will receive a click event if the `Enter Key` is used. The `AbortButton` is a button that recieves a `.Click` event if the `ESC` key is used. These buttons are simple assigned through the name of the control, using `self` syntax. The syntax for the `DefaultButton` is: ```python self.DefaultButton = forms.Button(Text = 'OK') ``` And for the `AbortButton` is: ```python self.AbortButton = forms.Button(Text = 'Cancel') ``` The `DefaultButton` and `AbortButton` normally close the dialog by using the `self.close()` method of the dialog. Please note that even though the dialog is closed, that values of the controls are still accesable to the rest of the script. More details can be found in the [Eto Button API Documentation](http://api.etoforms.picoe.ca/html/T_Eto_Forms_Button.htm). ## Calendar A Calendar control is used to pick a date or range of dates. Creating a Calender is very simple: ```python #Create a Calender self.m_calender = forms.Calendar() ``` Often other properties are set on the calendar control. There are a number of properties of a calender control that can be used to modify how the control works. ```python #Create a Calendar self.m_calendar = forms.Calendar() self.m_calendar.Mode = forms.CalendarMode.Single self.m_calendar.MaxDate = System.DateTime(2017,7,31) self.m_calendar.MinDate = System.DateTime(2017,7,1) self.m_calendar.SelectedDate = System.DateTime(2017,7,15) ``` The `.Mode` property sets if only a single date can be setting `forms.CalendarMode.Single` or date range can be selected by setting `.mode` to `forms.CalendarMode.Range`. The `MaxDate` and `.MinDate` property limits the range of dates that can be selected from. The `.SelectedDate` will result in that date being initially selected. More details can be found in the [Eto Calendar API Documentation](http://api.etoforms.picoe.ca/html/T_Eto_Forms_Calendar.htm). ## CheckBox A Checkbox control can be used to specify `True` and `False`: The simpliest syntax for a CheckBox is: ```python #Create a Checkbox self.m_checkbox = forms.CheckBox(Text = 'My Checkbox') ``` The `Text` property set the text visibel next to the control. By default the CheckBox is unchecked and carries a value of *None*. Once checked it contains a booloean value of True. Once unchecked, the value is `False`. The intial state of the checkbox can be set in the class. The checkbox will be checked by default by adding the line: ```python self.m_checkbox.Checked = True ``` More details can be found in the [Eto Checkbox API Documentation](http://api.etoforms.picoe.ca/html/T_Eto_Forms_CheckBox.htm). ## ColorPicker To select a single color from a drop down use the ColorPicker. This control also supports a right-click menu on the dropdown arrow. The right-click menu gives access to the eye dropper and the standard system color picker dialog. This simple code below will create a color picker with a blank default color: ```python #Create a ColorPicker Control self.m_colorpicker = forms.ColorPicker() ``` The initial default color may be set in the control by adding the code: ```python defaultcolor = Eto.Drawing.Color.FromArgb(255, 0,0) self.m_colorpicker.Value = defaultcolor ``` More details can be found in the [Eto ColorPicker API Documentation](http://api.etoforms.picoe.ca/html/T_Eto_Forms_ColorPicker.htm) and [Eto.Drawing.Color Documentation](http://api.etoforms.picoe.ca/html/T_Eto_Drawing_Color.htm) ## ComboBox A Combo box is a drop down list of items that also allows input of text directly: ```python #Create Combobox self.m_combobox = forms.ComboBox() self.m_combobox.DataStore = ['first pick', 'second pick', 'third pick'] ``` The default value can be set to display by adding the index postition to the DataStore property: ```python self.m_combobox.SelectedIndex = 1 ``` ## DateTimePicker Use the DateTimePicker to select a date and/or a time: Similiar to the Calendar control, there is also a DateTimePicker. The `.Mode` can be set on the control to display the `.Date` or `.Time`. The `.Date` version differs from the `.Calendar` control by accessing the date through a dropdown menu: ```python #Create DateTime Picker in Date mode self.m_datetimedate = forms.DateTimePicker() self.m_datetimedate.Mode = forms.DateTimePickerMode.Date self.m_datetimedate.MaxDate = System.DateTime(2017,7,30) self.m_datetimedate.MinDate = System.DateTime(2017,7,1) self.m_datetimedate.Value = System.DateTime(2017,7,15) ``` The `.Time` mode creates a spinner where the time of day can be selected: ```python #Create DateTime Picker in Date mode self.m_datetimetime = forms.DateTimePicker() self.m_datetimetime.Mode = forms.DateTimePickerMode.Time self.m_datetimetime.Value = System.DateTime.Now self.m_datetimetime.Value = System.DateTime(2017, 1, 1, 23, 43, 49, 500) ``` More details can be found in the [Eto DateTimePicker API Documentation](http://api.etoforms.picoe.ca/html/T_Eto_Forms_DateTimePicker.htm). ## DropDown Drop down menu with a list of items. ```python #Create Dropdown List self.m_dropdownlist = forms.DropDown() self.m_dropdownlist.DataStore = ['first', 'second', 'third'] ``` The default selection can be set by using the index location in the DataStore: ```python self.m_dropdownlist.SelectedIndex = 1 ``` More details can be found in the [Eto DropDown API Documentation](http://api.etoforms.picoe.ca/html/T_Eto_Forms_DropDown.htm). ## GridView A virtualized grid of data with editable cells. The `GridView` is used to emulate a ListView control: Creating a `.GridView()` is more involved then othe ETo controls. Start by creating the `.GridView` then populate the control with `forms.GridColumn()`. Each `.GridColumn` has a `.HeaderText`, `.Editable` property and contains a `.DataCell`. The `.DataCell` can display a `TextBoxCell` or a `CheckBoxCell`. After each `.GridColumn` is created, then add it to the `.GridView` through the `self.m_gridview.Columns.Add(column1)` method. The sample below populates 4 columns in the `.GridView`: ```python #Create Gridview self.m_gridview = forms.GridView() self.m_gridview.ShowHeader = True self.m_gridview.DataStore = (['first pick', 'second pick', 'third pick', True],['second','fourth','last', False]) column1 = forms.GridColumn() column1.HeaderText = 'Column 1' column1.Editable = True column1.DataCell = forms.TextBoxCell(0) self.m_gridview.Columns.Add(column1) column2 = forms.GridColumn() column2.HeaderText = 'Column 2' column2.Editable = True column2.DataCell = forms.TextBoxCell(1) self.m_gridview.Columns.Add(column2) column3 = forms.GridColumn() column3.HeaderText = 'Column 3' column3.Editable = True column3.DataCell = forms.TextBoxCell(2) self.m_gridview.Columns.Add(column3) column4 = forms.GridColumn() column4.HeaderText = 'Column 4' column4.Editable = True column4.DataCell = forms.CheckBoxCell(3) self.m_gridview.Columns.Add(column4) ``` Accessing the values in each cell of the `forms.GridView` can be done by using the index position of the value in the `.DataStore` property. ```python dialog.m_gridview.DataStore[1][2] ``` This will result in a value of 'third pick' in the above example. More details can be found in the [Eto GridView API Documentation](https://github.com/picoe/Eto/wiki/GridView). ## GroupBox A GroupBox displays a collection of controls surrounded by a border and optional title to identify the group: Like the larger dialog, the `forms.GroupBox()` requires a *Layout* to help position controls. Within the layout, controls are placed. Once the controls are created, the controls are added to rows in the layout, which is then added to the contents of the `forms.GroupBox` with the line `self.m_groupbox.Content = grouplayout`: ```python # Create a group box self.m_groupbox = forms.GroupBox(Text = 'Groupbox') self.m_groupbox.Padding = drawing.Padding(5) grouplayout = forms.DynamicLayout() grouplayout.Spacing = Size(3, 3) label1 = forms.Label(Text = 'Enter Text:') textbox1 = forms.TextBox() checkbox1 = forms.CheckBox(Text = 'Start a new row') grouplayout.AddRow(label1, textbox1) grouplayout.AddRow(checkbox1) self.m_groupbox.Content = grouplayout ``` More details can be found in the [Eto GroupBox API Documentation](http://api.etoforms.picoe.ca/html/T_Eto_Forms_GroupBox.htm). ## ImageView A control to display a single bitmap image: Creating a space for a ImageView is easy. The first 3 lines below create the `forms.ImageView()` and set its size. Populating formatting a bitmap to show in the `forms.ImageView()` is more difficult. In this sample a RHino view Capture is converted into a `drawing.Bitmap()` and set to the `.Image` property to show in the `forms.ImageView()`. ```python # Create an image view self.m_image_view = forms.ImageView() self.m_image_view.Size = drawing.Size(300, 200) self.m_image_view.Image = None # Capture the active view to a System.Drawing.Bitmap view = scriptcontext.doc.Views.ActiveView self.m_image_view.Image = Rhino.UI.EtoExtensions.ToEto(view.CaptureToBitmap()) ``` Bitmaps may be formatted from a number of different forms. In this case the `view.CaptureToBitmap()` image is translated to an Eto bitmap using the `Rhino.UI.EtoExtensions.ToEto()` method. More details can be found in the [Eto ImageView API Documentation](http://api.etoforms.picoe.ca/html/T_Eto_Forms_ImageView.htm). ## Label The simplest Eto Control is the `forms.Label()`. It is text that normally is used to create a prompt, label, message or description for another control. ```python self.m_label = forms.Label(Text = 'Enter the Room Number:') ``` As with many controls, the line above create a name for the control `m_label`. Then the main property of a Label is the text it shows by setting the Text Property of the label. Normally this is as complex as a label needs to get, but a label also has many more properties. Additonal properties include `VerticalAlignment`, `Horizontal Alignment`, `TextAlignment`, `Wrap`, `TextColor`, and `Font`. Properties can be added to the `Label()` by including each keyword separated by a comma(`,`) as follows: ```python self.m_label = forms.Label(Text = 'Enter the Room Number:', VerticalAlignment = VerticalAlignment.Center) ``` Labels also can be created directly in layouts directly. There is a shorthand syntax when adding controls to a layout that will automatically create a `forms.Label` ```python #Adds a new Label diplaying "Camera:" layout.AddRow('Camera:', None) #Adds a new label displaying "Name :" inline with the Textbox control. layout.AddRow('Name:', forms.TextBox(Text = 'Persp1')) ``` When adding Rows or Columns, a simple string can be inserted. Eto will automatically create a Label out of the string. This is a very fast way yo make `forms.Label`. For a complete list of properties and events of the Label class, see the [Eto Label Class](http://api.etoforms.picoe.ca/html/T_Eto_Forms_Label.htm) documentation. ## LinkButton A LinkButton is a simple label that acts like a button, similar to a hyperlink: Like a standard button the `forms.LinkButton()` needs to be bound to an action through the `+=` syntax: ```python # Create LinkButton self.m_linkbutton = forms.LinkButton(Text = 'For more details...') self.m_linkbutton.Click += self.OnLinkButtonClick # Linkbutton click handler def OnLinkButtonClick(self, sender, e): webbrowser.open("http://rhino3d.com") ``` More details can be found in the [Eto LinkButton API Documentation](http://api.etoforms.picoe.ca/html/T_Eto_Forms_LinkButton.htm). ## ListBox Create a scrollable list of items to select: The `forms.ListBox()` and the `.DataStore` is required to create a `forms.ListBox`: ```python #Create ListBox self.m_listbox = forms.ListBox() self.m_listbox.DataStore = ['first pick', 'second pick', 'third pick'] self.m_listbox.SelectedIndex = 1 ``` The `self.m_listbox.SelectedIndex = 1` is optional and sets the default selected object withing the DataStore. More details can be found in the [Eto ListBox API Documentation](http://api.etoforms.picoe.ca/html/T_Eto_Forms_ListBox.htm). ## NumericUpDown Numeric control that allows the user to adjust the value with the mouse using a spinner: Controlling the behavior of the spinner when clicking on the up or down arrows is key to the `forms.NumericUpDown()` control: ```python # Create Numeric Up Down self.m_numeric_updown = forms.NumericUpDown() self.m_numeric_updown.DecimalPlaces = 2 self.m_numeric_updown.Increment = 0.01 self.m_numeric_updown.MaxValue = 10.0 self.m_numeric_updown.MinValue = 1.0 self.m_numeric_updown.Value = 5.0 ``` More details can be found in the [Eto NumericUpDown API Documentation](http://api.etoforms.picoe.ca/html/T_Eto_Forms_NumericUpDown.htm). ## PasswordBox Use the `forms.PasswordBox()` to enter passwords or sensitive data with the text masked out: The `.MaxLength` property is optional: ```python # Create Password Box self.m_password = forms.PasswordBox() self.m_password.MaxLength = 7 ``` More details can be found at the [Eto PasswordBox API Documentation](http://api.etoforms.picoe.ca/html/T_Eto_Forms_PasswordBox.htm). ## ProgressBar Use the `forms.ProgressBar` to display the progress of long running tasks: The first step is to create the `forms.ProgressBar` with its minmimu and maxmum values: ```python # Create Progress Bar self.m_progressbar = forms.ProgressBar() self.m_progressbar.MinValue = 0 self.m_progressbar.MaxValue = 10 ``` The control can be set through the `.Value` property on the control as in the last line in the example below: ```python self.m_progress = 1 # GoButton button click handler def OnGoButtonClick(self, sender, e): self.m_progress = self.m_progress + 1 if self.m_progress > 10: self.m_progress = 10 self.m_progressbar.Value = self.m_progress ``` More details can be found in the [Eto ProgressBar API Documentation](http://api.etoforms.picoe.ca/html/T_Eto_Forms_ProgressBar.htm). ## RadioButtonList Create a series of Radio buttons that allow a single selection out of the list: Creating the `forms.RadioButtonList()` control is simple. Then populate the list with a `.DataStore`: ```python # Create Radio Button List Control self.m_radiobuttonlist = forms.RadioButtonList() self.m_radiobuttonlist.DataStore = ['first pick', 'second pick', 'third pick'] self.m_radiobuttonlist.Orientation = forms.Orientation.Vertical self.m_radiobuttonlist.SelectedIndex = 1 ``` The `.Orientation` of the list can be set to `forms.Orientation.Vertical` or `forms.Orientation.Horizontal`. This is an optional property. The `.SelectedIndex` sets the initial default selected object in the list. More details can be found in the [Eto RadioButtonList API Documentation](http://api.etoforms.picoe.ca/html/T_Eto_Forms_RadioButtonList.htm). ## RichTextArea Multi-line text area with rich text formatting: This differs from the TextBox in that it is used for multi-line text entry and can accept Tab and Enter input. ```python # Create Rich Text Edit Box self.m_richtextarea = forms.RichTextArea() self.m_richtextarea.Size = drawing.Size(400, 400) ``` The text can be formatted by using keystrokes. While these keystrokes may vary on different platforms, a good list of modifier keystrokes for this control can be found on the [Microsoft MSDN Editing Commands documentation](https://msdn.microsoft.com/en-us/library/system.windows.documents.editingcommands.aspx#Anchor_3). More details can be found in the [Eto RichTextArea API Documentation](http://api.etoforms.picoe.ca/html/T_Eto_Forms_RichTextArea.htm). ## Slider A horizontal or vertical slider to select a value from a range: A slider consists of the control, the maximum and minimum values. The initial `.Value` can also be set at the time the dialog is created: ```python # Create a slider self.m_slider = forms.Slider() self.m_slider.MaxValue = 10 self.m_slider.MinValue = 0 self.m_slider.Value = 3 ``` To set the sldier into a vertical orientation, add the line: ```python self.m_slider.Orientation = forms.Orientation.Vertical ``` More details can be found in the [Eto Slider API Documentation](http://api.etoforms.picoe.ca/html/T_Eto_Forms_Slider.htm). ## Spinner A spinner to show indeterminate progress in compact space: When creating the `forms.Spinner()`, setting the `.Enabled` to `True` willl activate the spinning motion: ```python # Create Spinner self.m_spinner = forms.Spinner() self.m_spinner.Enabled = True ``` More details can be found in the [Eto Spinner API Documentation](http://api.etoforms.picoe.ca/html/T_Eto_Forms_Spinner.htm). ## TextArea Multi-line text control with scrollbars: A simplified version of the `forms.RichTextArea()`. The `forms.TextArea()` controls needs only a couple lines: ``` # Create Text Area Box self.m_textarea = forms.TextArea() self.m_textarea.Size = drawing.Size(400, 400) ``` More details can be found in the [Eto TextArea API Documentation](http://api.etoforms.picoe.ca/html/T_Eto_Forms_TextArea.htm). ## TextBox A TextBox is used to enter a string into the dialog. To check the contents of the textbox in the script, the textbox control must have a name to reference it. ```python self.m_textbox = forms.TextBox() ``` In this case the name `m_textbox` can be used to reference the control later in the class method starting on line 44: ``` # Get the value of the textbox def GetText(self): return self.m_textbox.Text ``` Just creating a new `Eto.Forms.TextBox()` is common. There are a number of additional properties of a TextBox which can be used to control the input. These properties include `MaxLength`, `PlaceholderText`, `InsertMode` and many more that can be seen in the [Eto TextBox Class](http://api.etoforms.picoe.ca/html/T_Eto_Forms_TextBox.htm). ## TreeGridView A TreeView with additional property columns: The TreeGridView takes the two most sophisticated controls in Eto, TreeView and GridView to combine them into one control. This make the control powerful, but also requires very specific syntax to work. The first two lines are standard, create the `forms.TreeGridView()` and set its size: The `forms.TreeView()` control requires some very specific syntax. The general `TreeView` container is easy enough. Set the object up and then its size. If editing of the items in the tree, then the `.LabelEdit` property can be set to `True`. ```python # Create TreeGridView self.m_treegridview = forms.TreeGridView() self.m_treegridview.Size = drawing.Size(200, 200) column1 = forms.GridColumn() column1.HeaderText = 'Tree' column1.Editable = True column1.DataCell = forms.TextBoxCell(0) self.m_treegridview.Columns.Add(column1) column2 = forms.GridColumn() column2.HeaderText = 'Prop 2' column2.Editable = True column2.DataCell = forms.TextBoxCell(1) self.m_treegridview.Columns.Add(column2) column3 = forms.GridColumn() column3.HeaderText = 'Prop 3' column3.Editable = True column3.DataCell = forms.TextBoxCell(2) self.m_treegridview.Columns.Add(column3) treecollection = forms.TreeGridItemCollection() item1 = forms.TreeGridItem(Values=('node1', 'node1b', 'node1c')) item1.Expanded = True item1.Children.Add(forms.TreeGridItem(Values=('node2', 'node2b', 'node2c'))) item1.Children.Add(forms.TreeGridItem(Values=('node3', 'node3b', 'node3c'))) treecollection.Add(item1) item2 = forms.TreeGridItem(Values=('node11', 'node11b', 'node11c')) treecollection.Add(item2) self.m_treegridview.DataStore = treecollection ``` After setting up the `forms.TreeGridView()` the columns to display in the control need to be created as `form.GridColumn()`. The `.DataCell` property points to the `forms.TextBoxCell(index)` that exists in the `.DataStore` assigned at the last line of this script. The information for for a tree is stored into a `forms.TreeGridCollection()`. Items within the tree are a `forms.TreeGridItems` that have `.Values` of tuples. Each tuple will populate a row in the `forms.TreeGridView()`. The `forms.TreeGridView` does not automatically update it contents. After all the control is setup, the `DataStore` is set to the `treecollection`. Doing this is a different order may end up in a control that does not display the data. More details can be found in the [Eto TreeGridView API Documentation](http://api.etoforms.picoe.ca/html/T_Eto_Forms_TreeGridView.htm). ## WebView Display a live web page in a panel: Creating the `forms.WebView()` is simple by creating the webview, then to set its size. The starting web URL can be set through the use of a `System.Uri` set to the `.Url` property: ```python # Create a WebView self.m_webview = forms.WebView() self.m_webview.Size = drawing.Size(300, 400) self.m_webview.Url = System.Uri('http://developer.rhino3d.com/guides/rhinopython/') ``` More details can be found in the [Eto WebView API Documentation](http://api.etoforms.picoe.ca/html/T_Eto_Forms_WebView.htm). ## Sample Dialogs Now with some understanding of Eto Dialogs in Python, take a look at some of the Sample dialogs in the [Python Developer Samples Repo](https://github.com/mcneel/rhino-developer-samples/blob/master/rhinopython): 1. [A very simple dialog](https://github.com/mcneel/rhino-developer-samples/blob/6/rhinopython/SampleEtoDialog.py) 2. [Rebuild curve Dialog](https://github.com/mcneel/rhino-developer-samples/blob/6/rhinopython/SampleEtoRebuildCurve.py) 3. [Capture a view dialog](https://github.com/mcneel/rhino-developer-samples/blob/6/rhinopython/SampleEtoViewCaptureDialog.py) 4. [Collapsable controls on a Dialog](https://github.com/mcneel/rhino-developer-samples/blob/6/rhinopython/SampleEtoCollapsibleDialog.py) ## Related Topics - [RhinoScriptSyntax User interface methods](/api/RhinoScriptSyntax/win/#userinterface) - [Custom Eto Forms in Python guide](/guides/rhinopython/eto-forms-python/) - [Eto Layouts in Python](/guides/rhinopython/eto-layouts-python/) guide - [Eto Controls in Python](/guides/rhinopython/eto-controls-python/) guide -------------------------------------------------------------------------------- # Custom Package Repositories Source: https://developer.rhino3d.com/en/guides/yak/package-sources/ This is a quick guide to configuring custom package repositories in Rhino. By default Rhino uses the official McNeel package server - https://yak.rhino3d.com. In addition (or instead!), it's possible to configure Rhino to use your own package repositories. A custom package repository is simply a folder that contains .yak package files. The folder can be on the local machine or on a shared file server. You can configure Rhino to include packages from this repository in the Package Manager by following the steps below. 1. Go to _Options > Advanced_ and look for the `Rhino.Options.PackageManager.Sources` setting. 1. Add the full path to your package repository folder, separating it from the default package server with a semi-colon, e.g. `https://yak.rhino3d.com;C:\rhino_packages`. 1. Run the `_PackageManager` command and search for one of the packages that you added to the new package repository. for now it just supports whatever Directory.EnumerateFiles() supports, which as far as I can tell is regular paths, mapped drives (Windows), UNC paths (Windows) and mounted shares (macOS) ### Tips for shared folders On Windows use the UNC path, i.e. `\\server\share\packages`. If the share requires credentials then first navigate to `\\server` in Explorer, log in and check the "remember my credentials" box. On macOS the file share needs to be mounted first in Finder via _Go > Connect to Server..._ (⌘ + K). Enter the address (`smb://server/share`) and provide credentials if required. Now the mounted path can be used as a package source, i.e. `/Volumes/share/packages`. The mount isn't persistent, so it'll need to be remounted in future. ### Administrator-Enforced Settings See [Administrator-Enforced Settings](https://docs.mcneel.com/rhino/8/help/en-us/index.htm#information/admin-enforced_settings.htm) for tips on how to deploy and enforce this setting for Windows users in your organisation. ### Performance Rhino 8.15 included some performance improvements for private package repositories. The yak.exe tool has a new “cache” command that, when run inside the private package folder, will generate an index of the available packages. When the package manager sees this index file, it will use it _instead of_ traversing the directory. This greatly reduces the time it takes for the Package Manager to load when dealing with private repositories with many packages, large packages, or slow network connections. ``` $ cd X:\private\repo\directory $ "C:\Program Files\Rhino 8\System\yak.exe" cache Building cache for local package repository in X:\private\repo\directory [...] ``` Make sure to re-build the cache if package files are added or removed from the directory. Remove the _.cache*_ files from the directory to revert to the old behaviour. -------------------------------------------------------------------------------- # Pushing a Package to the Server Source: https://developer.rhino3d.com/en/guides/yak/pushing-a-package-to-the-server/ This is a step by step guide to pushing a package to the package server. See the [package server](/guides/yak/the-package-server/) guide for more information about the McNeel public package server. Yak is cross-platform. The examples below are for Windows. For Mac, replace the path to the Yak CLI tool with "https://developer.rhino3d.com/Applications/Rhino 8.app/Contents/Resources/bin/yak". ## Authentication Before you can push a package to the server, you need to authorize the Yak CLI tool using your Rhino Account. ```commandline > "C:\Program Files\Rhino 8\System\Yak.exe" login ``` A browser tab should open asking you to log in to Rhino Accounts (assuming you are not already logged in). The next window will ask you to give "Yak" access to your account. - **View basic info about you**: This scope is used to retrieve your name, locale and profile picture. This information will be used in the future, when the package database has a graphical interface. - **Verify your identity**: Used for authentication when querying package ownership. - **View your email address**: Your primary email address is stored so that you can be [added as an owner](../yak-cli-reference/#owner) of packages that others have published. Once you've accepted, the browser window will close itself. Yak has retrieved an OAuth token from Rhino Accounts and has stored this on your computer. - Mac - `~/.mcneel/yak.yml` - Windows - `%APPDATA%\McNeel\yak.yml` For security, the OAuth token is valid for a limited time only. Don't be surprised if the Yak CLI tool requires you to log in again after 30 days or so. ## Push! Now that you're logged in, it's possible to push a package to the server. I'll use the package created in the [previous guide](../creating-a-grasshopper-plugin-package) as an example. ```commandline > "C:\Program Files\Rhino 8\System\Yak.exe" push marmoset-1.0.0-rh6_18-any.yak ``` This command doesn't produce any output on success. You can check that your package has been successfully pushed by searching for it. You should see the name and version number of the package that you just pushed. 🤞 ```commandline > "C:\Program Files\Rhino 8\System\Yak.exe" search --all --prerelease marmoset marmoset (1.0.0) ``` If this is your first time, why not try pushing to the test server first? ```cmd "C:\Program Files\Rhino 8\System\Yak.exe" push --source https://test.yak.rhino3d.com marmoset-1.0.0-rh6_18-any.yak "C:\Program Files\Rhino 8\System\Yak.exe" search --source https://test.yak.rhino3d.com --all --prerelease marmoset marmoset (1.0.0) ``` This server is wiped clean each night. ## Troubleshooting There are a few reasons why pushing a package might not work. - Invalid `manifest.yml` _The error message should be self explanatory. Fix it up and try again! You can also try validating the YAML syntax itself with a [linter](http://www.yamllint.com)._ - The package name already exists, but you're not an **owner**. _Only package **owners** are permitted to push new versions of their packages. When a user pushes the first version of a package, they become its **owner**. Additional owners can be added with the [`owner`](../yak-cli-reference/#owner) command._ - The package version already exists. _In order to prevent disruption to others who are using one of your packages, it's not possible to delete or overwrite versions. Roll the version number and let your users know that there's something new for them to try!_ - Push something that you didn't mean to? _Use the [`yank` command](../yak-cli-reference/#yank) to unlist a specific version._ ## Related Topics - [The Package Server](/guides/yak/the-package-server/) - [Creating a Grasshopper Plug-in Package](/guides/yak/creating-a-grasshopper-plugin-package/) - [Creating a Rhino Plug-in Package](/guides/yak/creating-a-rhino-plugin-package/) -------------------------------------------------------------------------------- # Running a Python script in Rhino Source: https://developer.rhino3d.com/en/guides/rhinopython/python-running-scripts/ This guide demonstrates how to run a Python script in Rhino. ## Running Scripts The RunPythonScript command is used to execute script subroutines that were loaded into the Python engine. ## RunPythonScript operation The RunScript dialog box will display a file dialog box. Select a Python file (.py) to run in Rhino. Simply select the python file and hit OK. The Python script will run. ## Scripting the RunPythonScript command As is the case with most Rhino commands, the RunPythonScript command can be scripted, thus bypassing the interactive dialog box. To script the RunPythonScript command, simply precede the command name with a hyphen when entering the command on Rhino's command line. For example: -RunPythonScript After entering the command, you will be prompted to enter the name of the script to run. ## Assigning the RunPythonScript command to a button. The RunPythonScript command can also be assigned to command aliases or to a toolbar button. RunPythonScript ## Related Topics - [What is RhinoPython?](/guides/rhinopython/what-is-rhinopython) - [Your First Python Script in Rhino (Windows)](/guides/rhinopython/your-first-python-script-in-rhino-windows) - [Running Scripts](/guides/rhinopython/python-running-scripts) - [Canceling Scripts](/guides/rhinopython/python-canceling-scripts) - [Editing Scripts](/guides/rhinopython/python-editing-scripts) -------------------------------------------------------------------------------- # Creating a script and module Source: https://developer.rhino3d.com/en/guides/rhinopython/python-remote-local-module/ How to create a Python definition that is both a importable module and a script. ## Overview A Python script normally can be full of functions that can be imported as a library of functions in other scripts, or a python script can be a command that runs in Rhino. There is a way to have Python definitions be both a library of functions and a direct command. The key is to add these statements to the end of the file: ```python if __name__ == '__main__': CreateCircle() # Put the a call to the main function in the file. ``` ## A complete script example Here is a complete Python sample that shows how the `if __name__ == '__main__':` statement can be added to a Python script: ```python # Create a circle from a center point and a circumference. import rhinoscriptsyntax as rs import math def CreateCircle(circumference=None): center = rs.GetPoint("Center point of circle") if center: plane = rs.MovePlane(rs.ViewCPlane(), center) length = circumference if length is None: length = rs.GetReal("Circle circumference") if length and length>0: radius = length/(2*math.pi) objectId = rs.AddCircle(plane, radius) rs.SelectObject(objectId) return length return None # Check to see if this file is being executed as the "Main" python # script instead of being used as a module by some other python script # This allows us to use the module which ever way we want. if __name__ == '__main__': CreateCircle() ``` The CreateCricle file above will run as a script to create a circle. But it can be used in the *UseModule.py* script as an imported module, as follows: ```python # This script uses a function defined in the CircleFromLength.py # script file import CircleFromLength # call the function a few times just for fun using the # optional parameter length = CircleFromLength.CreateCircle() if length is not None and length>0.0: for i in range(4): CircleFromLength.CreateCircle(length) ``` ## How it works When the Python interpreter reads a source file, it executes all of the code found in it. Before executing the code, it will define a few special variables. If Python is running the file as the main program then Python will create a `__name__` variable with the value of *__main__*. If python is importing the file as an import into an already running *__main__* the `__name__` variable will be set to the module's name in the modules scope. One of the reasons for doing this is that sometimes you write a module (a .py file) where it can be executed directly. Alternatively, it can also be imported and used in another module. By doing the main check, you can have that code only execute when you want to run the module as a program and not have it execute when someone just wants to import your module and call your functions themselves. -------------------------------------------------------------------------------- # Eto Layouts in Rhino.Python Source: https://developer.rhino3d.com/en/guides/rhinopython/eto-layouts-python/ Using the Eto DynamicLayout to organize controls. [Eto is an open source cross-platform dialog box framework](https://github.com/picoe/Eto/wiki) available in Rhino 6. This guide demonstrates the syntax required to create Layouts. Layouts are containers in which controls are placed. The Layout will position the controls in a dialog or another Layout. It is possible to nest Layouts within each other to handle more complex Layouts. For details on creating a complete Eto Dialog in Rhino.Python go to the [Getting Started with Eto Guide](/guides/rhinopython/eto-forms-python/). The samples in this guide can be added to the controls section of the Basic dialog framework covered in the [Getting Started Guide](/guides/rhinopython/eto-forms-python/). There are [5 different layout styles in Eto](https://github.com/picoe/Eto/wiki/Containers). - [Panel](https://github.com/picoe/Eto/blob/develop/Source/Eto/Forms/Controls/Panel.cs) - Controls that subclass `Panel`, such as `Window`, `GroupBox`, `Scrollable`, etc allow you to specify a single child Content control - [TableLayout](https://github.com/picoe/Eto/wiki/TableLayout) - Similar to how an HTML table works, with a single control per cell - [PixelLayout](https://github.com/picoe/Eto/wiki/PixelLayout) - Specify X,Y coordinates for the position of each control (from Upper-Left) - [DynamicLayout](https://github.com/picoe/Eto/wiki/DynamicLayout) - Dynamic hierarchical horizontal and vertical layout - [StackLayout](https://github.com/picoe/Eto/wiki/StackLayout) - Horizontal or Vertical list of controls This guide will focus on using the [DynamicLayout](https://github.com/picoe/Eto/blob/develop/Source/Eto/Forms/Layout/DynamicLayout.cs. ) style only. The `forms.DynamicLayout()` has a clear simple syntax in Python and is quite flexible. ## Dynamic Layout A layout in Eto is a virtual grid in which controls are placed. The layout arranges its child controls in a table structure of rows and columns. It is similar to the HTML table layout. Understanding how rows and columns interact with each other is the key to using layouts effectively. ## AddRow Method In its simplest form a layout is built row by row using the `.AddRow` method. Each `.AddRow` may contain one or more controls. If one control is added for each row a simple stack of rows are created and the controls will stretch to the width of the Layout: ```python layout = forms.DynamicLayout() layout.Spacing = drawing.Size(5, 5) layout.AddRow(label) layout.AddRow(image) layout.AddRow(button-capture) layout.AddRow(button-cancel) ``` Each successive `.AddRow` will add a new row to the table. Of course dialogs are not often made up of only one control per row. `.AddRow` will create a new column cell for each control added with a single `.AddRow` statement: ```python layout = forms.DynamicLayout() layout.Spacing = drawing.Size(5, 5) layout.AddRow(label, textbox) layout.AddRow(button-OK, button-Cancel) ``` Because there are two controls added to each row, the layout creates a table with two cells per row. Not only the cells created, but the controls are lined up vertically based on the number of cells in each row. In this case the first column is sized to fit the larger text label. The *OK* button then must stretch to fill that column. Then textbox and *Cancel* button will also be sized the same. > Understanding that successive `.AddRow` statements will create cells that are vertically aligned is an important concept. The row with the most controls dictates the number of colums in the table. A single strong vertical alignment throughout the dialog may not work, especially is there is a different number of controls on each row. There is a need to be able to control how controls are aligned and sized. Use breaks and spacers to create more sophisticated layouts. ## Vertical Break Tables in a `.DynamicLayout()` can be separated using the `.BeginVertical()` and `.EndVertical`. The dynamic layout intrinsically starts with a vertical section. Additional breaks between tables can be created by placing a vertical break. The next `.AddRow()` will start a new table and the strong vertical alignment with previous rows is broken. The example below shows how the justification of the rows does not have to lie within a single grid: ```python layout = forms.DynamicLayout() layout.Spacing = drawing.Size(5, 5) layout.AddRow('Camera:') layout.BeginVertical() layout.AddRow(label-name, textbox1) layout.AddRow(label-lens, textbox2) layout.AddRow(label-projection, textbox3) layout.EndVertical() layout.BeginVertical() layout.AddRow(None, button-OK, button-cancel, None) layout.EndVertical() ``` In addition to the `.BeginVertical()` there is also `.BeginHorizontal()` and`.BeginCentered()` to start new sections. For detail on these, look in the comments in the [Eto DynamicLayout.cs file](https://github.com/picoe/Eto/blob/cf4c31951a28de8d0bf17270719526a96fc689fe/Source/Eto/Forms/Layout/DynamicLayout.cs) ## None spacer Using the *None* value in Python, spacers can be added in cells or as rows. *None* spacers will dynamically resize to fit the space needed. For instance use *None* to fill cells on rows to correctly layout a dialog: ```python layout = forms.DynamicLayout() layout.Spacing = drawing.Size(5, 5) layout.BeginVertical() layout.AddRow(label-resolution, None) layout.AddRow(label-height, textbox2) layout.AddRow(label-width, textbox3) layout.AddRow(None, checkbox) layout.EndVertical() layout.BeginVertical() layout.AddRow(None, button-OK, button-cancel, None) layout.EndVertical() ``` ## Add Separate Row The `AddSeparateRow()` method creates a single row of controls separated both horizontally and vertically from adjacent tables all in one single statement. The code below creates the same dialog as above, but uses the `.AddSeparateRow()` method: ```python layout = forms.DynamicLayout() layout.Spacing = drawing.Size(5, 5) layout.AddRow('Camera:') layout.BeginVertical() layout.AddRow(label-name, textbox1) layout.AddRow(label-lens, textbox2) layout.AddRow(label-projection, textbox3) layout.EndVertical() layout.AddSeparateRow(None, button-OK, button-cancel, None) ``` The `layout.AddSeparateRow(None, button-OK, button-cancel, None)` row is functionally equivalent to: ```python layout.BeginVertical() layout.BeginHorizontal() layout.AddRow(None, button-OK, button-cancel, None) layout.EndHorizontal() layout.EndVertical() ``` This makes `AddSeparateRow()` very convenient to create a set of OK and Cancel buttons row at the bottom of a dialog box. ## Additional Samples Now with some understanding of Eto Layouts, take a look at some of the Sample dialogs in the [Python Developer Samples Repo](https://github.com/mcneel/rhino-developer-samples/blob/master/rhinopython): 1. [A very simple dialog](https://github.com/mcneel/rhino-developer-samples/blob/master/rhinopython/SampleEtoDialog.py) 2. [Rebuild curve Dialog](https://github.com/mcneel/rhino-developer-samples/blob/master/rhinopython/SampleEtoRebuildCurve.py) 3. [Capture a view dialog](https://github.com/mcneel/rhino-developer-samples/blob/master/rhinopython/SampleEtoViewCaptureDialog.py) 4. [Collapsible controls on a Dialog](https://github.com/mcneel/rhino-developer-samples/blob/master/rhinopython/SampleEtoCollapsibleDialog.py) ## Related Topics - [RhinoScriptSyntax User Interface methods](/api/RhinoScriptSyntax/win/#userinterface) - [Custom Eto Forms in Python guide](/guides/rhinopython/eto-forms-python/) - [Eto Layouts in Python](/guides/rhinopython/eto-layouts-python/) guide - [Eto Controls in Python](/guides/rhinopython/eto-controls-python/) guide -------------------------------------------------------------------------------- # APIs Available to Python Source: https://developer.rhino3d.com/en/guides/rhinopython/apis-for-python/ This guide covers the APIs available to Python in Rhino. ## Overview Python is an extensible programming language with hundreds of additional modules. This allows Python to be used in many situations both inside Rhino and outside Rhino. There are some specific additional models that are specifically of interest related to Rhino and Grasshopper. These new modules can be accessed in a Python script by including the `import` method at the top of each Python script: ```python import rhinoscriptsyntax as rs import math ``` ## RhinoScriptSyntax The RhinoScriptSyntax methods library contains hundreds of easy-to-use functions that perform a variety of operations on Rhino. The library allows Python to be aware of the Rhino's: * Geometry * Commands * Document objects * Application methods To make these methods easy-to-use, all RhinoScriptSyntax methods return simple Python variables or Python List-based data structures. Thus, once you are familiar with Python, you will be able to use any and all functions in the RhinoScriptSyntax methods library. RhinoScriptSyntax is divided into [modules](/api/RhinoScriptSyntax/win) that mirror standard Rhino commands. A good set of tutorials on using RhinoScript methods can be found in the [Python in Rhino tutorials](https://developer.rhino3d.com/guides/rhinopython/#python-in-rhino) For those that are familiar with the RhinoScript language in Rhino for Windows. RhinoScriptSyntax is meant to duplicate the functionality provided by the RhinoScript language. If you are familiar with RhinoScript in Rhino for Windows the transition to Python in Rhino should be natural. ## RhinoCommon RhinoCommon is an extensive, low level .NET library of the Rhino SDK. It is meant for more experienced programmers that would like to most extensive access to Rhino and its classes. An important detail that will become important as you are learning to script Rhino with Python is that the implementation of Python that is embedded in Rhino is called IronPython: a Python implementation in C# that runs on the .Net/mono platform which means that in addition to the Python language features and the [rhinoscriptsyntax package](/api/RhinoScriptSyntax/win), you also have access to all the libraries in .Net/mono and [RhinoCommon](../../rhinocommon/what-is-rhinocommon/). The rhinoscriptsyntax package is implemented on top of RhinoCommon so reading the source code is a great way to learn how to use RhinoCommon from Python. The source code files are included in the Rhino distribution on your computer and the default location is: ``` /Users//Library/Application Support/McNeel/Rhinoceros/MacPlugIns/ironpython/settings/lib/rhinoscript ``` ## Common Python Modules There are many modules other modules for Python. Here are a list of the most useful to modules for Rhino.Python are: * Date and Time * datetime — Basic date and time types * time — Time access and conversions * Numeric and Mathematical Modules * math — Mathematical functions * fractions — Rational numbers * random — Generate pseudo-random numbers * File and Directory Access * System.IO — Common pathname manipulations * tempfile — Generate temporary files and directories * csv — CSV File Reading and Writing * String Services * string — Common string operations * StringIO — Read and write strings as files * fpformat — Floating point conversions A complete list of predefined modules in Python, see the [Python Standard Library modules](https://docs.python.org/3/library/) ## Python Package Index (PyPI) PyPI is a repository of software for the Python programming language with over 500,000 packages. Popular PiPy cpackage are: * Pandas - data analysis toolkit * Numpy - popular scientific computing with Python * Scipy - modules for statistics, optimization, integration, linear algebra, Fourier transforms, signal and image processing * pyyaml - human-readable YAML-serialized data * Pillow - Python Imaging Library * openpyxl - library to read/write Excel 2010 xlsx/xlsm/xltx/xltm files. The [PyPI website](https://pypi.org/) can be searched for the many packages that are availble. ## Related Topics - [What are Python and RhinoScriptSyntax?](/guides/rhinopython/what-is-rhinopython) - [Python Basic Syntax](/guides/rhinopython/python-statements/) - [Python Procedures](/guides/rhinopython/python-procedures/) - [Rhinoscript Syntax in Python](/guides/rhinopython/python-rhinoscriptsyntax-introduction/) - [Rhino.Python Home Page](/guides/rhinopython/) - [Python (homepage)](https://www.python.org/) -------------------------------------------------------------------------------- # Module Libraries in Python Source: https://developer.rhino3d.com/en/guides/rhinopython/python-modules/ This guide covers the various ways to import modules Python in Rhino. ## Overview Python is an extensible programming language with hundreds of additional modules. This allows Python to be used in many situations both inside Rhino and outside Rhino. There are some specific additional models that are specifically of interest related to Rhino and Grasshopper. These new modules can be accessed in a Python script by including the `import` method at the top of each Python script: ```python import rhinoscriptsyntax as rs import math ``` ## RhinoScriptSyntax The RhinoScriptSyntax methods library contains hundreds of easy-to-use functions that perform a variety of operations on Rhino. The library allows Python to be aware of the Rhino's: * Geometry * Commands * Document objects * Application methods To make these methods easy-to-use, all RhinoScriptSyntax methods return simple Python variables or Python List-based data structures. Thus, once you are familiar with Python, you will be able to use any and all functions in the RhinoScriptSyntax methods library. RhinoScriptSyntax is divided into [modules](/api/RhinoScriptSyntax/win) that mirror standard Rhino commands. A good set of tutorials on using RhinoScript methods can be found in the [Python in Rhino tutorials](https://developer.rhino3d.com/guides/rhinopython/#python-in-rhino) For those that are familiar with the RhinoScript language in Rhino for Windows. RhinoScriptSyntax is meant to duplicate the functionality provided by the RhinoScript language. If you are familiar with RhinoScript in Rhino for Windows the transition to Python in Rhino should be natural. ## RhinoCommon RhinoCommon is an extensive, low level .NET library of the Rhino SDK. It is meant for more experienced programmers that would like to most extensive access to Rhino and its classes. An important detail that will become important as you are learning to script Rhino with Python is that the implementation of Python that is embedded in Rhino is called IronPython: a Python implementation in C# that runs on the .Net/mono platform which means that in addition to the Python language features and the [rhinoscriptsyntax package](/api/RhinoScriptSyntax/win), you also have access to all the libraries in .Net/mono and [RhinoCommon](../../rhinocommon/what-is-rhinocommon/). The rhinoscriptsyntax package is implemented on top of RhinoCommon so reading the source code is a great way to learn how to use RhinoCommon from Python. The source code files are included in the Rhino distribution on your computer and the default location is: ``` /Users//Library/Application Support/McNeel/Rhinoceros/MacPlugIns/ironpython/settings/lib/rhinoscript ``` ## Common Python Modules There are many modules other modules for Python. Here are a list of the most useful to modules for Rhino.Python are: * Date and Time * datetime — Basic date and time types * time — Time access and conversions * Numeric and Mathematical Modules * math — Mathematical functions * fractions — Rational numbers * random — Generate pseudo-random numbers * File and Directory Access * System.IO — Common pathname manipulations * tempfile — Generate temporary files and directories * csv — CSV File Reading and Writing * String Services * string — Common string operations * StringIO — Read and write strings as files * fpformat — Floating point conversions A complete list of predefined modules in Python, see the [Python Standard Library modules](https://docs.python.org/3/library/) ## Python Package Index (PyPI) PyPI is a repository of software for the Python programming language with over 500,000 packages. Popular PiPy cpackage are: * Pandas - data analysis toolkit * Numpy - popular scientific computing with Python * Scipy - modules for statistics, optimization, integration, linear algebra, Fourier transforms, signal and image processing * pyyaml - human-readable YAML-serialized data * Pillow - Python Imaging Library * openpyxl - library to read/write Excel 2010 xlsx/xlsm/xltx/xltm files. The [PyPI website](https://pypi.org/) can be searched for the many packages that are availble. ## Related Topics - [What are Python and RhinoScriptSyntax?](/guides/rhinopython/what-is-rhinopython) - [Python Basic Syntax](/guides/rhinopython/python-statements/) - [Python Procedures](/guides/rhinopython/python-procedures/) - [Rhinoscript Syntax in Python](/guides/rhinopython/python-rhinoscriptsyntax-introduction/) - [Rhino.Python Home Page](/guides/rhinopython/) - [Python (homepage)](https://www.python.org/) -------------------------------------------------------------------------------- # Using Python Packages Source: https://developer.rhino3d.com/en/guides/rhinopython/python-packages/ This guide covers the various ways to install packages in Python for Rhino. One of the powers of Python is its ability to use thousands of additional modules in its scripts. There are 4 main sources of Python packages that may be installed or loaded into Python 3. * [Built-in Modules](#built-in-modules) - Comes with the Rhino.Python install. * [Local Modules](#local-modules) - These are Libraries made from other Python files. * [Packages on PyPI](#python-package-index-pypi) - 500,000 Python Modules available online. * [Downloaded Packages](#downloading-python-libraries-modules) - For some specialized Packages, it may be necessary to download the models manually. ## Built-in Modules Python comes with many built-in modules that add functionality. To use methods within built-in modules they simply need to be imported first: ```python import math ``` Here's a list of most useful predefined modules within Python: * Date and Time * datetime — Basic date and time types * time — Time access and conversions * Numeric and Mathematical Modules * math — Mathematical functions * fractions — Rational numbers * random — Generate pseudo-random numbers * File and Directory Access * System.IO — Common pathname manipulations * tempfile — Generate temporary files and directories * csv — CSV File Reading and Writing * String Services * string — Common string operations * StringIO — Read and write strings as files * fpformat — Floating point conversions A complete list of predefined modules in Python, see the [Python Standard Library modules](https://docs.python.org/3/library/) ## Local Modules In addition to built-in modules, you can define and import your own Python functions from other files. This allows you to organize your code into reusable components. For instance in a `python_functions.py` file here are two defined functions `print_rhino_version` and `get_python_version` ```python import Rhino import sys def print_rhino_version(): ver = str(Rhino.RhinoApp.Version) versp = ver.split(".") print("Major version: " + versp[0]) print("Service Release: " + versp[1]) def get_python_version(): ver = sys.version print("Python version: " + ver) ``` Those functions then can be imported and called by: ```python import python_functions as pf pf.print_rhino_version() ``` Additional details can be found on [Functions in Python](https://diveintopython.org/learn/functions) and [Modules in Python](https://docs.python.org/3/tutorial/modules.html) ## Python Package Index (PyPI) PyPI is a repository of software for the Python programming language with over 500,000 packages. You can specify the packages required for your scripts inside the script source. This creates self-contained scripts that carry all their requirements with them. Python 3 will use pip to install packages from PyPI.org. Note: pip does not support Python 2 any longer. Popular PyPI packages are: * Pandas - data analysis toolkit * Numpy - popular scientific computing with Python * Scipy - modules for statistics, optimization, integration, linear algebra, Fourier transforms, signal and image processing * pyyaml - human-readable YAML-serialized data * Pillow - Python Imaging Library * openpyxl - library to read/write Excel 2010 xlsx/xlsm/xltx/xltm files. Looking at a Python 3 default script, we can specify required packages using this syntax: ```python # r: numpy ``` or ```python # requirements: numpy ``` Let’s create a new Python 3 script and add numpy as a package and use that in our script: ```python # r: numpy import numpy print(f"using numpy: {numpy.version.full_version}\n") for i in numpy.random.rand(10): print(i) ``` Click Run, and the script editor will attempt to install the required packages before running the script. This process might take some time and the editor is going to be disabled. Once the packages are installed, editor will continue to execute the script: Multiple packages can be install and are recommended to be listed on the same line: ```python #r: numpy, scipy ``` Additional PyPI version specifiers can also be used as referenced in [Install PyPI Syntax Guide](https://packaging.python.org/en/latest/tutorials/installing-packages/) You may search the [PyPI website](https://pypi.org/) for the many available packages. ## Downloading Python Libraries (Modules) The last way that packages can be used is to download the packages and put them into a library on the computer. This allows very tight control of the packages. This is also a good way to install packages that are not available on PyPI. Another method of adding local packages to Python scripts is by adding their path to the `sys.path`. You can simplify this step by using the # env: specifier in your scripts to automatically add a path to the `sys.path` before running your script. Note: Create a customer folder for the downloaded modules as Rhino updates may overwrite the internal defaults folders. ```python # env: C:/Path/To/Where/My/Library/Is/Located/ import mylibrary mylibrary.do_something() ``` ## Related Topics - [What are Python and RhinoScriptSyntax?](/guides/rhinopython/what-is-rhinopython) - [Python Basic Syntax](/guides/rhinopython/python-statements/) - [Python Procedures](/guides/rhinopython/python-procedures/) - [Rhinoscript Syntax in Python](/guides/rhinopython/python-rhinoscriptsyntax-introduction/) - [Rhino.Python Home Page](/guides/rhinopython/) - [Python (homepage)](https://www.python.org/) -------------------------------------------------------------------------------- # Calling Overloaded Methods from Python Source: https://developer.rhino3d.com/en/guides/rhinopython/python-overloads/ This guide discusses calling overloaded methods with IronPython. ## Problem When you run a script that calls a RhinoCommon method that has [overloads](https://docs.microsoft.com/en-us/dotnet/standard/design-guidelines/member-overloading) , you might see the following error: ``` Message: Multiple targets could match ``` ## More Information .NET supports [overloading methods](https://docs.microsoft.com/en-us/dotnet/standard/design-guidelines/member-overloading) by both number of arguments and type of arguments. When [IronPython](https://ironpython.net/) code calls an overloaded method, it tries to select one of the overloads at runtime based on the number and type of arguments passed to the method, and also names of any keyword arguments. In most cases, the expected overload gets selected. However, [IronPython](https://ironpython.net/) will raise a `TypeError` if there are conversions to more than one of the overloads: To control the exact overload that gets called, use the **Overloads** method on *method* objects: ## Example For a practical example, let's look at RhinoCommon's [Brep.Split](https://developer.rhino3d.com/api/RhinoCommon/html/Overload_Rhino_Geometry_Brep_Split.htm) method, which has five overloads: ``` // Splits a Brep into pieces using a Brep as a cutter. public Brep[] Split(Brep splitter, double intersectionTolerance) // Splits a Brep into pieces using Breps as cutters. public Brep[] Split(IEnumerable cutters, double intersectionTolerance) // Splits a Brep into pieces using curves, at least partially on the Brep, as cutters. public Brep[] Split(IEnumerable cutters, double intersectionTolerance) // Splits a Brep into pieces using a Brep as a cutter. public Brep[] Split(Brep splitter, double intersectionTolerance, out bool toleranceWasRaised) // Splits a Brep into pieces using a combination of curves, to be extruded, and Breps as cutters. public Brep[] Split(IEnumerable cutters, Vector3d normal, bool planView, double intersectionTolerance) ``` If you run the following example code, which should split a `Brep` with one or more cutting `Breps`: ``` import Rhino import scriptcontext as sc import rhinoscriptsyntax as rs id = rs.GetObject("Select Brep to split", filter = 8+16, preselect = True) brep = rs.coercebrep(id) ids = rs.GetObjects("Select cutting Breps", filter = 8+16,) cutters = [rs.coercebrep(item) for item in ids] tol = sc.doc.ModelAbsoluteTolerance out_breps = brep.Split(cutters, tol) new_ids = [sc.doc.Objects.AddBrep(brep) for brep in out_breps] ``` You will receive the following error: ``` Message: Multiple targets could match: Split(IEnumerable[Curve], float), Split(IEnumerable[Brep], float) ``` To use the second overload, use the **Overloads** method in this manner: ```python out_breps = brep.Split.Overloads[IEnumerable[Rhino.Geometry.Brep], System.Double](cutters, tol) ``` Here is the full working sample: ```python import System import System.Collections.Generic.IEnumerable as IEnumerable import Rhino import rhinoscriptsyntax as rs import scriptcontext as sc id = rs.GetObject("Select Brep to split", filter = 8+16, preselect = True) brep = rs.coercebrep(id) ids = rs.GetObjects("Select cutting Breps", filter = 8+16,) cutters = [rs.coercebrep(item) for item in ids] tol = sc.doc.ModelAbsoluteTolerance out_breps = brep.Split.Overloads[IEnumerable[Rhino.Geometry.Brep], System.Double](cutters, tol) new_ids = [sc.doc.Objects.AddBrep(brep) for brep in out_breps] ``` -------------------------------------------------------------------------------- # Generating Random Numbers in Python Source: https://developer.rhino3d.com/en/guides/rhinopython/python-random-number/ This guide discusses using Python to generate random numbers in a certain range. ## Overview Python comes with a random number generator which can be used to generate various distributions of numbers. These random number generators are suitable for generating numbers for spacial and graphical distributions. To access the random number generators, the `random` module must be imported. IMPORTANT NOTE: The pseudo-random generators of this module should not be used for security purposes. Use `os.urandom()` or `SystemRandom` if you require a cryptographically secure pseudo-random number generator. ## Simple Random numbers The basic method to create a random number from the imported `random` module is with `random.random()` ```python import random ran_number = random.random() print ran_number ``` The output from the above code is a random number between 0 and 1, say 0.280268102083 The number can be used directly or to arrive at some other number: ```python import random import rhinoscriptsyntax as rs ran_number = random.random() degrees = ran_number*360 id = rs.GetObject("Select an object to rotate randomly around the CPlane origin") if id: rs.RotateObject(id,[0,0,0], degrees) ``` Use `random.uniform()` to generate a random number between two numbers other than zero and 1: ```python import random low = 4; high = 8 ran_number = random.uniform(low, high) print ran_number ``` The above would generate a random number between 4 and 8. Note for random.uniform low and high numbers must be specified. ## Advanced Random numbers For graphic and special random distributions, many times better distributions can be generated using one of the advanced distributions. These random numbers can generate cumulative distribution functions. There are functions to compute [uniform](https://en.wikipedia.org/wiki/Uniform_distribution_(continuous)), [normal \(Gaussian\)](https://en.wikipedia.org/wiki/Normal_distribution), [lognormal](https://en.wikipedia.org/wiki/Log-normal_distribution), [negative exponential](https://en.wikipedia.org/wiki/Exponential_distribution), [gamma](https://en.wikipedia.org/wiki/Gamma_distribution), and [beta](https://en.wikipedia.org/wiki/Beta_distribution) distributions. For generating distributions of angles, the [von Mises distribution](https://en.wikipedia.org/wiki/Von_Mises_distribution) is also available. For more detailed information on these advanced number generators go to the [Generate pseudo-random numbers documentation](https://docs.python.org/2/library/random.html) The random module contains functions for finding random integers, selection of a random element from a list, a function to generate a random permutation of a list in-place, for random sampling with and without replacement, etc. The Random number generator may also be started with a seed number. `random.seed(seed_number)` When a seed is used, the generator can create a repeatable set of pseudo-random numbers. If repeatability is important, this may be worth using. Normally the current system time is used which leads to a different solution each time. ## Choosing Randomly The random functions also have the ability to choose a random element from a list. The following code will print a random selection from the string containing the alphabet. Note that processing a string of characters is equivalent to processing a list of the characters in the string. ```python import random print random.choice('abcdefghijklmnopqrstuvmxyz') ​``` `random` can also generate a set of samples from a larger list of values. The following code will return 3 randomly chosen samples from the list of numbers. ​```python import random my_list = [1, 2, 3, 4, 5] my_samp = 3 print random.sample(my_list, my_samp) ``` For more detailed information on these advanced methods go to the [Generate pseudo-random numbers documentation](https://docs.python.org/2/library/random.html) ## Os and System Random For a random number generator that does not rely on the software state and for which the sequences are not reproducible, use the SystemRandom method. This function returns random bytes from an OS-specific randomness source. ```python import sys import random key_num = random.SystemRandom() print key_num.random() #produces a number between 0 and 1 print key_num.randint(0, sys.maxint) # produces a integer between 0 and the highest allowed by the OS. ``` ## Related Links - [Generate pseudo-random numbers documentation](https://docs.python.org/2/library/random.html) -------------------------------------------------------------------------------- # Installing and Managing Packages Source: https://developer.rhino3d.com/en/guides/yak/installing-and-managing-packages/ This is a step by step guide to installing and uninstalling a Yak package using the CLI. Yak is cross-platform. The examples below are for Windows. For Mac, replace the path to the Yak CLI tool with "https://developer.rhino3d.com/Applications/Rhino 8.app/Contents/Resources/bin/yak". ## Install Installing a yak package with the CLI tool is simple. ```commandline > "C:\Program Files\Rhino 8\System\Yak.exe" install marmoset Downloading marmoset (1.0.0)... Downloaded marmoset (1.0.0) Installing marmoset (1.0.0)... Successfully installed marmoset (1.0.0) ``` Rhino will load new packages the next time it starts. You can also ask Yak to install a specific version. ```commandline > "C:\Program Files\Rhino 8\System\Yak.exe" install marmoset 1.0.0 ... ``` The package is installed to a special folder, similar to the Grasshopper Libraries folder but with a folder/file structure that Yak controls. ## Uninstall Packages can also be easily uninstalled using the Yak CLI tool. ```commandline > "C:\Program Files\Rhino 8\System\Yak.exe" uninstall marmoset marmoset successfully uninstalled ``` Rhino will register that the package has been uninstalled the next time it starts. ## List At any point you can check which packages are currently installed. ```commandline > "C:\Program Files\Rhino 8\System\Yak.exe" list marmoset (1.0.0) ``` ## Related Topics - [Yak Guides and Tutorials](/guides/yak/) - [Creating a Grasshopper Plug-in Package](/guides/yak/creating-a-grasshopper-plugin-package/) - [Creating a Rhino Plug-in Package](/guides/yak/creating-a-rhino-plugin-package/) -------------------------------------------------------------------------------- # Providing Arguments for By-Reference Parameters Source: https://developer.rhino3d.com/en/guides/rhinopython/python-reference/ This guide discusses calling methods with by-ref parameters with IronPython. ## Overview Python functions can return multiple objects as a *tuple*. On the other hand, .NET methods, like those provided by RhinoCommon, can only return one object as the result of a call. In order to return more than one object, by-reference, or *by-ref*, parameters are needed. These parameters are decorated with the `ref` and `out` keywords in C#. ## More Information A example of a method that requires a by-ref parameter is [Dictionary.TryGetValue](https://docs.microsoft.com/en-us/dotnet/api/system.collections.generic.dictionary-2.trygetvalue), which has a `value` [output parameter](https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/out-parameter-modifier). The method returns `true` if the dictionary contains an element with the specified key (the element value itself is returned to the parameter "value"), otherwise it returns `false`. When making calls to such methods in IronPython, we may not pass in arguments for the output parameters. Instead, the result of such .NET method call in IronPython will likely be a tuple (unless the .NET method's return type is `void` and the method has only one by-ref parameter), which contains the value of the output parameter (see the example below). ```python import System d = System.Collections.Generic.Dictionary[int, str]() d[1], d[2] = 'One', 'Two' print d.TryGetValue(1) # (True, 'One') print d.TryGetValue(2)[1] # Two print d.TryGetValue(3) # (False, None) ``` We may also pass a **clr.Reference** object for the output parameter. The **clr.Reference** object has only one member "Value", which will carry the output parameter value after the call. When encountering this type of argument, the IronPython code generation and runtime does something special to update the "Value". ```python x = clr.Reference[str]() print d.TryGetValue(1, x), x.Value # True One print d.TryGetValue(3, x), x.Value # False None ``` For [reference parameters](https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/ref), we are required to pass in something. If the argument is not a **clr.Reference** object, such reference argument value will also be part of the returned tuple; otherwise, the by-ref change is tracked inside **clr.Reference**. Note, if the .NET method contains more than one by-ref parameter, Python expects the user to provide proper **clr.Reference** objects for either all or none of them. A mix of **clr.Reference** objects and normal argument/omission (for out parameter) will cause an error. ## Example For a practical example, let's look at RhinoCommon's [NurbsSurfacePointList.GetPoint](https://developer.rhino3d.com/api/RhinoCommon/html/Overload_Rhino_Geometry_Collections_NurbsSurfacePointList_GetPoint.htm) method, which has two overloads: ``` // Gets a world 3-D, or Euclidean, control point at the given u,v index. // The 4-D representation is (x, y, z, 1.0). public bool GetPoint(int u, int v, out Point3d point) // Gets a homogeneous control point at the given (u, v) index, // where the 4-D representation is (x, y, z, w). // The world 3-D, or Euclidean, representation is (x/w, y/w, z/w). public bool GetPoint(int u, int v, out Point4d point) ``` The following sample code calls each overloaded method. Notice how using **clr.Reference** eliminates the need for using the **Overloads** method, in this case. ```python import clr import Rhino import rhinoscriptsyntax as rs srf_id = rs.GetObject("Select surface", rs.filter.surface) srf = rs.coercesurface(srf_id) ns = srf.ToNurbsSurface() pt3d = clr.Reference[Rhino.Geometry.Point3d]() for u in range(0, ns.Points.CountU): for v in range(0, ns.Points.CountV): if ns.Points.GetPoint(u, v, pt3d): print pt3d pt4d = clr.Reference[Rhino.Geometry.Point4d]() for u in range(0, ns.Points.CountU): for v in range(0, ns.Points.CountV): if ns.Points.GetPoint(u, v, pt4d): print pt4d ``` -------------------------------------------------------------------------------- # Custom information on objects Source: https://developer.rhino3d.com/en/guides/rhinopython/python-user-text/ How to create and access customer user text with Python. ## User Text The Rhino allows developers to store custom information on Rhino objects and inside the Rhino 3DM file. This data is referred to as User Text. There are two types of user text: 1.Document user text - custom strings that is stored at the document level of the 3DM file. 2.Object user text - custom strings that are attached to either an object's geometry or an object's attributes. Important. When storing custom data on an object, the data can be stored on either the object's geometry (e.g. curve, surfaces, etc), or on the object's attributes. An object's attributes maintain it's layer, color and other non-geometric properties. The difference between the two storage methods is that adding or modifying user data that is located on an object's geometry will cause Rhino to place a copy of the object on the Undo stack. Thus, if you are working with large geometric object or lots of user data, it is more efficient to store object user data on an object's attributes. RhinoScript supports both document user data and object user data. The RhinoScript user data object organizes custom information by Key:Value pairs similar to that of a Windows-style initialization (INI) file or a single layer Python dictionary. For example: ``` [Section1] Key1:String1 Key2:String2 ... [Section2] Key1:String1 Key2:String2 ... ``` The `Section` value normally is normally a unique name for all the data you might store. The RhinoScriptSyntax functions only store string values. Fortunetly a Python string can be easily converted from one data type to another if needed. User Text Methods Rhino provides a standardized mechanism for users, script writers, and plug-in developers to store and retrieve simple text information on an object and in the document. This mechanism called User Text. Unlike RhinoScript's User Data methods, which stores data as Section/Entry/String, Rhino stores User Text key/value pairs. Rhino supports this method of storing object data with its GetUserText, SetUserText, GetDocumentUserText and SetDocumentUserText commands. RhinoScript supports this mechanism with the GetUserText, SetUserText, GetDocumentUserText, and SetDocumentUserText methods. User text can be stored on layers by using the GetLayerUserText and SetLayerUserText methods. User text can be stored on groups using the GetGroupUserText and SetGroupUserText methods. Runtime Data When you declare a variable within a procedure, the variable can only be accessed within that procedure. When the procedure exits, the variable is destroyed. These variables are called local variables. If you declare a variable outside a procedure, all procedures can access it. These variables are called global variables. The lifetime of these variables starts when they are declared, and ends when Rhino is closed. Note, if the Reinitialize script engine when opening new models option is enabled, all variables and procedures are destroyed when a new model is opened. RhinoScript maintains a runtime data dictionary that you can use to store data. Runtime data is similar to global variables in that it can be accessed from all procedures. But unlike global variables, the runtime data will not be destroyed if the script engine is reinitialized. Runtime data persists until Rhino is closed. Runtime data can be stored and retrieved using the GetRuntimeData and SetRuntimeData methods. User Data Methods Method Description AttributeDataCount Returns the number of RhinoScript user data items stored on an object's attributes. DeleteAttributeData Removes RhinoScript user data stored on an object's attributes. DeleteDocumentData Removes RhinoScript user data stored in the current document. DeleteObjectData Removes RhinoScript user data stored on an object's geometry. DocumentDataCount Returns the number of RhinoScript user data items stored in the current document. GetAttributeData Returns a RhinoScript user data item stored on an object's attributes. GetDocumentData Returns a RhinoScript user data item stored in the current document. GetDocumentUserText Returns user text that is stored in the document. GetGroupUserText Returns user text that is stored on a group. GetLayerUserText Returns user text that is stored on a layer. GetObjectData Returns a RhinoScript user data item stored on an object's geometry. GetRuntimeData Retrieves data from RhinoScript's runtime data dictionary. GetUserText Returns user text that is stored on an object. IsAttributeData Verifies that an object's attributes contains RhinoScript user data. IsDocumentData Verifies that the current document contains RhinoScript user data. IsDocumentUserText Verifies that the document contains user text. IsObjectData Verifies that an object's geometry contains RhinoScript user data. IsUserText Verifies that an object contains user text. ObjectDataCount Returns the number of RhinoScript user data items stored on an object's geometry. RuntimeDataCount Returns the number of key/value pairs in RhinoScript's runtime data dictionary. SetAttributeData Adds or sets a RhinoScript user data item stored on an object's attributes. SetDocumentData Adds or sets a RhinoScript user data item stored in the current document. SetDocumentUserText Sets or removes user text stored in the document. SetGroupUserText Sets or removes user text stored on a group. SetLayerUserText Sets or removes user text stored on a layer. SetObjectData Adds or sets a RhinoScript user data item stored on an object's geometry. SetRuntimeData Adds, modifies, or removes data from RhinoScript's runtime data dictionary. SetUserText Sets or removes user text stored on an object. ## Overview Prompting the user of a script fo the input of a value, selecting a layer, picking a point or selecting a Rhino object is important to many interactive scripts. The RhinoscriptSyntax module contains many ways to interactively prompt for several different types of input. There are three main styles of input that are contained in Rhinosciptsyntax: - Get methods. These are methods that work with the command line, wait for mouse input or prompt for specifc input. - Dialog methods. There are some simple specfic dialogs to prompt for input - File sytem dialogs. Browsing, saving and opening files on the system with Python. Many input methods will also validate the user input to make sure only the proper input is accepted. rs.SetDocumentUserText ### The GET methods #### GetPoint() Use `rs.GetPoint()` to ask the user for a single point location, say for the center of a circle. Like most if not all of the Get methods, rs.GetPoint() allows you to specify some parameters- in this case they are all optional, the function will run without any of them specified in the code you type. For example, the default prompt is "Pick point", but you can specify a different prompt, for example, "Set center point" depending on what you wish to convey to the user. ```python pt = rs.GetPoint("Set center point") ``` If the function succeeds, a Rhino point is returned, which can be treated as a list of three numbers representing the world x, y and z coordinates of the point. ```python import rhinoscriptsyntax as rs pt = rs.GetPoint("Click to get information about a point location") if pt is not None:# note it is a good idea to check if there is a result you can use print "That point has an x coordinate of " + str(pt[0]) # when you build a string that includes elements that are not text, convert to a string with str() ``` #### GetPoints() Use `rs.GetPoints()` to ask the user for multiple point locations. As in rs.GetPoint(), all parameters are optional. Note that there is a separate prompt for the first point, and a second one for subsequent points. You need to set the parameters in order, separated by commas. If you do not want to specify a paramter at all, and accept the default, you can leave it out but you must then specify any following parameters explicitly using the parameter name. For example, this will not work to set a custom first prompt: ```python import rhinoscriptsyntax as rs pts = rs.GetPoints( "Set the first point", "Set the next point") ``` Why? because the function has two paramters that come before the first prompt, 'draw_lines' and 'in_plane'. If you leave these out, you must specify what paramters you are setting explicitly in order for it to be recognized: ```python import rhinoscriptsyntax as rs pts = rs.GetPoints( message1= "Set the first point", message2= "Set the next point") ``` You could also make sure to set the other parameters even if you don't care what they are i.e. defaults are OK: ```python import rhinoscriptsyntax as rs pts = rs.GetPoints( None, None, "Set the first point", "Set the next point") ``` #### Getreal() Another common Get method is prompting for a *number* on the commandline with `rs.GetReal()`. ```python import rhinoscriptsyntax as rs # GetReal prompts on the command line with optional defaults and a minimum allowable value radius = rs.GetReal("Radius of new circle", 3.14, 1.0) if radius: rs.AddCircle( (0,0,0), radius ) ``` rs.GetReal() accepts any number, including decimals. In some cases your code may need only whole numbers- in this case use `rs.GetInteger()` There are 22 different Get methods. For details on all the Get functions in RhinoScriptSyntax for Python go to the [RhinoScriptSyntax User interface methods](/api/RhinoScriptSyntax/win/#userinterface) ## Dialog Methods The Dialog methods in RhinoScript syntax are used to prompt of with generic custom information. Dialogs can be used to draw more attention to a required interaction with the user. Dialogs generally interrupt the workflow - the script cannot continue until the dialog is dealt with by the user. #### MessageBox() The simplest dialog box is the `rs.MessageBox()` function. The rs.MessageBox() comes with many options to customize the buttons based on your needs: ```python import rhinoscriptsyntax as rs rs.MessageBox("Hello Rhino!") # Simple message dialog rs.MessageBox("Hello Rhino!", 4 | 32) # A Yes, No dialog rs.MessageBox("Hello Rhino!", 2 | 48) # An Abort, Retry dialog ``` RunPythonScript Note that rs.MessageBox() returns a value - you can set a variable to record the result from a message box so that you can tell which button the user has clicked. ```python import rhinoscriptsyntax as rs button = rs.MessageBox("Hello Rhino!", 2 | 48) # An Abort, Retry dialog ``` The value of 'button' in the code above will tell the script which button was clicked and it can proceed appropriately. See the Rhino IronPython Help for details on the available buttons and the return codes from rs.MessageBox() #### ListBox() Some of the more advanced dialogs can be polulated with custom selections: ```python import rhinoscriptsyntax as rs options = ('First Pick', 'Second Pick', 'Third Pick') if options: result = rs.ListBox(options, "Pick an option") if result: rs.MessageBox( result + " was selected" ) ``` Here is a list of dialog box methods: | Method | | | Description | |:-------|-|-|:------------| | CheckListBox | | | Displays a list of strings in a checkable list. The user can pick multiple items.| | ComboListBox | | | Displays a list of strings in a combo list. | | EditBox | | | Displays a dialog box with a multi-line edit control. | | ListBox | | | Displays a list of strings in a simple list box. The user can pick one item.| | MessageBox | | | Displays a Windows message box. | | PopupMenu | | | Displays a context-like popup menu. | | PropertyListBox | | | Displays a list of items and values in a property list. | | RealBox | | | Displays a dialog box prompting the user to enter a number. | | StringBox | | | Displays a dialog box prompting the user to enter a string. | For details on all the dialog box functions in RhinoScriptSyntax for Python go to the [RhinoScriptSyntax User interface methods](/api/RhinoScriptSyntax/win/#userinterface) ## File System dialogs Working with files and folders on the computer take a special class of dialogs. ```python import rhinoscriptsyntax as rs filename = rs.OpenFileName() if filename: rs.MessageBox(filename) ``` RunPythonScript | Method | | | Description | |:-------|-|-|:------------| | BrowseForFolder | | | Displays a Windows browse-for-folder dialog box. | | OpenFileName | | | Displays a Windows file open dialog box. | | OpenFileNames | | | Displays a Windows file open dialog box. | | SaveFileName | | | Displays a Windows file save dialog box. | ## Related Topics - [Reading and Writing files with Python](/guides/rhinopython/python-reading-writing) - [RhinoScriptSyntax User interface methods](/api/RhinoScriptSyntax/win/#userinterface) -------------------------------------------------------------------------------- # Point and Vector Methods Source: https://developer.rhino3d.com/en/guides/rhinopython/python-rhinoscriptsyntax-point-vector-methods/ This guide provides an overview of the RhinoScriptSytntax Point and Vector methods. ## Point & Vector Methods The following methods are available for creating and manipulating 3-D points and 3-D vectors. 3-D points and 3-D vectors are represented as zero-based, one-dimensional arrays that contain three numbers. For more information, see the [Points](/guides/rhinopython/python-rhinoscriptsyntax-points) and [Vectors](/guides/rhinopython/python-rhinoscriptsyntax-vectors) discussion in [RhinoScript Fundamentals](/guides/rhinopython/python-rhinoscriptsyntax-introduction). | Method | | | Description | |:--------|:-:|:-:|:--------| | IsVectorParallelTo | | | Compares two vectors to see if they are parallel. | | IsVectorPerpendicularTo | | | Compares two vectors to see if they are perpendicular. | | IsVectorTiny | | | Verifies a vector is tiny. | | IsVectorZero | | | Verifies a vector is zero. | | PointAdd | | | Adds a point or a vector to a point. | | PointArrayBoundingBox | | | Returns the bounding box of an array of 3-D points. | | PointArrayClosestPoint | | | Finds the point in an array of 3-D points that is closest to a test point | | PointArrayTransform | | | Transforms an array of 3-D points.| | PointClosestObject | | | Finds the object that is closest to a test point.| | PointCompare | | | Compares two points.| | PointDivide | | | Divides a point by a value.| | PointsAreCoplanar | | | Verifies that a list of 3-D points are coplanar.| | PointScale | | | Scales a point by a value.| | PointSubtract | | | Subtracts a point or a vector from a point.| | PointTransform | | | Transforms a point.| | ProjectPointToMesh | | | Projects one or more points onto one or more meshes.| | ProjectPointToSurface | | | Projects one or more points onto one or more surfaces | | PullPoints | | | Pulls points to a surface or a mesh object.| | VectorAdd | | | Adds two vectors.| | VectorAngle | | | Returns the angle between two 3-D vectors.| | VectorCompare | | | Compares two vectors.| | VectorCreate | | | Create a vector from two 3-D points.| | VectorCrossProduct | | | Returns the cross product of two vectors.| | VectorDivide | | | Divides a vector.| | VectorDotProduct | | | Returns the dot product of two vectors.| | VectorLength | | | Returns the length of a vector.| | VectorMultiply | | | Multiplies two vectors.| | VectorReverse | | | Reverses a vector. | | VectorRotate | | | Rotates a vector. | | VectorScale | | | Scales a vector. | | VectorSubtract | | | Subtracts two vectors. | | VectorTransform | | | Transforms a vector. | | VectorUnitaze | | | Unitizes, or normalizes, a vector. | ## Related Topics - [What is RhinoScriptSyntax and RhinoScript?](/guides/rhinopython/what-is-rhinopython) - [Python List of Points](/guides/rhinopython/python-rhinoscriptsyntax-list-points) - [Python Vectors](/guides/rhinopython/python-rhinoscriptsyntax-vectors) - [Python Lines](/guides/rhinopython/python-rhinoscriptsyntax-lines) - [Python Planes](/guides/rhinopython/python-rhinoscriptsyntax-planes) - [Python Objects](/guides/rhinopython/python-rhinoscriptsyntax-objects) - [RhinoScript Points and Vectors Methods](/guides/rhinopython/python-rhinoscriptsyntax-point-vector-methods) -------------------------------------------------------------------------------- # Line and Plane Methods Source: https://developer.rhino3d.com/en/guides/rhinopython/python-rhinoscriptsyntax-line-plane-methods/ This guide provides an overview of the rhinoscriptsytntax Line and Plane methods. ## Line and Plane Methods The following methods are available for creating and manipulating lines and planes. Lines are represented as zero-based, one-dimensional arrays containing two elements: the start point (3-D point) and the end point (3-D point). Planes are represented as zero-based, one-dimensional arrays containing four elements: the plane's origin (3-D point), the plane's x-axis direction (3-D vector), the plane's y-axis direction (3-D vector), and the plane's z-axis direction (3-D vector). For more information in [RhinoScript Fundamentals](/guides/rhinopython/python-rhinoscriptsyntax-introduction). | Method | | | Description | |:--------|:-:|:-:|:--------| | DistanceToPlane | | | Returns the distance from a plane to a point. | | EvaluatePlane | | | Evaluates a point on a plane. | | IntersectPlanes | | | Returns the point calculated by intersecting three planes. | | LineArcIntersection | | | Intersects an infinite line and an arc. | | LineBetweenCurves | | | Create a line perpendicular or tangent between two curves. | | LineBoxIntersection | | | Intersects an infinite line and an axis aligned bounding box. | | LineCircleIntersection | | | Intersects an infinite line and a circle. | | LineClosestPoint | | | Finds the point on an infinite line that is closest to a test point. | | LineCurveIntersection | | | Intersect an infinite line and a curve object. | | LineCylinderIntersection | | | Calculates the intersection of a line and a cylinder. | | LineIsFartherThan | | | Determines if the shortest distance from a line to a point or another line is greater than a specified distance. | | LineLineIntersection | | | Returns the point calculated by intersecting two lines. | | LineMaxDistanceTo | | | Finds the longest distance between the line, as a finite chord, and a point or another line. | | LineMeshIntersection | | | Intersects an infinite line with a mesh object. | | LineMinDistanceTo | | | Finds the shortest distance between the line, as a finite chord, and a point or another line. | | LinePlane | | | Returns a plane that contains the line. | | LinePlaneIntersection | | | Returns the point calculated by intersecting a line with a plane. | | LineSphereIntersection | | | Calculates the intersection of a line and a sphere. | | LineTransform | | | Transforms a line. | | MovePlane | | | Moves the origin of a plane. | | PlaneAngle | | | Calculates the angle between two points on a plane. | | PlaneArcIntersection | | | Intersects a plane and an arc. | | PlaneCircleIntersection | | | Intersects a plane and a circle. | | PlaneClosestPoint | | | Returns the closest point on a plane from a point. | | PlaneCurveIntersection | | | Intersects an infinite plane and a curve object. | | PlaneEquation | | | Returns the equation of a plane. | | PlaneFitFromPoints | | | Returns a plane through an array of points. | | PlaneFromFrame | | | Creates a plane from an origin point, X axis direction, and Y axis direction. | | PlaneFromNormal | | | Creates a plane from an origin point, and a normal direction. | | PlaneFromPoints | | | Creates a plane from three non-colinear points. | | PlanePlaneIntersection | | | Returns the line calculated by intersecting two planes. | | PlaneSphereIntersection | | | Calculates the intersection of a plane and a sphere. | | PlaneTransform | | | Transforms a plane. | | RotatePlane | | | Rotates a plane. | | WorldXYPlane | | | Returns Rhino's world XY plane. | | WorldYZPlane | | | Returns Rhino's world YZ plane. | | WorldZXPlane | | | Returns Rhino's world ZX plane. | ## Related Topics - [What is RhinoScriptSyntax and RhinoScript?](/guides/rhinopython/what-is-rhinopython) - [Python List of Points](/guides/rhinopython/python-rhinoscriptsyntax-list-points) - [Python Vectors](/guides/rhinopython/python-rhinoscriptsyntax-vectors) - [Python Lines](/guides/rhinopython/python-rhinoscriptsyntax-lines) - [Python Planes](/guides/rhinopython/python-rhinoscriptsyntax-planes) - [Python Objects](/guides/rhinopython/python-rhinoscriptsyntax-objects) - [RhinoScript Points and Vectors Methods](/guides/rhinopython/python-rhinoscriptsyntax-point-vector-methods) -------------------------------------------------------------------------------- # Rhino NURBS Geometry Overview Source: https://developer.rhino3d.com/en/guides/rhinopython/python-rhinoscriptsyntax-nurbs/ This guide provides an introduction to Rhino's NURBS Geometry from a Python perspective. ## Overview Rhino uses various geometry types. There are some basic types like [Points](/guides/rhinopython/python-rhinoscriptsyntax-points), [Vectors](/guides/rhinopython/python-rhinoscriptsyntax-vectors), [Lines](/guides/rhinopython/python-rhinoscriptsyntax-lines) and [Planes](/guides/rhinopython/python-rhinoscriptsyntax-planes). In Python, simple geometry can be described with simple lists. More complicated geometry objects such as NURBS curves, surfaces, and poly-surfaces can be created by Rhino. [Rhino Geometry Objects](/guides/rhinopython/python-rhinoscriptsyntax-objects) are referenced by an object ID by RhinoScriptSyntax Although not required, having some understanding of the computational methods used in 3D modeling and computer graphics can be helpful when developing in Rhino. A good introductory resource is the [Essential Mathematics for Computational Design](/guides/general/essential-mathematics/parametric-curves-surfaces/#32-nurbs-curves). Although all concepts are explained visually using Grasshopper®, they are directly applicable to RhinoScriptSyntax. #### NURBS Geometry All NURBS curves and surfaces share terminology and behave similarly. Since curves are easiest to describe, we will cover them in detail. A NURBS curve is defined by four things: degree, control points, knots, and an evaluation rule. #### Degree The degree of a curve is a positive whole number. This number is usually 1, 2, 3 or 5, but can be any positive whole number. NURBS lines and polylines are usually degree 1, NURBS circles are degree 2, and most free-form curves are degree 3 or 5. Sometimes the terms "linear," "quadratic," "cubic," and "quintic" are used. "Linear" means degree 1, "quadratic" means degree 2, "cubic" means degree 3, and "quintic" means degree 5. You may see references to the "order" of a NURBS curve. The order of a NURBS curve is positive whole number equal to the degree plus one (degree+1). Consequently, the degree is equal to the order minus one (order-1). It is possible to increase the degree of a NURBS curve and not change its shape. Generally, it is not possible to reduce a NURBS curve’s degree without changing its shape. #### Control Points The control points are a list of at least degree+1 points. One of easiest ways to change the shape of a NURBS curve is to move its control points. The control points have an associated number called a "weight." With a few exceptions, weights are positive numbers. When a curve’s control points all have the same weight (usually 1), the curve is called "non-rational"; otherwise the curve is called rational. The *R* in NURBS stands for *rational* and indicates that a NURBS curve has the possibility of being rational. In practice, most NURBS curves are *non-rational*. A few NURBS curves, circles and ellipses being notable examples, are always rational. #### Knots The knots are a list of degree+N-1 numbers, where N is the number of control points. Sometimes this list of numbers is called the "knot vector." In this case, the word "vector" does not mean 3D direction. This list of knot numbers must satisfy several technical conditions. The standard way to ensure that the technical conditions are satisfied is to require the numbers to stay the same or get larger as you go down the list and to limit the number of duplicate values to no more than the degree. For example, for a degree 3 NURBS curve with 11 control points, the list of numbers 0,0,0,1,2,2,2,3,7,7,9,9,9 is a satisfactory list of knots. The list 0,0,0,1,2,2,2,2,7,7,9,9,9 is unacceptable because there are four 2s and four is larger than the degree. The number of times a knot value is duplicated is called the knot’s multiplicity. In the preceding example of a satisfactory list of knots, the knot value 0 has multiplicity three, the knot value 1 has multiplicity one, the knot value 2 has multiplicity three, the knot value 3 has multiplicity one, the knot value 7 has multiplicity two, and the knot value 9 has multiplicity three. A knot value is said to be a full-multiplicity knot if it is duplicated degree many times. In the example, the knot values 0, 2, and 9 have full multiplicity. A knot value that appears only once is called a simple knot. In the example, the knot values 1 and 3 are simple knots. If a list of knots starts with a full multiplicity knot, is followed by simple knots, terminates with a full multiplicity knot, and the values are equally spaced, then the knots are called uniform. For example, if a degree 3 NURBS curve with 7 control points has knots 0,0,0,1,2,3,4,4,4, then the curve has uniform knots. The knots 0,0,0,1,2,5,6,6,6 are not uniform. Knots that are not uniform are called non-uniform. The *N* and *U* in NURBS stand for *non-uniform* and indicate that the knots in a NURBS curve are permitted to be non-uniform. Duplicate knot values in the middle of the knot list make a NURBS curve less smooth. At the extreme, a full multiplicity knot in the middle of the knot list means there is a place on the NURBS curve that can be bent into a sharp kink. For this reason, some designers like to add and remove knots and then adjust control points to make curves have smoother or kinkier shapes. Since the number of knots is equal to (N+degree-1), where N is the number of control points, adding knots also adds control points and removing knots removes control points. Knots can be added without changing the shape of a NURBS curve. In general, removing knots will change the shape of a curve. #### Knots and Control Points A common misconception is that each knot is paired with a control point. This is true only for degree 1 NURBS (polylines). For higher degree NURBS, there are groups of 2 x degree knots that correspond to groups of degree+1 control points. For example, suppose we have a degree 3 NURBS with 7 control points and knots 0,0,0,1,2,5,8,8,8. The first four control points are grouped with the first six knots. The second through fifth control points are grouped with the knots 0,0,1,2,5,8. The third through sixth control points are grouped with the knots 0,1,2,5,8,8. The last four control points are grouped with the last six knots. Some modelers that use older algorithms for NURBS evaluation require two extra knot values for a total of degree+N+1 knots. When Rhino is exporting and importing NURBS geometry, it automatically adds and removes these two superfluous knots as the situation requires. #### Evaluation Rule A curve evaluation rule is a mathematical formula that takes a number and assigns a point. The NURBS evaluation rule is a formula that involves the degree, control points, and knots. In the formula there are some things called B-spline basis functions. The B and S in NURBS stand for “basis spline.” The number the evaluation rule starts with is called a parameter. You can think of the evaluation rule as a black box that eats a parameter and produces a point location. The degree, knots, and control points determine how the black box works. ## Related Topics - [Essential Mathematics for Computational Design](/guides/general/essential-mathematics/parametric-curves-surfaces/#32-nurbs-curves) - [Rhino Geometry Objects](/guides/rhinopython/python-rhinoscriptsyntax-objects) -------------------------------------------------------------------------------- # Python Code Conventions Source: https://developer.rhino3d.com/en/guides/rhinopython/python-code-conventions/ This guide provides an overview of Python coding conventions. ## Overview Coding conventions are suggestions designed to help you write Python and RhinoScript code. Coding conventions can include the following: - Naming conventions for objects, variables, and procedures - Commenting conventions - Code Block syntax - Whitespace (spaces vs tabs) The reason for using coding conventions is to standardize the structure and style of a script or set of scripts so that you and others can easily read and understand the code. Using coding conventions results in clear, precise, and readable code that is consistent with other language conventions and is intuitive. For the official documentation on Python Syntax, see the [Style Guide for Python Code](https://www.python.org/dev/peps/pep-0008/) ## Names and Capitalization In Python names are used as identifiers of functions, classes, variables, etc.... Identifiers must start with a Letter (A-Z) or an underscore ("_"), followed by more letters or numbers. Python does not allow characters such as @, $, and % within identifier names. Python is case sensitive. So "selection" and "Selection" are two different identifiers. Normally class names will begin with capital letters and other identifiers will be all lower case. It is also common practice to start private identifiers with an underscore. The body of a variable or procedure name should use mixed case and be as descriptive as necessary. Also, procedure names should begin with a verb, such as `InitNameArray` or `ValidateLayer`. For frequently used or long terms, standard abbreviations are recommended to help keep name length reasonable. In general, variable names greater than 32 characters can be difficult to read. When using abbreviations, make sure they are consistent throughout the entire script. For example, randomly switching between `Cnt` and `Count` within a script may lead to confusion. Best practices for Python naming can be found in the [Style Guide for Python Naming Conventions](https://www.python.org/dev/peps/pep-0008/#naming-conventions). ## Comments in Python Comments in Pythons are used to leave notes in the code to better explain what is happening. Comments are ignored by the interpreter during compile. Python comments are started with a hash (#) sign. The hash sign can be used at the start of a line, followed by a single line comment. This is considered a blank line by the interpreter. ```python # My first comment # this is a second line to this comment # use multiple hash characters to make multiline comments ``` A hash sign can also be added at the end of a line of code. After the hash sign add the comment. To Python this is considered an end of statement comment. ```python print ("Hello, World!") # the second comment that I make ``` Remember the following points: - Every important variable declaration should include an inline comment describing the use of the variable being declared. - Variables and procedures should be named clearly to ensure that inline comments are only needed for complex implementation details. - At the beginning of your script, you should include an overview that describes the script, enumerating objects, procedures, algorithms, dialog boxes, and other system dependencies. Sometimes a piece of pseudocode describing the algorithm can be helpful. ## Block Statements Python does not use braces to denote block statement. Block statements are created using whitespace to the left of the lines within the block. Each following line within the block must have the same amount of white space. A good example is an if block. For example − ```python if True: print ("Your answer is True.") else: print ("Your answer is False.") ``` However, the following block generates an error − ```python if True: print ("You are ") print ("correct") else: print ("You are not") print ("correct") ``` You may also see the use of the colon (:) in the statements above. The colon is used for compound code statements (suites in Python) such as if and while loops. ## Whitepace While Python can interpret both tabs and spaces as whitespace to the left of a statement, it is recommended that spaces are used. Common practice is to use 4 spaces to denote an indentation. ## Code Formatting Screen space should be conserved as much as possible, while still allowing code formatting to reflect logic structure and nesting. Here are a few suggestions: - Indent standard nested blocks four spaces. - Indent the overview comments of a procedure one space. - Indent the highest level statements that follow the overview comments two spaces, with each nested block indented an additional two spaces. ## In Summary The following code adheres to Python coding conventions: ```python #**************************************************** # Purpose: Locates the first occurrence of a specified # layer in the LayerList array. # Inputs: arrLayerList: the list of layers to be searched. # strTargetLayer: the name of the layer to search for. # Returns: The index of the first occurrence of the # strTargetLayer in the strLayerList array. # If the target layer is not found, return -1. #**************************************************** import rhinoscriptsyntax as rs def FindLayer(LayerList, TargetLayer): FindLayer = -1 # Default return value i = 0 # Initialize loop counter blnFound = False while i < len(LayerList) and not blnFound: if LayerList[i] == TargetLayer: blnFound = True # Set flag to True FindLayer = i # Set return value to loop count i = i + 1 # Increment loop counter ``` ## Related Topics - [What are VBScript and RhinoScript?](/guides/rhinoscript/what-are-vbscript-rhinoscript) -------------------------------------------------------------------------------- # Python Dictionaries Source: https://developer.rhino3d.com/en/guides/rhinopython/python-dictionaries/ This guide discusses using Python's Dictionary object. ## Overview One of the nice features of scripting languages such as Python is what is called an associative array. An associative array differs from a "normal" array in one major respect: rather than being indexed numerically (i.e. 0, 1, 2, 3, ...), it is indexed by a key, or an English-like word. Python has something very similar to an associative array in the Dictionary object. The Python Dictionary object provides a key:value indexing facility. Note that dictionaries are unordered - since the values in the dictionary are indexed by keys, they are not held in any particular order, unlike a list, where each item can be located by its position in the list. The Dictionary object is used to hold a set of data values in the form of (key, item) pairs. A dictionary is sometimes called an associative array because it associates a key with an item. The keys behave in a way similar to indices in an array, except that array indices are numeric and keys are arbitrary strings. Each key in a single Dictionary object must be unique. Dictionaries are used when some items need to be stored and recovered by name. For example, a dictionary can hold all of the environment variables defined by the system or all the values associated with a registry key. While this can be much faster than iterating a list looking for a match, a dictionary can only store one item for each key value. That is, dictionary keys must all be unique. ## Creating Dictionaries To create an empty dictionary, use a pair of braces `{}` `room_empty = {}` To construct an instance of a dictionary object with key:item pairs filled in, use one of the following methods. The dictionary room_num is created and filled in with each key:value pair, rather than as an empty dictionary. The `key` is a string or number, in the example below it is a person's name, followed be a colon `:` as a separator from the associated value which can be any datatype, in this case an integer. Commas `,` seperate different key:value pairs in the dictionary: `room_num = {'john': 425, 'tom': 212, 'sally':325}` This dictionary is created from a list of tuples using the `dict` key word: `room_num1 = dict([('john', 425), ('tom', 212), ('sally', 325)])` The `dict`keyword can be used in other ways to construct dictionaries. ## Adding Values To add a value to a Dictionary, specify the new key and set a value. Below, the code creates the dictionary *room_num* with two key:value pairs for John and Liz, then adds a third one for Isaac: ```python room_num = {'John': 425, 'Liz': 212} room_num['Isaac'] = 345 print (room_num) ``` There is no limit to the number of values that can be added to a dictionary (within the bounds of physical memory). Changing a value for any of the keys follows the same syntax. If the key already exists in the dictionary, the value is simply updated. ## Removing Values To remove a value from a dictionary, use the `del` method and specify the key to remove: ```python room_num = {'John': 425, 'Liz': 212, 'Isaac': 345} del room_num['Isaac'] print (room_num) ``` ## Counting Values Use the `len()` property to obtain a count of values in the dictionary. ```python room_num = {'John': 425, 'Liz': 212, 'Isaac': 345} print len(room_num) ``` ## Get Values for Key The `in` syntax returns True if the specified key exists within the dictionary. For example you may want to know if Tom is included in a dictionary, in this case False: ```python room_num = {'John': 425, 'Liz': 212, 'Isaac': 345} var1 = 'Tom' in room_num print ("Is Tom in the dictionary? " + str(var1)) ``` or you may want to know if an Isaac is *not* in the dictionary. Below the answer will be also be False: ```python room_num = {'John': 425, 'Liz': 212, 'Isaac': 345} var1 = 'Isaac' not in room_num print ("Is Isaac not in room_num? " + str(var1)) ``` Use the variable name and the key value in brackets `[]` to get the value associated with the key. ```python room_num = {'John': 425, 'Liz': 212, 'Isaac': 345} var1 = room_num['Isaac'] print ("Isaac is in room number " + str(var1)) ``` The `.keys()` and `.values()` methods return an array containing all the keys or values from the dictionary. For example: ```python room_num = {'john': 425, 'tom': 212} print (room_num.keys()) print (room_num.values()) ``` ## Looping through Dictionaries Dictionaires can be used to control loops. In addition both the keys and values can be extracted at the same time using the `.items()` method: ```python room_num = {'john': 425, 'tom': 212, 'isaac': 345} for k, v in room_num.items(): print (k + ' is in room ' + str(v)) ``` You can also go through the dictionary backwards by using the `reversed()` method: ```python room_num = {'john': 425, 'tom': 212, 'isaac': 345} for k, v in reversed(room_num.items()): print (k + ' is in room ' + str(v)) ``` ## Sorting Dictionaries On occasion, it may be important to sort your dictionary. Dictionaries and be sorted by `key` name or by `values` To sort a dictionary by key using the following `sorted()` function: ```python room_num = {'john': 425, 'tom': 212, 'isaac': 345} print (sorted(room_num)) ``` To sort by `values` use the `sorted()` method along with the `.values()` function: ```python room_num = {'john': 425, 'tom': 212, 'isaac': 345} print (sorted(room_num.values())) ``` The Dictionary object is not there to replace list iteration, but there are times when it makes more sense to index your array using English-like terms as opposed to numerical values. It can be much faster to locate an object in a dictionary than in a list. -------------------------------------------------------------------------------- # VBScript Code Conventions Source: https://developer.rhino3d.com/en/guides/rhinoscript/vbscript-code-conventions/ This guide provides an overview of VBScript coding conventions. ## Overview Coding conventions are suggestions designed to help you write VBScript and RhinoScript code. Coding conventions can include the following: - Naming conventions for objects, variables, and procedures - Commenting conventions - Text formatting and indenting guidelines The main reason for using a consistent set of coding conventions is to standardize the structure and coding style of a script or set of scripts so that you and others can easily read and understand the code. Using good coding conventions results in clear, precise, and readable source code that is consistent with other language conventions and is intuitive. ## Constant Naming Earlier versions of VBScript had no mechanism for creating user-defined constants. Constants, if used, were implemented as variables and distinguished from other variables using all uppercase characters. Multiple words were separated using the underscore `_` character. For example: ```vbnet USER_LIST_MAX NEW_LINE ``` While this is still an acceptable way to identify your constants, you may want to use an alternative naming scheme now that you can create true constants using the `Const` statement. This convention uses a mixed-case format in which constant names have a "con" prefix. For example: ```vbnet conYourOwnConstant ``` ## Variable Naming To enhance readability and consistency, use the following prefixes with descriptive names for variables in your VBScript code: | Subtype | | | | Prefix | | | | Example | |:--------|:-:|:-:|:-:|:--------|:-:|:-:|:-:|:--------| | Array | | | | arr | | | | `arrLayers` | | Boolean | | | | bln | | | | `blnFound` | | Byte | | | | byt | | | | `bytRasterData` | | Date (Time) | | | | dtm | | | | `dtmStart` | | Double | | | | dbl | | | | `dblTolerance` | | Error | | | | err | | | | `errOrderNum` | | Integer | | | | int | | | | `intQuantity` | | Long | | | | lng | | | | `lngDistance` | | Object | | | | obj | | | | `objCurrent` | | Single | | | | sng | | | | `sngAverage` | | String | | | | str | | | | `strFirstName` | ## Variable Scope Variables should always be defined with the smallest scope possible. VBScript variables can have the following scope: | Scope | | | | Where Variable Is Declared | | | | Visibility | |:--------|:-:|:-:|:-:|:--------|:-:|:-:|:-:|:--------| | Procedure-level | | | | Event, Function, or Sub procedure. | | | | Visible in the procedure in which it is declared. | | Script-level | | | | Outside any procedure. | | | | Visible in every procedure in the script. | ### Variable Scope Prefixes As script size grows, so does the value of being able to quickly differentiate the scope of variables. A one-letter scope prefix preceding the type prefix provides this, without unduly increasing the size of variable names. | Scope | | | | Prefix | | | | Example | |:--------|:-:|:-:|:-:|:--------|:-:|:-:|:-:|:--------| | Procedure-level | | | | None | | | | `dblVelocity` | | Script-level | | | | s | | | | `sblnCalcInProgress` | ## Descriptive Names The body of a variable or procedure name should use mixed case and should be as descriptive as necessary. Also, procedure names should begin with a verb, such as `InitNameArray` or `ValidateLayer`. For frequently used or long terms, standard abbreviations are recommended to help keep name length reasonable. In general, variable names greater than 32 characters can be difficult to read. When using abbreviations, make sure they are consistent throughout the entire script. For example, randomly switching between `Cnt` and `Count` within a script or set of scripts may lead to confusion. ## Code Commenting All procedures should begin with a brief comment describing what they do. This description should not describe the implementation details (how it does it) because these often change over time, resulting in unnecessary comment maintenance work, or worse, erroneous comments. The code itself and any necessary inline comments describe the implementation. Arguments passed to a procedure should be described when their purpose is not obvious and when the procedure expects the arguments to be in a specific range. Return values for functions and variables that are changed by a procedure, especially through reference arguments, should also be described at the beginning of each procedure. Procedure header comments should include the following section headings: | Heading | | | | Comment Contents | |:--------|:-:|:-:|:-:|:--------| | Purpose | | | | What the procedure does (not how). | | Assumptions | | | | List of any external variable, control, or other element whose state affects this procedure. | | Effects | | | | List of the procedure's effect on each external variable, control, or other element. | | Inputs | | | | Explanation of each argument that is not obvious. Each argument should be on a separate line with inline comments. | | Return Values | | | | Explanation of the value returned. | Remember the following points: - Every important variable declaration should include an inline comment describing the use of the variable being declared. - Variables, controls, and procedures should be named clearly to ensure that inline comments are only needed for complex implementation details. - At the beginning of your script, you should include an overview that describes the script, enumerating objects, procedures, algorithms, dialog boxes, and other system dependencies. Sometimes a piece of pseudocode describing the algorithm can be helpful. ## Code Formatting Screen space should be conserved as much as possible, while still allowing code formatting to reflect logic structure and nesting. Here are a few suggestions: - Indent standard nested blocks two spaces. - Indent the overview comments of a procedure one space. - Indent the highest level statements that follow the overview comments two spaces, with each nested block indented an additional two spaces. ## In Sum The following code adheres to VBScript coding conventions: ```vbnet '**************************************************** ' Purpose: Locates the first occurrence of a specified ' layer in the LayerList array. ' Inputs: arrLayerList: the list of layers to be searched. ' strTargetLayer: the name of the layer to search for. ' Returns: The index of the first occurrence of the ' strTargetLayer in the strLayerList array. ' If the target layer is not found, return -1. '**************************************************** Option Explicit Function FindLayer(arrLayerList, strTargetLayer) Dim i ' Loop counter. Dim blnFound ' Target found flag FindLayer = -1 ' Default return value i = 0 ' Initialize loop counter Do While i <= UBound(arrLayerList) And Not blnFound If arrLayerList(i) = strTargetLayer Then blnFound = True ' Set flag to True FindLayer = i ' Set return value to loop count End If i = i + 1 ' Increment loop counter Loop End Function ``` ## Related Topics - [What are VBScript and RhinoScript?](/guides/rhinoscript/what-are-vbscript-rhinoscript) -------------------------------------------------------------------------------- # VBScript Dictionaries Source: https://developer.rhino3d.com/en/guides/rhinoscript/vbscript-dictionaries/ This guide discusses using VBScript's Dictionary object in RhinoScript. ## Overview One of the nice features of other scripting languages, such as Perl, LISP, and Python is what is called an associative array. A n associative array differs from a "normal" array in one major way: rather than being indexed numerically (i.e. 0, 1, 2, 3, ...), it is indexed by a key, or an English-like word. VBScript has something very similar to an associative array. This object is called the Dictionary object. The VBScript Dictionary object provides an item indexing facility. Dictionaries are part of Microsoft's Script Runtime Library. The Dictionary object is used to hold a set of data values in the form of (key, item) pairs. A dictionary is sometimes called an associative array because it associates a key with an item. The keys behave in a way similar to indices in an array, except that array indices are numeric and keys are arbitrary strings. Each key in a single Dictionary object must be unique. Dictionaries are frequently used when some items need to be stored and recovered by name. For example, a dictionary can hold all the environment variables defined by the system or all the values associated with a registry key. However, a dictionary can only store one item for each key value. That is, dictionary keys must all be unique. ## Creating Dictionaries To construct an instance of a dictionary object, just use the following lines of code: ```vbnet Dim objDictionary Set objDictionary = CreateObject("Scripting.Dictionary") ``` Dictionary objects have one property that should be set before any data values are stored in the dictionary. There are two modes for the .CompareMode property which control how individual keys are compared. If the mode is `vbBinaryCompare` (the default), upper and lower case letters in keys are considered distinct. If the mode is `vbTextCompare`, upper and lower case letters in keys are considered identical. This means that a Dictionary object in binary mode can contain two keys "Key" and "key", whereas these would be considered the same key in text mode. ## Adding Values To add a value to a Dictionary, use the `.Add` method. For example: ```vbnet Dim objDictionary Set objDictionary = CreateObject("Scripting.Dictionary") objDictionary.CompareMode = vbTextCompare objDictionary.Add "Pastrami", "Great" objDictionary.Add "Roast Beef", "OK on Sunday" objDictionary.Add "Salami", "Not so good" ``` The first argument to the `.Add` method is the key value and the second argument is the item value. The key is similar to the index in a numerically-based, indexed array, and the item is the value at that index. There is no limit to the number of values that can be added to a dictionary (within the bounds of physical memory). ## Removing Values To remove a value from a dictionary, use the `.Remove` method and specify the key to remove. For example: ```vbnet objDictionary.Remove "Salami" ``` To remove all values and clear the dictionary, use the `.RemoveAll` method. ## Counting Values Use the `.Count` property to obtain a count of values in the dictionary. ## Get Values for Key The `.Exists` method returns True if the specified key exists within the dictionary. For example: ```vbnet If objDictionary.Exists("Pastrami") Then Rhino.Print "Pastrami is available today." ``` To retrieve the item value for a given key, use the `.Item` property. This is the default property for a Dictionary object. For example: ```vbnet If objDictionary.Exists("Salami") Then Rhino.Print objDictionary("Salami") ``` The `Rhino.Print` statement displays the item value stored in the dictionary for the `"Salami"` key. Use the `.Key` property to change the key value for a given key. For example: ```vbnet objDictionary.Key("Salami") = "Italian Salami" ``` The `.Keys` and `.Items` methods return an array containing all the keys or items from the dictionary. For example: ```vbnet aMeats = oDict.Keys aComments = oDict.Items ``` ## Sorting Dictionaries On occasion, it may be important to sort your dictionary. You can sort a dictionary by using the following function... ```vbnet ' Description: ' Sorts a dictionary by either key or item ' Parameters: ' objDict - the dictionary to sort ' intSort - the field to sort (1=key, 2=item) ' Returns: ' A dictionary sorted by intSort ' Function SortDictionary(objDict, intSort) ' declare constants Const dictKey = 1 Const dictItem = 2 ' declare our variables Dim strDict() Dim objKey Dim strKey,strItem Dim X,Y,Z ' get the dictionary count Z = objDict.Count ' we need more than one item to warrant sorting If Z > 1 Then ' create an array to store dictionary information ReDim strDict(Z,2) X = 0 ' populate the string array For Each objKey In objDict strDict(X,dictKey) = CStr(objKey) strDict(X,dictItem) = CStr(objDict(objKey)) X = X + 1 Next ' perform a a shell sort of the string array For X = 0 To (Z - 2) For Y = X To (Z - 1) If StrComp(strDict(X,intSort),strDict(Y,intSort),vbTextCompare) > 0 Then strKey = strDict(X,dictKey) strItem = strDict(X,dictItem) strDict(X,dictKey) = strDict(Y,dictKey) strDict(X,dictItem) = strDict(Y,dictItem) strDict(Y,dictKey) = strKey strDict(Y,dictItem) = strItem End If Next Next ' erase the contents of the dictionary object objDict.RemoveAll ' repopulate the dictionary with the sorted information For X = 0 To (Z - 1) objDict.Add strDict(X,dictKey), strDict(X,dictItem) Next End If End Function ``` The Dictionary object is not there to replace the array, but there are certainly times when it makes more sense to index your array using English-like terms as opposed to numerical values. -------------------------------------------------------------------------------- # VBScript Err Objects Source: https://developer.rhino3d.com/en/guides/rhinopython/python-err-objects/ This guide discusses the VBScript Err object. ## Overview The VBScript `Err` object provides access to run-time error information. The `Err` object encapsulates errors for a VBScript script. By default, if an error occurs, VBScript terminates script execution and RhinoScript reports the error back to the user. Sometimes this default error processing is not desirable. In this case, the `Err` object and the `On Error` statement can be used to let scripts perform their own error handling. ## Details The `Err` object is a predefined global object. It does not need to be declared before it can be used. The object is used to encapsulate all the information relating to an error condition. This information is presented as a series of properties: - The `.Number` property is the error number (or code) for the error. This is the default property of the Err object. - The `.Source` property contains a string that specifies the source of the error. This is typically the `ProgID` (Programmatic Identifier) of the object that generated the error. - The `.Description` property contains a string describing the error. - The `.HelpFile` and `.HelpContext` properties store information to reference a help topic in a help file. This allows the error to refer to information on possible causes of the error. ## Using On Error To generate a user-defined run-time error, first clear the `Err` object using the `.Clear` method. Then raise the error using the `.Raise` method. This method takes up to five arguments that correspond, in order, to the properties previously listed. For example: ```vbnet Err.Clear Err.Raise 1000, "This is a script-defined error", "Test Script" ``` This example displays the standard RhinoScript error dialog box showing the error information. To intercept run-time errors and process them in scripts, use the `On Error` statement. The syntax of this statement is: ```vbnet On Error Resume Next ``` After this statement executes, the next run-time errors do not cause script execution to end. Instead, the `Err` object properties are set to reflect the error information and processing continues with the next statement. For example: ```vbnet Err.Clear On Error Resume Next Err.Raise 100, "Script Error" If Err.Number Then Rhino.Print "Error=" & CInt(Err.Number) ``` In this example, the `Err.Raise` method is used to raise a run-time error. Normally, this would stop script execution. However, the earlier `On Error` statement allows script execution to continue. So the If statement executes and displays the error number. In the preceding example, the error was generated by the `Err.Raise` method. Yet, the same processing applies to any run-time error, regardless of how it is generated. For example: ```vbnet On Error Resume Next Err.Clear x = CInt("foo") If Err.Number <> 0 Then Rhino.Print Err.Number Rhino.Print Err.Description Rhino.Print Err.Source End If ``` Here, an attempt is made to convert the string `"foo"` into an integer. Because this is an invalid conversion, a run-time type mismatch error is generated. The information on this error is placed in the `Err` object. This is then processed by the `If` statement. After an `On Error` statement executes, it remains in effect for the rest of the procedure in which it executes. If the `On Error` statement executes in global scope, it remains in effect until the script terminates. Nested procedures can each have their own `On Error` statement. For example, subroutine A can execute an `On Error` statement and then execute subroutine B, which in turn, executes an `On Error` statement. If an error occurs while an `On Error` statement is active, execution continues with the following statement in the same scope as the most recently executed `On Error` statement. For example: ```vbnet On Error Resume Next Rhino.Print "Begin" Sub1 Rhino.Print "End" Sub Sub1 Rhino.Print "Enter Sub1" Err.Raise 100 Rhino.Print "Leave Sub1" End Sub ``` In this example, an `On Error` statement executes at global scope, and then the `Sub1` procedure executes. Within this procedure, an `Err.Raise` method is used to generate an error. Because the most recently executed error is at global scope, the next statement to execute is the `Rhino.Print` statement at global scope, and not the `Rhino.Print` statement following the `Err.Raise` statement. In fact, the error causes VBScript to abandon further execution of the statements in `Sub1` and continue execution at global scope. Thus, the output of this script is: ```vbs Begin Enter Sub1 End ``` Notice that the final `Rhino.Print` statement in `Sub1` never executes. When an error occurs, VBScript immediately abandons execution of any running procedures necessary to resume with the correct statement after an error. For example: ```vbnet On Error Resume Next Rhino.Print "Begin" Sub1 Rhino.Print "End" Sub Sub1 Rhino.Print "Enter Sub1" Sub2 Rhino.Print "Leave Sub1" End Sub Sub Sub2 Rhino.Print "Enter Sub2" Err.Raise 100 Rhino.Print "Leave Sub2" End Sub ``` Here, the `Err.Raise` method invocation is nested even more deeply. In this case, when the error occurs, VBScript abandons further execution of both `Sub1` and `Sub2` to continue execution at the global level. Because VBScript abandons execution of procedures only until it finds the most recently executed `On Error` statement, it is possible to capture an error within a procedure simply by placing an `On Error` statement in that procedure. For example: ```vbnet On Error Resume Next Rhino.Print "Begin" Sub1 Rhino.Print "End" Sub Sub1 Rhino.Print "Enter Sub1" On Error Resume Next Sub2 Rhino.Print "Leave Sub1" End Sub Sub Sub2 Rhino.Print "Enter Sub2" Err.Raise 100 Rhino.Print "Leave Sub2" End Sub ``` This example modifies the previous example by adding an `On Error` statement to the `Sub1` procedure. So, when the `Err.Raise` method in `Sub2` executes, execution continues with the next statement in `Sub1`. Notice that the `Leave Sub2` line never executes. The output from this example is: ```vbs Begin Enter Sub1 Enter Sub2 Leave Sub1 End ``` ## Structured Exception Handling The `On Error` statement is a simple form of a technique known as structured exception handling. The basic idea behind this technique is to centralize all the error handling code in a single location outside of the main flow of the program. This is the reason for the complex behavior of the `On Error` statement. The assumption is that a large script might contain many procedures that interact in complex ways. It is possible that an error will occur when procedures are nested very deeply. Without the `On Error` statement, each procedure must return some form of error code to the procedure from which it was called. In turn, this procedure must do the same thing, and so on for all the nested procedures. This can greatly add to the complexity of the script. With the `On Error` statement, this complexity can be avoided. When an error occurs, VBScript automatically takes care of unwinding out of the complex nest of procedures back to the statement that followed the original procedure invocation. -------------------------------------------------------------------------------- # VBScript Err Objects Source: https://developer.rhino3d.com/en/guides/rhinoscript/vbscript-err-objects/ This guide discusses the VBScript Err object. ## Overview The VBScript `Err` object provides access to run-time error information. The `Err` object encapsulates errors for a VBScript script. By default, if an error occurs, VBScript terminates script execution and RhinoScript reports the error back to the user. Sometimes this default error processing is not desirable. In this case, the `Err` object and the `On Error` statement can be used to let scripts perform their own error handling. ## Details The `Err` object is a predefined global object. It does not need to be declared before it can be used. The object is used to encapsulate all the information relating to an error condition. This information is presented as a series of properties: - The `.Number` property is the error number (or code) for the error. This is the default property of the Err object. - The `.Source` property contains a string that specifies the source of the error. This is typically the `ProgID` (Programmatic Identifier) of the object that generated the error. - The `.Description` property contains a string describing the error. - The `.HelpFile` and `.HelpContext` properties store information to reference a help topic in a help file. This allows the error to refer to information on possible causes of the error. ## Using On Error To generate a user-defined run-time error, first clear the `Err` object using the `.Clear` method. Then raise the error using the `.Raise` method. This method takes up to five arguments that correspond, in order, to the properties previously listed. For example: ```vbnet Err.Clear Err.Raise 1000, "This is a script-defined error", "Test Script" ``` This example displays the standard RhinoScript error dialog box showing the error information. To intercept run-time errors and process them in scripts, use the `On Error` statement. The syntax of this statement is: ```vbnet On Error Resume Next ``` After this statement executes, the next run-time errors do not cause script execution to end. Instead, the `Err` object properties are set to reflect the error information and processing continues with the next statement. For example: ```vbnet Err.Clear On Error Resume Next Err.Raise 100, "Script Error" If Err.Number Then Rhino.Print "Error=" & CInt(Err.Number) ``` In this example, the `Err.Raise` method is used to raise a run-time error. Normally, this would stop script execution. However, the earlier `On Error` statement allows script execution to continue. So the If statement executes and displays the error number. In the preceding example, the error was generated by the `Err.Raise` method. Yet, the same processing applies to any run-time error, regardless of how it is generated. For example: ```vbnet On Error Resume Next Err.Clear x = CInt("foo") If Err.Number <> 0 Then Rhino.Print Err.Number Rhino.Print Err.Description Rhino.Print Err.Source End If ``` Here, an attempt is made to convert the string `"foo"` into an integer. Because this is an invalid conversion, a run-time type mismatch error is generated. The information on this error is placed in the `Err` object. This is then processed by the `If` statement. After an `On Error` statement executes, it remains in effect for the rest of the procedure in which it executes. If the `On Error` statement executes in global scope, it remains in effect until the script terminates. Nested procedures can each have their own `On Error` statement. For example, subroutine A can execute an `On Error` statement and then execute subroutine B, which in turn, executes an `On Error` statement. If an error occurs while an `On Error` statement is active, execution continues with the following statement in the same scope as the most recently executed `On Error` statement. For example: ```vbnet On Error Resume Next Rhino.Print "Begin" Sub1 Rhino.Print "End" Sub Sub1 Rhino.Print "Enter Sub1" Err.Raise 100 Rhino.Print "Leave Sub1" End Sub ``` In this example, an `On Error` statement executes at global scope, and then the `Sub1` procedure executes. Within this procedure, an `Err.Raise` method is used to generate an error. Because the most recently executed error is at global scope, the next statement to execute is the `Rhino.Print` statement at global scope, and not the `Rhino.Print` statement following the `Err.Raise` statement. In fact, the error causes VBScript to abandon further execution of the statements in `Sub1` and continue execution at global scope. Thus, the output of this script is: ```vbs Begin Enter Sub1 End ``` Notice that the final `Rhino.Print` statement in `Sub1` never executes. When an error occurs, VBScript immediately abandons execution of any running procedures necessary to resume with the correct statement after an error. For example: ```vbnet On Error Resume Next Rhino.Print "Begin" Sub1 Rhino.Print "End" Sub Sub1 Rhino.Print "Enter Sub1" Sub2 Rhino.Print "Leave Sub1" End Sub Sub Sub2 Rhino.Print "Enter Sub2" Err.Raise 100 Rhino.Print "Leave Sub2" End Sub ``` Here, the `Err.Raise` method invocation is nested even more deeply. In this case, when the error occurs, VBScript abandons further execution of both `Sub1` and `Sub2` to continue execution at the global level. Because VBScript abandons execution of procedures only until it finds the most recently executed `On Error` statement, it is possible to capture an error within a procedure simply by placing an `On Error` statement in that procedure. For example: ```vbnet On Error Resume Next Rhino.Print "Begin" Sub1 Rhino.Print "End" Sub Sub1 Rhino.Print "Enter Sub1" On Error Resume Next Sub2 Rhino.Print "Leave Sub1" End Sub Sub Sub2 Rhino.Print "Enter Sub2" Err.Raise 100 Rhino.Print "Leave Sub2" End Sub ``` This example modifies the previous example by adding an `On Error` statement to the `Sub1` procedure. So, when the `Err.Raise` method in `Sub2` executes, execution continues with the next statement in `Sub1`. Notice that the `Leave Sub2` line never executes. The output from this example is: ```vbs Begin Enter Sub1 Enter Sub2 Leave Sub1 End ``` ## Structured Exception Handling The `On Error` statement is a simple form of a technique known as structured exception handling. The basic idea behind this technique is to centralize all the error handling code in a single location outside of the main flow of the program. This is the reason for the complex behavior of the `On Error` statement. The assumption is that a large script might contain many procedures that interact in complex ways. It is possible that an error will occur when procedures are nested very deeply. Without the `On Error` statement, each procedure must return some form of error code to the procedure from which it was called. In turn, this procedure must do the same thing, and so on for all the nested procedures. This can greatly add to the complexity of the script. With the `On Error` statement, this complexity can be avoided. When an error occurs, VBScript automatically takes care of unwinding out of the complex nest of procedures back to the statement that followed the original procedure invocation. -------------------------------------------------------------------------------- # VBScript String Literals Source: https://developer.rhino3d.com/en/guides/rhinoscript/vbscript-string-literals/ This brief guide demonstrates how to use string literals in VBScript. ## Overview In VBScript, you enclose strings with double quote characters, and you use the ampersand (`&`) operator to concatenate strings. For example: ```vbnet Dim s s = "Hello" s = "Hello" & " Rhino!" ``` What if you want to assign `"Hello Rhino!"` (including the quotes) to the variables? In VBScript, you can use two double quote characters to include a double quote character in the string. For example: ```vbnet Dim s s = "Hello Rhino!" ``` Alternatively you can use the `Chr(34)` construct: ```vbnet Dim s s = Chr(34) & "Hello Rhino" & Chr(34) ``` Or, to make your code more readable, you can write a function... ```vbnet Function Quote(ByVal s) Quote = Null If (VarType(s) = vbString) Then Quote = Chr(34) & CStr(s) & Chr(34) End If End Function '... Dim s s = Quote("Hello Rhino!") ``` -------------------------------------------------------------------------------- # Source: https://developer.rhino3d.com/en/guides/scripting/advanced-core-api/ -------------------------------------------------------------------------------- # Source: https://developer.rhino3d.com/en/guides/scripting/advanced-editor-api/ Script Editor uses the open-source [Monaco](https://microsoft.github.io/monaco-editor/) for the editing control. That is the rectangular area that displays and edits the code: ![]() -------------------------------------------------------------------------------- # Source: https://developer.rhino3d.com/en/guides/scripting/advanced-libraries/ ### Local Library Cache ### Published Library Cache -------------------------------------------------------------------------------- # Source: https://developer.rhino3d.com/en/guides/scripting/advanced-pyruntime/ -------------------------------------------------------------------------------- # Source: https://developer.rhino3d.com/en/guides/scripting/advanced-typehints/ Coming Soon. If you have any questions please reach out on [McNeel Forums](https://discourse.mcneel.com) -------------------------------------------------------------------------------- # Source: https://developer.rhino3d.com/en/guides/scripting/advanced-vscode/ -------------------------------------------------------------------------------- # Source: https://developer.rhino3d.com/en/guides/scripting/editor-debug/ - debuggable scripts - add breakpoint - toggle breakpoints - clear breakpoints - variables - watch - call stack -------------------------------------------------------------------------------- # Source: https://developer.rhino3d.com/en/guides/scripting/editor-explorer/ - explore a path - async expand - file prompt - context menus - create - create under a path - open - rename - delete -------------------------------------------------------------------------------- # Source: https://developer.rhino3d.com/en/guides/scripting/editor-help/ - help tray - show rhino 7 help - online help and help menus - examples panel - open an example -------------------------------------------------------------------------------- # Source: https://developer.rhino3d.com/en/guides/scripting/editor-problems/ - shows diagnostic results - these are NOT exec errors - can be disabled in options - errors by default - warings and info can be added - click to go to line -------------------------------------------------------------------------------- # Source: https://developer.rhino3d.com/en/guides/scripting/editor-search/ - search panel - searches all scripts - search options - clear search -------------------------------------------------------------------------------- # Source: https://developer.rhino3d.com/en/guides/scripting/editor-templates/ - browse templates - async explore - create new template - save - edit the template - rename - delete -------------------------------------------------------------------------------- # Source: https://developer.rhino3d.com/en/guides/scripting/template/ -------------------------------------------------------------------------------- # Source: https://developer.rhino3d.com/en/guides/scripting/what-is-scripteditor/ - its a place you write scripts to customize - what rhino had: c#, ironpython, rhinoscript, vb, ghgl - new in rhino 8: - unified editor - python 3 - modern c# - better ironpython - no more vb - ghgl in progress - libraries - easy sharing scripts -------------------------------------------------------------------------------- # Administration Source: https://developer.rhino3d.com/en/admin/ The administrator's view of the developer.rhino3d.com website. [![Build Status](https://travis-ci.org/mcneel/developer-rhino3d-com.svg?branch=master)](https://travis-ci.org/mcneel/developer-rhino3d-com) This site is the canonical source for developer documentation relating to Rhino, Grasshopper, and other companion products. The goal of this site is to provide a clear, easy-to-navigate, reference, with consistent formatting and nomenclature. For contributors or administrators, the following guides are necessary reading: - [Contributing (This Website)](/guides/general/contributing/#this-website) - [Getting Started with Developer Docs](https://github.com/mcneel/developer-rhino3d-com/blob/master/README.md) - [How This Site Works](/guides/general/how-this-site-works) - [Developer Docs Style Guide](/guides/general/developer-docs-style-guide) -------------------------------------------------------------------------------- # Asynchronous Execution Source: https://developer.rhino3d.com/en/guides/scripting/advanced-async/ Provides information on running scripts in asynchronous mode to avoid locking Rhino UI with long running tasks ## What is Asynchronous Normally, most operations in an application with a graphical user interface (GUI), run on the *UI Thread*. That is the thread that starts the UI and listens to the events like clicking buttons and moving the mouse. When you click on a button, the code behind that button runs on the UI thread. **Asynchronous (Async for short) operations run on Non-UI threads and do not freeze the UI.** If the task is time-consuming, the UI thread (now executing the task after button click) can not respond to any other events. Therefore UI is *"Frozen"* (not the Disney® movie) and unresponsive. Normally this is ok since you would not want the user to change the document while the task is running. It is a good idea to use Non-UI threads for time-consuming tasks and run them *Asynchronously*. ## Async in Script Editor In Rhino Script Editor, if a script is performing a time-consuming task, clicking on the *Run* button would cause Rhino UI to freeze for the duration of the script. As we mentioned above this is ok. However, if your task does not deal with the Rhino document (could be changed by the Rhino user while your script is running), it could be made async. This would make Rhino UI responsive while your script is running. It is also a good habit to show progress while the task is running in the background: - In C#, add `// async:true` to the top of your script. - In Python, add `# async:true` to the top of your script. When script is marked as `async: true` the Script Editor runs the full script on a Non-UI thread. This is a feature of Rhino Script Editor and not the scripting language. You can remove this line or set it to `false` to make the script synchronous again. ## Asynchronous C# The example C# script below completely freezes Rhino UI for about 2 seconds. That the amount of time we are specifying in `Thread.Sleep` to simulate work. This could be a long running computation or waiting to receive some data from web: ```csharp // #! csharp using System; using System.Threading; using Rhino; RhinoApp.WriteLine("Start Task"); Thread.Sleep(2000); // simulate work RhinoApp.WriteLine("End Task"); ``` By adding the line `// async:true`, we can force this complete script to run on a Non-UI thread, keeping Rhino UI active so we can continue working while the script is running: ```csharp // #! csharp // async:true using System; using System.Threading; using Rhino; RhinoApp.WriteLine("Start Task"); Thread.Sleep(2000); // simulate work RhinoApp.WriteLine("End Task"); ``` Notice that this is the only change we made to the script. Also note that the *Run* button in Script Editor dashboard now shows a red arrow to represent the async execution of this script: ![](https://developer.rhino3d.com/en/guides/scripting/advanced-async/editor-csharp-async.png) ## Asynchronous Python The example Python script below completely freezes Rhino UI for about 2 seconds. That the amount of time we are specifying in `time.sleep` to simulate work. This could be a long running computation or waiting to receive some data from web: ```python #! python3 import threading import time class Job(threading.Thread): def __init__(self, id, name, wait): super().__init__() self.id = id self.name = name self.wait = wait def run(self): print("Starting " + self.name) time.sleep(self.wait) # wait to simulate work print(f"Done {self.name}: {time.ctime(time.time())}") job1 = Job(1, "Job-1", 2) job1.start() job1.join() print("Complete") ``` By adding the line `# async:true`, we can force this complete script to run on a Non-UI thread, keeping Rhino UI active so we can continue working while the script is running (this is a feature of Rhino Script Editor and not Python language): ```python #! python3 # async: true import threading import time class Job(threading.Thread): def __init__(self, id, name, wait): super().__init__() self.id = id self.name = name self.wait = wait def run(self): print("Starting " + self.name) time.sleep(self.wait) # wait to simulate work print(f"Done {self.name}: {time.ctime(time.time())}") job1 = Job(1, "Job-1", 2) job1.start() job1.join() print("Complete") ``` Notice that this is the only change we made to the script. Also note that the *Run* button in Script Editor dashboard now shows a red arrow to represent the async execution of this script: ![](https://developer.rhino3d.com/en/guides/scripting/advanced-async/editor-python-async.png) ## Show Progress It is a good practice to show feedback on the progress of background tasks. Rhino UI has a progress indicator on the status bar. This is an example of how you can use this progress bar in your scripts. `Thread.Sleep` is used below to simulate work: ```csharp // #! csharp using System; using System.Threading; using Rhino; using Rhino.UI; using Eto.Forms; // setup the progress indicator with expected range, and a message StatusBar.ShowProgressMeter(0, 5, "Progress", embedLabel: true, showPercentComplete: false); RhinoApp.WriteLine("Start Task"); for (int i = 0; i < 5; i++) { Thread.Sleep(1000); // simulate work // update progress StatusBar.UpdateProgressMeter("Progress", i, true); // since we are on the main thread here, // call this method to force Rhino UI to update Application.Instance.RunIteration(); } // do not forget to hide the progress when done StatusBar.HideProgressMeter(); RhinoApp.WriteLine("End Task"); ``` ![](https://developer.rhino3d.com/en/guides/scripting/advanced-async/editor-csharp-progress-sync.png) ### Python Progress In Python you can use `rhinoscriptsyntax` module to access the progress indicator easier: ```python import rhinoscriptsyntax as rs from Rhino import RhinoApp MAX = 1000 rs.StatusBarProgressMeterShow("Progress", 0, MAX) for i in range(0, MAX): rs.StatusBarProgressMeterUpdate(i) rs.StatusBarProgressMeterHide() ``` ## Async in Grasshopper `async: true` pattern is NOT SUPPORTED in Grasshopper, since it needs to wait for the script to fully execute and set the output data before executing the rest of component graph. We can however have background threads running computations, and continuously trigger a Grasshopper solve to update the results. This is an example of a python script component that runs computation on background thread. We use the *Trigger* component is Grasshopper to recompute this component on intervals and therefore update the geometry previews in Rhino: - `RunScript` sets up the worked thread on the first run. It does not do anything on later runs except for outputing `"Training in Progress"` and the current state of compute mesh - `main_solve` is the solver function that is being executed by the worker thread. It updates the class variable `MyComponent.CURRENT_MESH` while running - `DrawViewportMeshes` is called by Grasshopper after each trigger and displays the current state of computed mesh in `MyComponent.CURRENT_MESH` ```python import System import System.Drawing as SD import Rhino import Rhino.Geometry as G import Grasshopper import Grasshopper.Kernel as GHK import threading import time def main_solve(): for r in range(10, 20): # wait represents compute work Rhino.RhinoApp.WriteLine("computing mesh") time.sleep(1) sphere = G.Sphere(G.Point3d.Origin, r) MyComponent.CURRENT_MESH = G.Mesh.CreateFromSphere(sphere, 10, 10) Rhino.RhinoApp.WriteLine("computed mesh") Rhino.RhinoApp.WriteLine("computed completed") class MyComponent(Grasshopper.Kernel.GH_ScriptInstance): SOVLE_STARTED = False CURRENT_MESH = None def RunScript(self): if MyComponent.SOVLE_STARTED: return ("Training in Progress", MyComponent.CURRENT_MESH) MyComponent.SOVLE_STARTED = True threading.Thread(target=main_solve).start() return ("Training in Progress", None) @property def ClippingBox(self): return G.BoundingBox(-30, -30, -30, 30, 30, 30) def DrawViewportMeshes(self, args: GHK.IGH_PreviewArgs): if d := getattr(args, "Display", None): if MyComponent.CURRENT_MESH: d.DrawMeshWires(MyComponent.CURRENT_MESH, SD.Color.Blue, 2) ``` Notice that Rhino UI stays active during this background computation: ## Advanced Async Sometime it is necessary to run operations on the UI thread before or after completing a time-consuming operation. Remember that the `async:true` mechanism mentioned above is for convenience and runs the complete script on a Non-UI thread. Based on the script language, you can use the threading or async features on the language to perform more complicated sync/async operations. This is an example C# script that runs on UI thread on parts **A** and **C** of the script (blocking), and has a time-consuming operation on part **B**. Rhino UI is frozen during the blocking portions, but is fully available otherwise. Notice that: - Script specifies `// async: true`. **This means that the complete script is running on Non-UI thread.** - To make sure **parts A and C are running on UI thread** and can make changes to Rhino, we use `Application.Instance.Invoke`. This method is provided by Eto which is the UI framework Rhino >=8 uses, and ensures the given action runs on the UI thread. - On part B, script is calling `.GetAwaiter().GetResult()` on the `Task` object created by`Task.Run` call. This is to ensure **execution waits for the task to complete** and we have the result before proceeding to part C. Also notice that calling `Application.Instance.RunIteration` is not necessary here and causes a crash if called. ```csharp // #! csharp // async: true using System; using System.Threading; using System.Threading.Tasks; using Rhino; using Rhino.UI; using Eto.Forms; // Part A: runs on UI thread (blocking) Application.Instance.Invoke(() => { // CAN MAKE CHANGES TO RHINO or DOCUMENT HERE StatusBar.ShowProgressMeter(0, 5, "Progress", true, false); }); // Part B: runs on Non-UI thread int result = Task.Run(() => { for (int i = 0; i < 5; i++) { Thread.Sleep(1 * 1000); StatusBar.UpdateProgressMeter("Progress", i, true); // DO NOT CALL THIS SINCE WE ARE NOT ON UI THREAD // Application.Instance.RunIteration(); } return 42; }).GetAwaiter().GetResult(); // Part C: runs on UI thread (blocking) Application.Instance.Invoke(() => { // CAN MAKE CHANGES TO RHINO or DOCUMENT HERE RhinoApp.WriteLine($"Result: {result}"); StatusBar.HideProgressMeter(); }); ``` You can also debug this script by placing breakpoints inside the scope of each part. Notice how the first and last breakpoints are paused on `Thread 1` (main and UI thread in Rhino), but the breakpoint on line 19 is paused on `Thread 15` which happens to be the thread used to run the task by dotnet runtime: ![](https://developer.rhino3d.com/en/guides/scripting/advanced-async/editor-csharp-mixed-debug.png) Here is a similar example in Python. Note that we are using `rhinoscriptsyntax` to handle the progress indicator. `part_a` and `part_c` functions are executed on the main UI thread, and the middle part is executed on the new thread created in the script: ```python #! python3 # async: true import threading import time import rhinoscriptsyntax as rs from Rhino import RhinoApp from Eto.Forms import Application class Job(threading.Thread): def __init__(self, id, name): super().__init__() self.id = id self.name = name self.result = 0 def run(self): thread_id = threading.current_thread().ident print(f"Starting {self.name} on Thread: {thread_id}") for i in range(5): time.sleep(1) # wait to simulate work rs.StatusBarProgressMeterUpdate(i) self.result = 42 print(f"Done {self.name}: {time.ctime(time.time())}") def part_a(): # CAN MAKE CHANGES TO RHINO or DOCUMENT HERE thread_id = threading.current_thread().ident print(f"Thread: {thread_id}") rs.StatusBarProgressMeterShow("Progress", 0, 5) def part_c(result): # CAN MAKE CHANGES TO RHINO or DOCUMENT HERE thread_id = threading.current_thread().ident print(f"Thread: {thread_id}") print(f"Result: {result}") rs.StatusBarProgressMeterHide() RhinoApp.ClearCommandHistoryWindow() Application.Instance.Invoke(part_a) job1 = Job(1, "Job-1") job1.start() job1.join() result = job1.result Application.Instance.Invoke(lambda: part_c(result)) print("Complete") ``` Notice that the thread id matches for `part_a` and `part_c`, but the middle section is executed on a thread with a different id. Also note that thread identifiers are different from dotnet thread ids when using C#: ![](https://developer.rhino3d.com/en/guides/scripting/advanced-async/editor-python-mixed-threadids.png) ### C# Async/Await In C# (Rhino >= 8.12) you can use [async/await](https://learn.microsoft.com/en-us/dotnet/csharp/asynchronous-programming/) for asynchronous programming. Here is an example of creating an async function in the script editor: ```csharp // #! csharp // async: true using System; using System.Threading; using System.Threading.Tasks; async Task Compute() { await Task.Delay(TimeSpan.FromMilliseconds(2000)); return 42; } int result = await Compute(); Console.WriteLine($"Result: {result}"); ``` Notice that if we remove the `// async: true` line or set that to `false` the editor shows an error on the `await` call in global scope: ![](https://developer.rhino3d.com/en/guides/scripting/advanced-async/editor-csharp-await-error.png) When running C# scripts, the editor recomposes the script into something that looks like the example below. This is done so multiple instances of the same script can be created, holding onto their own internal states, and executed using different contexts. Notice that the main `__RunScript__` method is NOT marked as *async*: ```csharp sealed class __RhinoCodeScript__ { public void __RunScript__(Rhino.Runtime.Code.Execution.RunContext __context__) { // YOUR SCRIPT IS EMBEDDED HERE }} ``` When using `await` in global scope, we need to mark the script as `// async: true` to ensure `__RunScript__` is marked as `async` and returns a `Task` instance so the editor can await the execution: ```csharp sealed class __RhinoCodeScript__ { public async Task __RunScript__(Rhino.Runtime.Code.Execution.RunContext __context__) { async Task Compute() { await Task.Delay(TimeSpan.FromMilliseconds(2000)); return 42; } int result = await Compute(); Console.WriteLine($"Result: {result}"); }} ``` -------------------------------------------------------------------------------- # Creating Rhino/Grasshopper Script Plugins Source: https://developer.rhino3d.com/en/guides/scripting/projects-create/ Provides information on creating plugin projects in Script Editor Rhino Script Editor is designed to utilize the widespread plugin infrastructure in both Rhino and Grasshopper, and generate plugins from your scripts. Script editor can: - Convert scripts into Rhino commands and publish as Rhino plugin (`.rhp` file) - Convert scripts into Grasshopper components and publish as Grasshopper plugin (`.gha` file) - Create a Yak package and publish on Rhino package server It can also: - Generate toolbar layout files for published Rhino commands (`.rui` file) - Share code libraries and data files with published commands or components - Generate dotnet project solution for published plugins for furthur customization (`.sln` and `.csproj` files) ![](https://developer.rhino3d.com/en/guides/scripting/projects-create/create-project.svg) ## Create a Project To create a project: - Run `ScriptEditor` command - Choose **Create Project** from main *File* menu - Navigate to where you would like to save the project. Project file will be stored with `.rhproj` extension - Editor displays **Edit Project** dialog. This dialog highlights the most important pieces of information about the new project: - **Id**: Unique UUID of this project. It should not be changed. See [Project Id](#project-id) - **Name**: Plugins will be published with this name. See [Project Name](#project-name) - **Version**: Plugins will be published with this version. See [Project Version](#project-version) - **Author**: This is required and if there are no authors available, this field will show a *No Author* error. See [Project Authors](#project-authors) ![](https://developer.rhino3d.com/en/guides/scripting/projects-create/project-edit-info.png) - Choose **Save Changes** - Project file is now saved and editor opens the **Projects** panel on the left browser tray ![](https://developer.rhino3d.com/en/guides/scripting/projects-create/project-open-in-tray.png) - Now that the project saved and open, we can add command and components. We will get a chance to edit project information before publishing the project as plugins. ## Add Commands To add a command, click on the **+** icon on the *Project Tray* toolbar and select **Add Commands/** item: ![](https://developer.rhino3d.com/en/guides/scripting/projects-create/project-commands-add-button.png) You can also right-click on the **Commands/** to add new commands: ![](https://developer.rhino3d.com/en/guides/scripting/projects-create/project-commands-add-rclick.png) You can select a single or multiple scripts and add them to the project. Once scripts are added, they are opened in the editor and a black circle appears in front of project name in *Project Tray* to show that project is modified and must be saved. Click the Save Project button in *Project Tray* toolbar to save the project: ![](https://developer.rhino3d.com/en/guides/scripting/projects-create/project-commands-added.png) Rhino commands can be created from Grasshopper scripts that contain contextual inputs and outputs. In the example above, `Script_C` is a Grasshopper 1 definition (`Script_C.gh`) that contains two contextual integer inputs and a contextual print output component. ![](https://developer.rhino3d.com/en/guides/scripting/projects-create/project-commands-gh1.png) See [Run Published Commands](/guides/scripting/projects-publish#run-published-commands) for information on how the inputs are collected on Rhino command line. During build, each script under **Commands/** is converted into a Rhino command. The script name is the default name of the command. See [Project Commands](#commands) for information on editing command, assigning icons, and changing command types. To remove a command, select the command in *Project Tray* and click the trash button on the toolbar: ![](https://developer.rhino3d.com/en/guides/scripting/projects-create/project-commands-remove.png) ### Grasshopper Previews Grasshopper calls the components and parameters on the canvas to draw previews and follows the drawing settings and mode that is stored in the document. You can change the mode and settings for each document from the Grasshopper UI. Component previews are also configurable, and there is a *Custom Preview* component available as well. This means that you can customize how your Grasshopper definition previews the geometry it is working with. When running a published command that embeds a Grasshopper definition, previews are drawn in Rhino viewport, in the same way as Grasshopper UI would draw the previews, while command is asking for inputs. This is an example of a Grasshopper definition that creates a sphere at given point. It draws its own preview of the sphere using *Custom Preview* component: ![](https://developer.rhino3d.com/en/guides/scripting/projects-create/project-commands-ghpreview_def.png) When publishing this Grasshopper definition as a Rhino command and running that command, the same preview is drawn while Rhino is asking for the input point: ![](https://developer.rhino3d.com/en/guides/scripting/projects-create/project-commands-ghpreview_run.gif) ### Special Variables There are a few builtin variables available when published scripts are executed as Rhino commands: - `__rhino_command__` ([Rhino.Commands.Command](https://developer.rhino3d.com/api/rhinocommon/rhino.commands.command)): Rhino command instance. This is the automatically generated command in your plugin, that contains and runs its embedded script. - `__rhino_doc__` ([Rhino.RhinoDoc](https://developer.rhino3d.com/api/rhinocommon/rhino.rhinodoc)): Active document the command is running on - `__rhino_runmode__` ([Rhino.Commands.RunMode](https://developer.rhino3d.com/api/rhinocommon/rhino.commands.runmode)): Command Run Mode - `__is_interactive__` (boolean): Whether command is executed interactively (when `RunMode == RunMode.Interactive`) ## Add Components To add components, click on the **+** icon on the *Project Tray* toolbar and select **Add Components/** item: ![](https://developer.rhino3d.com/en/guides/scripting/projects-create/project-comps-add-button.png) You can also right-click on the **Components/** to add new components: ![](https://developer.rhino3d.com/en/guides/scripting/projects-create/project-comps-add-rclick.png) To remove a component, select the source Grasshopper definition in *Project Tray* and click the trash button on the toolbar. Note that you can only remove complete definitions from **Components/** and not the individual components: ![](https://developer.rhino3d.com/en/guides/scripting/projects-create/project-comps-remove.png) ## Components Types: Script When adding components to a project, an open dialog appears asking for Grasshopper definitions (`.gh` or `.ghx`). Depending on the contents of the definition, one of these two types of components are created in the project: **Script:** If Grasshopper definition *does not contain* any contextual inputs or outputs, a component is created for each *Script* that exist in the definition. The new component matches nickname, inputs, and outputs of the *Script* and runs the same code. **Example:** In this example, the definition contains 3 *Script* components, nicknamed *First*, *Second*, and *Third*. Each script components becomes a component in published Grasshopper plugin matching the nickname, inputs, and outputs. See [Project Components](#components) on how to edit components, add icons, and set their exposure. ![](https://developer.rhino3d.com/en/guides/scripting/projects-create/project-comps-added-scripts.png) This is how the components look like when published (default icon). Note that although they are generated from scripts of different languages, the published components are language agnostic and the three components look the same: ![](https://developer.rhino3d.com/en/guides/scripting/projects-create/project-comps-published-script.png) ### Required Inputs Input parameters of published components follow the *Required*, *Access*, and *Type Hint* settings of their corresponding input on the original script component: ![](https://developer.rhino3d.com/en/guides/scripting/projects-create/project-commands-requiredinputs.png) ### Output Previews Outputs parameters of published components follow the *Preview* settings of their corresponding output on the original script component: ![](https://developer.rhino3d.com/en/guides/scripting/projects-create/project-comps-outputpreview.png) ## Components Types: Contextual **Contextual:** If Grasshopper definition *does contain* contextual inputs or outputs, a single component is created for the complete definition. The new component matches the contextual inputs and outputs of the definition and runs the full definition on each iteration. **Example:** In this example, the definition contains contextual inputs and outputs. The complete definition becomes a component in published Grasshopper plugin, and its name matches the definition name by default. Component inputs and outputs will match contextual inputs and outputs of the definition. Note that any other *Script* component in this definition is not converted. See [Project Components](#components) on how to edit components, add icons, and set their exposure. ![](https://developer.rhino3d.com/en/guides/scripting/projects-create/project-comps-added-ctx.png) This is how the component looks like when published (default icon): ![](https://developer.rhino3d.com/en/guides/scripting/projects-create/project-comps-published-ctx.png) ### Inputs and Outputs Contextual components embed and run full Grasshopper definitions as a single component. Inputs and outputs of the component are based on the Contextual inputs and outputs placed on the source Grasshopper definition. Take this file as an example: ![](https://developer.rhino3d.com/en/guides/scripting/projects-create/project-commands-gh1.png) This Grasshopper definition is converted to a component like below. Note that a specific *GH Parameter* corresponding with the type of *Get Integer* contextual component is used as inputs to the component: ![](https://developer.rhino3d.com/en/guides/scripting/projects-create/project-comps-ctx-simple.png) *Prompt* value is used as the name of the input parameter: ![](https://developer.rhino3d.com/en/guides/scripting/projects-create/project-comps-ctx-simple-prompt.png) There are a few contextual components that can filter their input data. *Get Geometry* is an example of such contextual component. The more specific the filter, the more specific the final parameter will be. If filter is specific to one type, a parameter of that specific type is created on published component: ![](https://developer.rhino3d.com/en/guides/scripting/projects-create/project-comps-ctx-specific-brep.png) ![](https://developer.rhino3d.com/en/guides/scripting/projects-create/project-comps-ctx-specific-brep-param.png) If filter not specific, a flexible geometry parameter is created on published component: ![](https://developer.rhino3d.com/en/guides/scripting/projects-create/project-comps-ctx-specific-geom.png) ![](https://developer.rhino3d.com/en/guides/scripting/projects-create/project-comps-ctx-specific-geom-param.png) Contextual output components are converted to: - Generic Goo parameter for *Context Bake* ![](https://developer.rhino3d.com/en/guides/scripting/projects-create/project-comps-ctx-outgoo.png) - Text parameter for *Context Print* ![](https://developer.rhino3d.com/en/guides/scripting/projects-create/project-comps-ctx-outtext.png) ### Grasshopper Previews Grasshopper calls the components and parameters on the canvas to draw previews and follows the drawing settings and mode that is stored in the document. You can change the mode and settings for each document from the Grasshopper UI. Component previews are also configurable, and there is a *Custom Preview* component available as well. This means that you can customize how your Grasshopper definition previews the geometry it is working with. Normally a published component that embeds a script (e.g. Python) draws previews on its outputs as well as any drawings performed by [Preview Overrides](/guides/scripting/scripting-gh-python/#preview-overrides) of its script. In case of published component with embedded Grasshopper definitions, the component itself does not draw any previews and asks the embedded definition to draw its own previews (Rhino >= 8.13). This means that whatever custom preview you have specified in the embedded definition, the same preview is drawn on the canvas. **Note:** To preserve consistency with other Grasshopper components, Contextual Components will not draw previews of embedded definition if the Preview option is off on the component or it is disabled. Preview mode is also synchronized to the embedded definition, meaning that if you set preview mode to *Wireframe Preview* in Grasshopper UI, the embedded definition will also draws in wireframe mode. This is an example of a Grasshopper definition that creates a sphere at given point. It draws its own preview of the sphere using *Custom Preview* component: ![](https://developer.rhino3d.com/en/guides/scripting/projects-create/project-comps-ghpreview_def.png) When publishing this Grasshopper definition as a Contextual Component, the same preview is drawn with the input provided to the component: ![](https://developer.rhino3d.com/en/guides/scripting/projects-create/project-comps-ghpreview_run.gif) ## Project Info To edit project information, either use the **Publish Project** dialog, or run the **Edit Project Info** command from editor command prompt. Both dialogs show identical project information fields: ![](https://developer.rhino3d.com/en/guides/scripting/projects-create/project-edit-fields.png) ### Project Id Project UUID is assigned when the project is created and remains read-only afterwards. This id, uniquely identifies your plugin among all other past and future Rhino and Grasshopper plugins. This id is embedded in the final plugin assemblies as: ```csharp [assembly: Guid("e73d16e6-a2d4-4917-93e7-aebeac2f38f5")] ``` It is also assigned to the Grasshopper `AssemblyInfo`: ```csharp public sealed class AssemblyInfo : GH_AssemblyInfo { public override Guid Id { get; } = new Guid("e73d16e6-a2d4-4917-93e7-aebeac2f38f5"); } ``` ### Project Name and Icon *Name* is the official name of your project. This name will be used when generating plugin assemblies and other associated files (`` is the project name in this example): ```text ├── .rhp ├── .rui ├── .Components.gha ├── -0.1.16222.8992-rh8-any.yak └── src    ├──    │   ├── AssemblyInfo.cs    │   ├── ProjectCommand_*.cs    │   ├── ProjectInterop.cs    │   ├── ProjectPlugin.cs    │   └── .csproj    ├── .Components    │   ├── AssemblyInfo.cs    │   ├── ProjectComponent_*.cs    │   ├── ProjectComponent_Base.cs    │   ├── ProjectInterop.cs    │   ├── ProjectPlugin_Grasshopper.cs    │   └── .Components.csproj    └── .sln ``` You can add/remove SVG icons (both light and dark) for the plugin. This icon is used in plugin manager and toolbars for both Rhino and Grasshopper: ![](https://developer.rhino3d.com/en/guides/scripting/projects-create/project-edit-name-icon.png) Project name and icon are used for the toolbar: ![](https://developer.rhino3d.com/en/guides/scripting/projects-create/project-commands-installed.png) Project name is also used as the main category name in Grasshopper UI. Project icon is shown if Grasshopper is set to show the icons in category tabs (or first letter of project name if no icon is set): ![](https://developer.rhino3d.com/en/guides/scripting/projects-create/project-comps-plugin.png) ### Project Version This is the project version and follows [Semantic Versioning](https://semver.org) style. When creating a new project it is set to v0.1 by default. ![](https://developer.rhino3d.com/en/guides/scripting/projects-create/project-edit-version.png) Note that the final version used in building assemblies is `0.1.8991+28927`. Script editor automatically generates patch and build numbers to completion the version and make it specific: - *Patch* number is number of days since start of century (01/01/2000) - *Build* is the number of seconds since midnight divided by 2. You can assign your own custom patch number by entering a value in the *Patch* field. ![](https://developer.rhino3d.com/en/guides/scripting/projects-create/project-edit-version-patch.png) You can also mark the version as *PreRelease* by checking the box. The patch suffix `-beta` will be added to the full version. Note that due to lack of support for custom patch extensions in `AssemblyVersion` attributes, `-beta` extension is excluded. However the generated *Yak* package will include the tag and is marked as pre-release. ![](https://developer.rhino3d.com/en/guides/scripting/projects-create/project-edit-version-beta.png) ![](https://developer.rhino3d.com/en/guides/scripting/projects-create/project-edit-version-patch-beta.png) Here is an example of how the full version number is included in the plugins: ```csharp [assembly: AssemblyVersion("0.1.234.8991")] [assembly: AssemblyFileVersion("0.1.234.8991")] [assembly: AssemblyInformationalVersion("0.1.234.8991")] ``` ### Project Authors Assigning an *Author* is required for a build. Project Edit or Publish dialogs show a dropdown, listing all the available authors. Selected author information will be saved within the project file. ![](https://developer.rhino3d.com/en/guides/scripting/projects-create/project-edit-author-dropdown.png) Choose *Edit* button on the right side of author dropdown to edit the list of available authors: ![](https://developer.rhino3d.com/en/guides/scripting/projects-create/project-edit-author.png) Author information is also included in the published plugins using the [PlugInDescription](https://developer.rhino3d.com/api/rhinocommon/rhino.plugins.plugindescriptionattribute) attribute in RhinoCommon. Note that the full name of author is not listed and is usually included as part of *Copyright* message: ```csharp [assembly: PlugInDescription(DescriptionType.Email, "ehsan@mcneel.com")] [assembly: PlugInDescription(DescriptionType.Phone, "+1 (206)-888 8888")] [assembly: PlugInDescription(DescriptionType.Organization, "McNeel")] [assembly: PlugInDescription(DescriptionType.Address, "Seattle, WA")] [assembly: PlugInDescription(DescriptionType.Country, "USA")] [assembly: PlugInDescription(DescriptionType.WebSite, "https://www.rhino3d.com/")] ``` ### Project Copyright, License, and URL Edit the copyright field and add your custom copyright message, or click on the **Copyright** button to add a default message based on the selected Author: ![](https://developer.rhino3d.com/en/guides/scripting/projects-create/project-edit-copyright.png) The copyright message is embedded in the final plugin assemblies as: ```csharp [assembly: AssemblyCopyright("Copyright © 2024 Ehsan Iran-Nejad")] ``` ## Commands ### Name and Icon Once a command is added to the project, you can select and click the *Edit* icon on the *Project Tray* toolbar, to edit the command properties. The *Edit Command* dialog provides fields to modify the command *Name*, and to add/remove SVG icons (both light and dark) for the command. This icon will be used in the generated toolbar layout file (*.rui): ![](https://developer.rhino3d.com/en/guides/scripting/projects-create/project-commands-edit-name-icon.png) Command icons are used for the project toolbar: ![](https://developer.rhino3d.com/en/guides/scripting/projects-create/project-commands-toolbar-buttons.png) ### Type (Style) Choose the type of Rhino command from the *Type* dropdown menu. Hidden and Transparent correspond with the [Rhino.Commands.Style.Hidden](https://developer.rhino3d.com/api/rhinocommon/rhino.commands.style) and [Rhino.Commands.Style.Transparent](https://developer.rhino3d.com/api/rhinocommon/rhino.commands.style) enum values in RhinoCommon: ![](https://developer.rhino3d.com/en/guides/scripting/projects-create/project-commands-edit-type.png) ### Excluding Commands Sometimes it is desired to keep a command in the project but exclude that from published plugins. You can check the *Exclude* box to exclude the command: ![](https://developer.rhino3d.com/en/guides/scripting/projects-create/project-commands-edit-exclude.png) Note that *Project Tray* shows a dimmed icon for excluded commands: ![](https://developer.rhino3d.com/en/guides/scripting/projects-create/project-commands-excluded.png) ## Components ### Name and Icon Once a component is added to the project, you can select and click the *Edit* icon on the *Project Tray* toolbar, to edit the component properties. The *Edit Component* dialog provides fields to modify the component *Name*, *NickName*, *Description*, and to add/remove SVG icons (both light and dark) for the component: ![](https://developer.rhino3d.com/en/guides/scripting/projects-create/project-comps-edit-name-icon.png) ### Panel Name (Sub-Category) *Sub-Category* is then name of Grasshopper panel that contains the published component. You can edit and customize the panel name or choose from a list of previously set panel names in the project: ![](https://developer.rhino3d.com/en/guides/scripting/projects-create/project-comps-subcategory.png) ![](https://developer.rhino3d.com/en/guides/scripting/projects-create/project-comps-edit-subcategory.png) ### Exposure Exposure dropdown sets the location of component on the subcategory panel. It is simplified from [Grasshopper.Kernel.GH_Exposure](https://developer.rhino3d.com/api/grasshopper/html/T_Grasshopper_Kernel_GH_Exposure.htm) and includes 4 main areas (`primary`, `secondary`, `tertiary`, and `quarternary`) of the subcategory panel. See [Component Versioning](#component-versioning) for information on how to update your component versions. ![](https://developer.rhino3d.com/en/guides/scripting/projects-create/project-comps-edit-exposure.png) ### Excluding Components Sometimes it is desired to keep a component in the project but exclude that from published plugins. You can check the *Exclude* box to exclude the component: ![](https://developer.rhino3d.com/en/guides/scripting/projects-create/project-comps-edit-exclude.png) Note that *Project Tray* shows a dimmed icon for excluded components: ![](https://developer.rhino3d.com/en/guides/scripting/projects-create/project-comps-excluded.png) ## Component Versioning Script editor creates a new component in the Grasshopper plugin for each script component extracted from Grasshopper definition. The **Instance Id** of the source script component, will be used as **Component Id** of the published component. This means that if you duplicate the same script component on your Grasshopper canvas, you will end up with multiple unique components in the published plugin: ![](https://developer.rhino3d.com/en/guides/scripting/projects-create/project-comps-identical.png) **This is a feature and not a bug.** ### Legacy and New If you need to make a "breaking" change to a published component (e.g. changing inputs or outputs), it is a good practice to mark the previously published component as Legacy and create a new component. Simply duplicate the previous script component on the source Grasshopper definition, and make your changes to this new instance. Saved the definition and the *Project Tray* will update to show then new component. Select and edit the first component and choose *Legacy* exposure (Corresponds with [Grasshopper.Kernel.GH_Exposure.hidden](https://developer.rhino3d.com/api/grasshopper/html/T_Grasshopper_Kernel_GH_Exposure.htm)): ![](https://developer.rhino3d.com/en/guides/scripting/projects-create/project-comps-edit-legacy.png) Note that *Project Tray* shows a dimmed icon with `(Legacy)` postfix for legacy components: ![](https://developer.rhino3d.com/en/guides/scripting/projects-create/project-comps-legacy.png) Legacy components are still included in the published plugin but are hidden and marked as *Old*. You would need to prefix search with `#` to see these hidden components. This allows your plugin to be backwards compatible and all previous Grasshopper definitions using the old component still work. The source Grasshopper definition (`identical.gh` in the screenshot above) also ends up including the source for each of the published component versions: ![](https://developer.rhino3d.com/en/guides/scripting/projects-create/project-comps-legacy-published.png) ## Shared Libraries To share code between command and components, create a code library and add that to your project. These libraries will be embedded in the published plugins and deployed on the target machine. Here is an example of a project with a *Python 3* and *C#* library added: ![](https://developer.rhino3d.com/en/guides/scripting/projects-create/project-libs-example.png) ### What is a Library Code libraries are a group of source files organized in a folder. They are language-specific and follow the library or module creation patterns of that language. For example, to create a *Python 3* library, you would need to organize your python sources like below and include `__init__.py` files. Otherwise *Python* will not be able to import these libraries correctly: ```text testmodule/ ├── __init__.py ├── riazi.py └── utils ├── __init__.py └── utils.py ``` For C#, a series of `.cs` files organized in a folder will do the job: ```text TestAssembly/ ├── Math.cs └── Types.cs ``` Language libraries are cached when the project is being edited or is published. See [Language Libraries](/guides/scripting/advanced-libraries) for more information. ### Add Language Libraries You can include code libraries in your project under **Libraries/** path. Choose **Add * Library** from **+** menu in *Project Tray* toolbar or right-click on **Libraries/** and add a shared file: ![](https://developer.rhino3d.com/en/guides/scripting/projects-create/project-libs-add-menu.png) ![](https://developer.rhino3d.com/en/guides/scripting/projects-create/project-libs-add.png) ### Reference Libraries Once libraries are added, you can reference them in your scripts: ![](https://developer.rhino3d.com/en/guides/scripting/projects-create/project-libs-use-csharp.png) ## Shared Resources ### Add Shared Resources You can include data files of any type and extension in your project under **Shared/** path. Choose **Add Shared/** from **+** menu in *Project Tray* toolbar or right-click on **Shared/** and add a shared file: ![](https://developer.rhino3d.com/en/guides/scripting/projects-create/project-shared-add-menu.png) ![](https://developer.rhino3d.com/en/guides/scripting/projects-create/project-shared-add.png) These files will be included in the final *Yak* package under `shared/` path and are deployed on target machine. Here is an example of including `data.json` in the project. The file is included in the *Yak* package as `shared/data.json` and deployed when *Yak* package is installed: ```text myproject-0.1.234.8992-rh8-any.yak ├── MyProject.rhp ├── MyProject.rui ├── MyProject.Components.gha ├── manifest.yml └── shared/ └── data.json ``` ![](https://developer.rhino3d.com/en/guides/scripting/projects-create/project-shared-deployed.png) ### Find Shared Resource To query and get access to the deployed data files, use [Rhino.PlugIns.PlugIn.PathFromId](https://developer.rhino3d.com/api/rhinocommon/rhino.plugins.plugin/pathfromname) to get the installed path of you plugin. Use this path to build a path to the deployed data files. Note that data files and deployed under `shared/` path alongside the plugin assembly file: ![](https://developer.rhino3d.com/en/guides/scripting/projects-create/project-shared-query.png) You can also use [Rhino.PlugIns.PlugIn.PathFromName](https://developer.rhino3d.com/api/rhinocommon/rhino.plugins.plugin/pathfromname) and pass your plugin name however the plugin id is better since it should never change and is more unique than the name. ## Projects in Explorer The file explorer on the left side of Script Editor shows project source files. You can open a project by double-clicking the entry in the explorer (Rhino >=8.12): ![](https://developer.rhino3d.com/en/guides/scripting/projects-create/project-in-explorer.png) -------------------------------------------------------------------------------- # Developing Software In Public Source: https://developer.rhino3d.com/en/guides/general/developing-software-in-public/ An overview of the McNeel Development Process. ## Overview Over the last 20 years we've put together a process that helps us build customer delight. There are eight pieces to this process, and they are all equally important. For years, we built our own proprietary tools to support most of the parts of this process. But now, there are great commercially available tools - tools we encourage you to use, too. Our Software Development process, just like the other processes, is a cycle. So we can start anywhere. ![Rhino Development Cycle](https://developer.rhino3d.com/images/developing-software-in-public-01.png) ## The Cycle Since this is a developer guide, let's start with writing code. ### Code This is what we as software developers spend a lot of our time doing. We’ve got our favorite IDE open, we write code, we debug, we solve problems. We don’t know a software developer that doesn’t love solving problems. When we’ve got something, we *commit* it to our version control system... ### Commit We commit code to a [Version Control System](https://en.wikipedia.org/wiki/Version_control). In our case, we use [git](https://git-scm.com/) with [GitHub](https://github.com/). There are many other version control systems out there. We used to use [Subversion](https://subversion.apache.org/), but now we use [GitHub](https://github.com/). [GitHub](https://github.com/) plays nicely with so many other tools and has such a rich API. But there are others worth considering: [BitBucket](https://bitbucket.org), [Mercurial](https://www.mercurial-scm.org/), etc. If you don’t use any version control, we beg you: please start. It’s so easy now. It lets you get back to other versions of your software before you introduced a problem. It helps you collaborate as a team. It is required for any kind of build automation. Did we mention it's easy? As developers, we use a modified version of [GitHub Flow](https://guides.github.com/introduction/flow/) to create and merge pull requests into our master branch. After we commit our code, we build it... ### Compile In addition to compiling at our desks, we have dedicated [TeamCity](https://www.jetbrains.com/teamcity/) servers that constantly build our code, and verify that it works with our master branch on [GitHub](https://github.com/). This makes sure that we don’t break each other’s ability to get the latest code and compile. These [TeamCity](https://www.jetbrains.com/teamcity/) servers verify every commit and also build our daily releases - many of them - about every four hours. They also build our public WIP and Service Release builds. With every new build, we test... ### Test When developers fix bugs and close issues, our internal testing staff makes sure the public build works correctly. We also rely on our customers to test WIP and Release Candidate builds. Testing happens before and after the next step: Publishing... ### Publish Whenever we’ve got a build that is ready to go out to customers, we deploy (or publish) it. This includes releasing ... - [Downloadable installers](http://www.rhino3d.com/download) - [SDKs](http://developer.mcneel.com) - Documentation (this here site) ...and making public announcements by email, blogs, and social media. We plan to release new versions of Rhino the **2nd Tuesday of each month**, which includes a: - Service Release (SR) for all users who have updates enabled. - [Service Release Candidate (SRC)](https://discourse.mcneel.com/t/rhino-service-release-candidates/53358). After the SRC goes through final testing, it becomes the next Service Release the following 2nd Tuesday. SRCs are also published weekly - typically on Tuesdays - and we encourage users to [install Service Release Candidates](https://discourse.mcneel.com/t/rhino-service-release-candidates/53358) as they help us test the next update. - [Work-In-Progress (WIP)](https://discourse.mcneel.com/t/welcome-to-serengeti/9612) build. ### Listen We listen in as many ways as we can: - [Chat](http://www.rhino3d.com/support#) - [Email](mailto:tech@mcneel.com) - Telephone Support (206) 545-6877 - [Forum (Discourse)](https://discourse.mcneel.com/) And often, when we listen, we find problems that need to be fixed. Sometimes they’re little...sometimes they’re HUGE. We always log an issue... ### Track We log issues in [YouTrack](https://mcneel.myjetbrains.com). [YouTrack](https://mcneel.myjetbrains.com) works well for us because it helps us ensure that each issue gets properly tested and documented. ### Prioritize Figuring out what is the next most important thing is HARD. We talk with our customers. We talk with each other. We use Gmail, Google Drive, and Google Docs to communicate. We chat 24 hours a day on [Slack](https://slack.com/). We meet every week on Tuesday. Before we meet, we share what we’ve done in a Google Doc. In that document, we share our goals for each of the products we’re releasing next each of the feature groups we’re working on including graphs of how we’re progressing over time there are links back to our [YouTrack](https://mcneel.myjetbrains.com) issues and we get verbal reports from each of the people working on the features. Also, each developer writes down what they’ve been working on, what they plan to do next, and what is getting in their way of completing their work. ### Automation And last but not least, we do a LOT of automation. Here are some of the things we automate: - Build every commit from every developer before it goes into our master development branch. - Closing issues in [YouTrack](https://mcneel.myjetbrains.com) when fixes get merged into our master development branch by the [TeamCity](https://www.jetbrains.com/teamcity/) servers. - Build internal and public releases on our [TeamCity](https://www.jetbrains.com/teamcity/) servers. - Publishing new WIP releases by typing a command into [Slack](https://slack.com/). - Upload public releases to our download servers. ## In Public Up until recently, these are the parts of our processes that we’ve made public: - Testing - Publishing (at least you see what we publish) - Listening And in the last couple of years, we made our issue tracker public by switching to YouTrack. Some issues we hide from public view for security or user privacy reasons. Something we'd like to do soon is to make even more of this public: - Sharing some of our code as public repos on GitHub so you’ve got some real, production-hardened code examples to work from - Letting you share fixes and improvements to our code. - Making it easier to build plug-in projects by publishing RhinoCommon as a NuGet package. - Helping with build automation where necessary. ## Related Topics - [Rhino Technology Overview](/guides/general/rhino-technology-overview) - [Contributing](/guides/general/contributing) - [Developer Prerequisites](/guides/general/rhino-developer-prerequisites) -------------------------------------------------------------------------------- # Grasshopper Scripting: C# Source: https://developer.rhino3d.com/en/guides/scripting/scripting-gh-csharp/ Provides detailed information on C# scripting in Grasshopper This guide is meant to be a detailed reference on all the important aspects and features of the Grasshopper C# Script component. If you would like a quick introduction to the script component, please check: [Grasshopper Script Component](/guides/scripting/scripting-component)
This guide does not discuss C# programming language syntax or the Rhino APIs. If you would like to learn how to create custom scripts using C# programming language, please check: Essential C# Scripting for Grasshopper - by Rajaa Issa
## C# Component Let's dive into C# scripting in Grasshopper by creating a Script component. Go to the **Maths** tab and **Script** panel and drop a C# Script component onto the canvas: ![](https://developer.rhino3d.com/en/guides/scripting/scripting-gh-csharp/csharp-component.png) You can also use the generic *Script* component that can run any language, and choose C# from the [ ● ● ● ] menu: ![](https://developer.rhino3d.com/en/guides/scripting/scripting-gh-csharp/csharp-component-generic.png) ### Opening Script Editor Now we can double-click on the component to open a script editor. Note that the component draws a cone pointing to the editor that is associated with this component. ![](https://developer.rhino3d.com/en/guides/scripting/scripting-gh-csharp/csharp-component-open.png) ### Component Options At any time, you can right-click on a script component to access a few options that would change how the component behaves. You know a couple of them that are common with other Grasshopper components like **Preview**, **Enable**, and **Bake**. We will discuss all the options that are specific to this script component in detail below: ![](https://developer.rhino3d.com/en/guides/scripting/scripting-gh-csharp/csharp-component-menu.png) #### Advanced Options An extended (advanced) flavour if the context menu is accessible by holding the Shift key and right-clicking on the component. This menu has a series of more advanced options that are needed in special cases, and are discussed later in this guide. #### Script Name Scripts in Grasshopper are not stored as files. Most often they are embedded inside script components. To name a script, we basically renamed the component itself. The script tab and breakpoints panel reflect the script name: ![](https://developer.rhino3d.com/en/guides/scripting/scripting-gh-csharp/csharp-component-name.png) ### Modern C# In C# script component, we can use a more modern flavour of C# language. It has more features that the older C# used in previous Rhino versions and also supports much simpler *Script-Mode* that is discussed below. For example, [String Interpolation](https://learn.microsoft.com/en-us/dotnet/csharp/tutorials/string-interpolation) is a one of these modern features: ```csharp int value = 42; // interpolated strings start with $ before opening the quotation // Each pair of {} can contain a valid C# statement. The computed result // of that statement is converted to a string (`.ToString()`) and // mixed into the interpolated string. Console.WriteLine($"Value: {value}"); ``` The script editor shows the version of C# language on the status bar: ![](https://developer.rhino3d.com/en/guides/scripting/scripting-gh-csharp/csharp-component-version.png) ## Inputs, Outputs The most important concept on a script component is the inputs/outputs. The **Script** component supports *Zoomable User Interface* (*ZUI* for short). This means that you can modify the inputs and outputs of the component by zooming in until the **Insert** [ ⊕ ] and **Remove** [ ⊖ ] controls are visible on either side: ![](https://developer.rhino3d.com/en/guides/scripting/scripting-gh-csharp/csharp-component-params-zui.png) By default a script component will have `x` and `y` inputs, and `out` and `a` as outputs. When all parameters on either side are removed, the component will draw a jagged edge on that side. This is completely okay as not all scripts require inputs or produce values as outputs: ![](https://developer.rhino3d.com/en/guides/scripting/scripting-gh-csharp/csharp-component-params.png) Every time you add a parameter, a new temporary name is assigned to it. You can right-click on the parameter itself, to edit the name. It is good practice to assign a meaningful name to parameters so others can understand what kind of inputs to pass to your component or what these inputs are gonna be used for. ![](https://developer.rhino3d.com/en/guides/scripting/scripting-gh-csharp/csharp-component-params-name.png) C# is a compiled language. Every time you are changing the combination of input and output parameters, or their *Type Hints* you C# script is updated and must be recompiled. The component will show any compile or execute error messages that might occur on the message bubbles. A short compile delay is expected when you are making parameter changes. If you have a very large script that takes a while to compile, you can disable the Grasshopper solver when making many parameter changes. ### Reserved Names Every programming language has a set of reserved words (or keywords) that are used in its language constructs. For example in C#, the words `return`, `decimal`, or `default` are all reserved. The C# script component does not allow using any of these keywords for input or output parameters: ![](https://developer.rhino3d.com/en/guides/scripting/scripting-gh-csharp/csharp-component-params-reserved-names.png) Check out [C# Keywords](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/) for more information on these keywords. ### Standard Output (out) The `out` output parameter is special. It captures anything that the script prints to the console (`Console.WriteLine`). The captured output is passed to this parameter as one string or multiple strings (one for each line). The default behaviour is that each line being printed to the console, becomes one item in the `out` parameter: ![](https://developer.rhino3d.com/en/guides/scripting/scripting-gh-csharp/csharp-component-outparam-01.png) You can control the single vs multi-line behaviour using the **Avoid Grafting Output Lines**. When this option is checked, all the console output will be passed as one single item to the `out` parameter. ![](https://developer.rhino3d.com/en/guides/scripting/scripting-gh-csharp/csharp-component-outparam-02.png) #### Toggling Output In case your script is not printing anything to the output, the `out` and be toggled using the **Standard Output/Error Parameter** option in the component context menu. When checked, the `out` parameter is added as the first output parameter. Otherwise, it would be removed: ![](https://developer.rhino3d.com/en/guides/scripting/scripting-gh-csharp/csharp-component-outparam-03.png) Removing the `out` parameter may improve the performance of your script component by a small amount, as the component will not attempt to capture the output, process (split into lines), and set the results on the `out` parameter. This performance increase might not be meaningful for a single component, but it would possibly be noticable in a larger Grasshopper definitions with multiple script components, each running thousands of times to process your data. Genereally it is good practice to toggle off the `out` parameter when unused. ### Type Hints C# is a strongly-typed language. More often than not we need to define the types of inputs and outputs to be able to use all their associated features. For example, C# knows how to add two integers (`int`) together, so if we are going to add inputs in the script, we would need to define a type that supports the addition. This is where *Type Hints* come into play. They define the type of input or output parameter while also act as data type converters. For example you can pass a *Line* to an input of type `integer` and the converter would capture the length of the line and pass that as the integer to the component. The conversions are identical to how Grasshopper parameters can convert to each other. By default all inputs and outputs of a C# component have **No Type Hint** meaning they are defined as type `object` in the script. This provides a lot of flexibility but if you pass two integers to the default `x` and `y` inputs, this script below would fail as C# does not know how to add two `object`s to each other. ```csharp a = x + y; ``` ![](https://developer.rhino3d.com/en/guides/scripting/scripting-gh-csharp/csharp-component-typehints-addition-fail.png) We can choose an `int` type hint for both `x` and `y` inputs to allow the addition to work in our script: ![](https://developer.rhino3d.com/en/guides/scripting/scripting-gh-csharp/csharp-component-typehints.png) There are plenty of *Type Hints* to choose from. They are available on both input and output parameters. Check out [Advanced: Type Hints](/guides/scripting/advanced-typehints) for more information on these type hints and their use cases. ### Outputs Are Objects In C# script component, the output parameters are always defined as `object`. Their associated *Type Hint* is only used as a converter to convert the output value to the desired type, and does not affect the data type defined in the script. This provides the flexibility to assign any value to an output and let the Type Hint determine how to convert that to the desired type. For example, assume output value `a` have an `int` *Type Hint* assigned. Since it is defined as `object` we can assign a value of `Sphere` and let the *Hint* convert the Sphere into a single numerical value. ### Parameter Access Component input parameters have another useful option on their context menu. This feature is called **Parameter Access** and is part of Grasshopper SDK ([GH_ParamAccess](https://developer.rhino3d.com/api/grasshopper/html/T_Grasshopper_Kernel_GH_ParamAccess.htm)): | Access | Description | | --------- | :---------- | | **Item** | Every data item is to be treated individually | | **List** | All data branches will be treated at the same time | | **Tree** | The entire data structure will be treated at once | {.scriptTable} We can modify this option on the script component inputs as well: ![](https://developer.rhino3d.com/en/guides/scripting/scripting-gh-csharp/csharp-component-paramaccess-01.png) Here is an example of the data type passed to the script component on the `x` parameter, for the three access kinds. Notice, on **Item** access, `x` is set passed as an individual `double` representing the number value, for **List** access, `x` is set to a `List` that contains all the number values in one branch, and for **Tree** access,`x` is set to a `DataTree` that provides access to all branches and items of the input: ![](https://developer.rhino3d.com/en/guides/scripting/scripting-gh-csharp/csharp-component-paramaccess-02.png) To get the generic collection data structures to use the correct `double` type, we can apply a `double` *Type Hint* to input parameter `x`: ![](https://developer.rhino3d.com/en/guides/scripting/scripting-gh-csharp/csharp-component-paramaccess-03.png) ### Required Inputs Script input parameters are always created as *Optional*. This means that your script runs whether wires are connected to the inputs or not. However, sometimes it is important to mark an input as **Required** (not *Optional*) to ensure there is a value available on that input before script runs. This is especially important if you are planning to [publish your scripts](#publishing-scripts). As of Rhino 8.14, there is a *Required* option on the input context menu that you could check to make an input required: ![](https://developer.rhino3d.com/en/guides/scripting/scripting-gh-csharp/csharp-component-required.png) ### Extracting Parameters Grasshopper allows extracting an input parameter from a component. Parameters on a component are independent entities that could exist as inputs or outputs on a component or as floating parameters ([Types of Parameters](https://developer.rhino3d.com/guides/grasshopper/simple-parameters/#types-of-parameters)). You can extract a script input by choosing **Extract** from the right-click menu on the parameter: ![](https://developer.rhino3d.com/en/guides/scripting/scripting-gh-csharp/csharp-component-extractparam-01.png) If you have a *Type Hint* set on a parameter, the extracted floating parameter will be of that data type: ![](https://developer.rhino3d.com/en/guides/scripting/scripting-gh-csharp/csharp-component-extractparam-02.png) ## SDK-Mode There are two ways you can create a C# script in Grasshopper. The first and the easiest is to write a C# script in the simplest form. For example, if we want to pass the sum of our two `x` and `y` inputs to the `a` output, we would create a script component with this script: ```csharp a = x + y; ``` Note that we did not add and `using` statements to our script because we are only using builtin C# functionality here that is addition of two numbers. However, a typical Grasshopper component can: - Execute code *Before* component is asked to solve the inputs ([BeforeSolveInstance](https://developer.rhino3d.com/api/grasshopper/html/M_Grasshopper_Kernel_GH_Component_BeforeSolveInstance.htm)) - *Solve* the inputs and pass results to outputs ([SolveInstance](https://developer.rhino3d.com/api/grasshopper/html/M_Grasshopper_Kernel_GH_Component_SolveInstance.htm)) - Execute code *After* component is finishing solving the inputs ([AfterSolveInstance](https://developer.rhino3d.com/api/grasshopper/html/M_Grasshopper_Kernel_GH_Component_AfterSolveInstance.htm)) - Execute code to draw geometry wires on Rhino viewports ([DrawViewportWires](https://developer.rhino3d.com/api/grasshopper/html/M_Grasshopper_Kernel_GH_Component_DrawViewportWires.htm)) - Execute code to draw geometry meshes on Rhino viewports ([DrawViewportMeshes](https://developer.rhino3d.com/api/grasshopper/html/M_Grasshopper_Kernel_GH_Component_DrawViewportMeshes.htm)) The methods linked above are part of Grasshopper SDK for creating custom components. Every developer that creates a Grasshoper plugin is aware of these methods and might be using them to customize the component behaviour. In a C# script component, we can implement our scripts in a similar manner. That is why we are calling it the **SDK-Mode** as it provides similar functionality that is available in Grasshopper SDK. By default when you create a C# script component, the template script is already in *SDK-Mode* as this is how C# components before Rhino 8 have been working and we kept it the same in Rhino 8 and after. This is how the default script looks like (actual script might not be identical): ```csharp using System; using Rhino; using Rhino.Geometry; public class Script_Instance : GH_ScriptInstance { private void RunScript(object x, object y, ref object a) { // Write your logic here a = null; } } ``` `GH_ScriptInstance` is the base class that implements methods below, similar to Grasshopper components: - `BeforeRunScript`: Execute code *Before* component is asked to solve the inputs - `RunScript`: *Solve* the inputs and pass results to outputs - `AfterRunScript`: Execute code *After* component is finishing solving the inputs - `DrawViewportWires`: Execute code to draw geometry wires on Rhino viewports - `DrawViewportMeshes`: Execute code to draw geometry meshes on Rhino viewports This class provides base implementation for these methods except for `RunScript` that we must implement. In the example above, we subclass from `GH_ScriptInstance` and provide an empty implementation for `RunScript`. ### RunScript Signature The `RunScript` method signature is going to include all the component inputs and outputs by their name and data type (based on their *Type Hints*). All outputs are passed by reference using `ref` keyword so that providing a value for them would be optional. You can write the logic of your component inside the `RunScript` block, take the input values, compute, and set the outputs. As with any other Grasshopper component, the `RunScript` method might be call multiple times based on the pairing of input data. ### Changing RunScript Signature *Script* component is smart enough to update the RunScript signature when parameters on the component are changed. It is also capable of updating parameters on the component, when the RunScript signature is manually edited: Notice that input and output types will be used to apply an appropriate *Type Hint* to the parameter. The collection type of the input (`List`, or `DataTree`) is also used to apply the correct access kind to the associated input parameter. If the data type does not have an associated *Type Hint*, it will adopt a *Cast Type Hint* that tries to directly cast input values to the data type: ![](https://developer.rhino3d.com/en/guides/scripting/scripting-gh-csharp/csharp-component-runscriptsig-02.png) ### Before, After Solve Overrides You can easily add the `BeforeRunScript` and `AfterRunScript` methods to your `Script_Instance` implementation by: - Click on the **Add SolveInstance Overrides** button on the editor dashboard - Click on the **Add SolveInstance Overrides** menu inside the **Grasshopper** menu on the editor - Typing them yourself ![](https://developer.rhino3d.com/en/guides/scripting/scripting-gh-csharp/csharp-component-sdkmode-01.png) These two methods will be added to the class implementation: ![](https://developer.rhino3d.com/en/guides/scripting/scripting-gh-csharp/csharp-component-sdkmode-02.png) A good example of using these two methods would be to setup instance variables on the class instance during `BeforeRunScript` and clean them up after the execution during `AfterRunScript`. The component is not allowed to make changes to the output parameters inside these methods. Each one of these methods is executed only once, per one full execution of this component. We can put a few print statements in these methods, and check the order of execution: ![](https://developer.rhino3d.com/en/guides/scripting/scripting-gh-csharp/csharp-component-sdkmode-03.png) There are two range components included in this example to provide inputs to the script component. Each range component outputs 3 items, and their associated input parameter on the script component has a `double` *Type Hint* assigned to it. This means the `RunScript` method is going to be executed 3 times for 3 pairs of `x` and `y`. Notice that the text *Before Solve* is printed on the same output item as *Solve #0* which is the first iteration of solving inputs. This is because `BeforeRunScript` runs before the script component is allowed to set values on its output parameters and therefore any output printed to the console are going to be captured by the first iteration of `RunScript` that runs right after. All other iterations of `RunScript` will continue after the first. The `AfterRunScript` is executed after all the iterations of `RunScript` have completed execution. Notice that the text *After Solve* is captured and appended to the last message on the `out` parameter which belongs to the last iteration of `RunScript` that ran right before. ### Preview Overrides *Preview Overrides* are continuously called as you are interacting with the Rhino viewports. See [Draw Calls]() for more information on how these overrides work. You can easily add the `DrawViewportWires` and `DrawViewportMeshes` methods to your `Script_Instance` implementation by: - Click on the **Add Preview Overrides** button on the editor dashboard - Click on the **Add Preview Overrides** menu inside the **Grasshopper** menu on the editor - Typing them yourself ![](https://developer.rhino3d.com/en/guides/scripting/scripting-gh-csharp/csharp-component-sdkmode-04.png) These two methods will be added to the class implementation: ![](https://developer.rhino3d.com/en/guides/scripting/scripting-gh-csharp/csharp-component-sdkmode-05.png) `DrawViewportWires` is called first and here you can draw points and curves. `DrawViewportMeshes` is called later and this is where you can draw transparent shapes, such as meshes. Notice there is also a `ClippingBox` property implementation that is added as well. The default value is `BoundingBox.Empty` but you should change that to a larger bounding box that bounds any custom geometry being drawn by your component. Here is an example of a component that draws a 2D filled rectangle at the top-left corner of Rhino viewport: ![](https://developer.rhino3d.com/en/guides/scripting/scripting-gh-csharp/csharp-component-sdkmode-06.png) An argument of type [IGH_PreviewArgs](https://developer.rhino3d.com/api/grasshopper/html/T_Grasshopper_Kernel_GH_PreviewArgs.htm) is passed to these preview override methods. As you can see in the example above, you can access the `args.Display` property which is a Rhino [DisplayPipeline](https://developer.rhino3d.com/api/rhinocommon/rhino.display.displaypipeline) instance and has a lot of helpful draw methods. Rhino 8 and above primarily use a UI framework called [Eto](https://github.com/picoe/Eto). However Grasshopper 1 predates this adoption and uses `System.Drawing` framework for its graphical interface. They both have similar data structures (e.g. `Rectangle`) but it is important to know when working with Grasshopper preview overrides to use `System.Drawing` data types. ### Draw Calls It is very important to know that **Preview Overrides** are fundamentally different from *Solve Overrides* (`BeforeRunScript`, `RunScript`, `AfterRunScript`) in a sense that they are executed outside of Grasshopper solution and are designed to work in tandem with the solve methods. When you interact with a component, Grasshopper triggers a new solution on the definition. When solution is completed, Grasshopper is ready to draw previews on all the geometries generated by the components on the canvas. Any interaction with Rhino viewports results in Grasshopper receiving a request from Rhino to draw its previews in the viewports. These draw requests happen anytime you interact with Rhino viewports and a Grasshopper definition is open. This is exactly where the *Preview Overrides* come into play. Their main purpose is to peform custom drawings in Rhino viewports based on the results of the computation done by *Solve Overrides*. Contradictory to the solve methods, the *Preview Overrides* are continuously called as you are interacting with the Rhino viewports. ## Script-Mode A simpler, more script-like, method of using *C# Script Component* is to write C# code in the script editor without implementing the `GH_ScriptInstance` class. This method does not support running code before and after the script or creating custom graphics on Rhino viewports, but it is great for any script that does not need these functionalities. Here is a very simple example: ![](https://developer.rhino3d.com/en/guides/scripting/scripting-gh-csharp/csharp-component-scriptmode-01.png) Check out [NuGet Packages]() for another example. *SDK-Mode* and *Script-Mode* are both valid ways of writing scripts in C# script component. Choose the one you are comfortable with and is the most appropriate for the use case. ### Accessing Inputs Notice that we do not have a `RunScript` method that would show the input and outputs and their data types. The `x` input parameter is magically defined and set before your script starts. You can reference the input values anywhere in the *Global* scope of your script. In the example above, the output `x` is already defined and set in the script scope, and its value can be used in the script: ```csharp a = $"Hello {x}"; ``` You can also define functions in the global scope without defining classes. These global functions can access the globally defined parameters: ```csharp using System; double Solve() { return x + y; } a = Solve(); ``` However, the script below will throw an error since `Solver.Compute()` method does not have access to the `x` and `y` inputs defined in the global scope. This is a limitation of C# language itself and is by design: ```csharp using System; Solver s = new Solver(); a = s.Compute(); class Solver { public double Compute() { return x + y; } } ``` In these cases, try passing the desired values to your custom methods: ```csharp using System; Solver s = new Solver(); a = s.Compute(x, y); class Solver { public double Compute(double left, double right) { return left + right; } } ``` ### Settings Outputs As mentioned above, in *Script-Mode*, the output variables are magically defined in the script scope by the component. Assign the desired values to the outputs in your script and they will be set on the component outputs. ## Debugging Scripts You can debug your C# scripts in the script editor. During debug, we can execute the script line by line or pause the execution at certain lines called **Breakpoints** and inspect the values of global and local variables. Move your mouse cursor to the left side of any script line and click to add a **Breakpoint**: ![](https://developer.rhino3d.com/en/guides/scripting/scripting-gh-csharp/csharp-component-debugging-01.png) The **Breakpoints** tray at the bottom will show all the breakpoints, and will provide buttons to *Enabled/Disable* or *Clear* them: ![](https://developer.rhino3d.com/en/guides/scripting/scripting-gh-csharp/csharp-component-debugging-02.png) Use the **Toggle** button to activate or deactivate the breakpoints. Deactivated breakpoints will show up as gray dots in the editor: ![](https://developer.rhino3d.com/en/guides/scripting/scripting-gh-csharp/csharp-component-debugging-03.png) When you add breakpoints, the editor makes a few UI changes and provides a few more utilities for debugging: - The **Run** button will change to **Debug** - **Variables**, **Watch**, and **Call Stack** trays will be added to the bottom tray bar Now click on the green **Debug** button on the editor dashboard. The editor will run the script and: - Stops at breakpoints - Highlights the breakpoint line in orange and shows an arrow on the left side of the line - Highlights status bar in orange to show we are debugging a script - Activates the debug control buttons on the editor dashboard - Opens the **Variables** tray at the bottom to show global and local variables ![](https://developer.rhino3d.com/en/guides/scripting/scripting-gh-csharp/csharp-component-debugging-04.png) We can control the execution of script using the debug control buttons on the editor dashboard: ![](https://developer.rhino3d.com/en/guides/scripting/scripting-gh-csharp/csharp-component-debugging-05.png) From left to right, they are: - **Continue:** continues running the script until it stops on another breakpoint - **Step Over:** executes current line and moves on the next line - **Step In:** if the current line includes a function call, this will step into the lines defining the function code - **Step Out:** if previously stepped into a function code, this will continue executing the function code until control is returned from the function to the calling code and will stop there - **Stop:** stops debugging the script and does not continue executing the rest Click on **Continue** to see the execution move to the next line: Notice that the **Variables** panel now shows new values for **x** and **y**. The panel header also shows **Run Script (2 of 11)** on the top-right meaning this is the second time Grasshopper is executing this component with a pair of **x** and **y** inputs: ![](https://developer.rhino3d.com/en/guides/scripting/scripting-gh-csharp/csharp-component-debugging-06.png) Progressively clicking on **Continue** will continue executing the script and modifying the variables. At each stop, the **Variables** tray shows the current values of global and local variables. At any point during debug, the **Stop** button stops debugging. The script component will show an error marking with the message **Debug Stopped**: ![](https://developer.rhino3d.com/en/guides/scripting/scripting-gh-csharp/csharp-component-debugging-07.png) Once the debug stops, the editor UI changes back to normal, and the **Variables** tray will show the last state of the variables. The tray will keep these data until another session of debugging is started. ### Variables Tray *Variables* tray is a great tool to inspect the current values of all the global and local variables in our script. Variables data is shown in a table with 3 columns: **Name**, **Value**, **Type**. For each variable you can see the current value and the type of data it is holding. A red marker will highlight the variables that changed during debug: ![](https://developer.rhino3d.com/en/guides/scripting/scripting-gh-csharp/csharp-component-debugging-08.png) For more complicated data types with fields and properties, you can expand the variable to see current values of its members: ![](https://developer.rhino3d.com/en/guides/scripting/scripting-gh-csharp/csharp-component-debugging-09.png) If a value is a collection of other values, you can expand the variable to see each item individually. The *Name* column shows the item index like `[0]`: ![](https://developer.rhino3d.com/en/guides/scripting/scripting-gh-csharp/csharp-component-debugging-10.png) ### Watch Tray *Watch* tray is very similar to the variables tray. The primary difference is that *Watch* tray only shows the variables that you have specifically added to watch. Use the **Add Expression** button on the tray toolbar to add a new variable to watch. Hit Enter on the added *Expression* item to edit and type the variable name: ![](https://developer.rhino3d.com/en/guides/scripting/scripting-gh-csharp/csharp-component-debugging-11.png) *Watch* tray will show a green checkmark when it can extract the variable value during debug. A yellow warning icon is shown when the variable is not in scope: ![](https://developer.rhino3d.com/en/guides/scripting/scripting-gh-csharp/csharp-component-debugging-12.png) ### Call Stack Tray *Call Stack* tray shows the call stack frames. This loosly translates to functions calls in our script. The item at the top is the last function that the script is executing, and the list goes on to show other functions that called the current function In the example below, script execution started by running `RunScript`. Then the script called the `Sum` method and that is where we are paused during debug right now. *Call Stack* tray shows this function at the top of the list, with `RunScript` following right after. *Variable* and *Watch* trays also show the values of variables in the current call frame: ![](https://developer.rhino3d.com/en/guides/scripting/scripting-gh-csharp/csharp-component-debugging-13.png) You can click on other stack frames, and switch to *Variables* or *Watch* tray to inspect the values in their scope: ![](https://developer.rhino3d.com/en/guides/scripting/scripting-gh-csharp/csharp-component-debugging-14.png) *Call Stack* tray shows stack frames for different threads in your script independently. ## NuGet Packages C# script can benefit from third-party packages that are published on [NuGet](https://www.nuget.org) package server. You can use the *Install Package* button to install any of these packages and use in your script: ![](https://developer.rhino3d.com/en/guides/scripting/scripting-gh-csharp/csharp-component-packages-01.png) The Install Package dialog, shows a couple of example of how you can specify the package name and version requirements. The **Add Package Reference to Script** option, when check (default), adds a package reference to the script text. In this manner, the script always knows which packages it needs even when you send this script to others and they do not have the required packages installed. Notice the `#r "nuget: RestSharp, 110.2.0"` line in the example script below. The format follows the package reference for script on NuGet website: ![](https://developer.rhino3d.com/en/guides/scripting/scripting-gh-csharp/csharp-component-packages-02.png) ```csharp #r "nuget: RestSharp, 110.2.0" using System; using System.Collections.Generic; using Rhino; using RestSharp; using RestSharp.Authenticators; var client = new RestClient("https://httpbin.org"); var request = new RestRequest("get"); var response = client.Get(request); a = response.Content; ``` ## Assembly References C# scripts can also directly reference dotnet assembly files. You can use the **Install Package** dialog and change the **Package Source** option to **DLL Reference**: ![](https://developer.rhino3d.com/en/guides/scripting/scripting-gh-csharp/csharp-component-packages-03.png) If the assembly is already loaded in Rhino, you can reference it by just typing the name of the assembly. Make sure the extension is included in the assembly name (e.g. `.dll`) A package reference like example below is optionally added to your script: ```csharp #r "System.Text.Json.dll" ``` As the *Install Package* dialog examples show, you can also provide a relative or absolute path to the assembly: ```csharp #r "https://developer.rhino3d.com/path/to/my/assemblies/MySharedAssembly.dll" ``` ## Customizing Editor Script editor used in C# script component, is an embedded variant of the main script editor in Rhino that is launched from `ScriptEditor` command. The component script editor, has a *Grasshopper* menu and few other Grasshopper-specific buttons. We have already discussed the *SDK-Mode* related buttons in the editor dashboard. Here is a description of other editor options that are useful in Grasshopper: ### Close On Save By default, when *Saving* the script in C# script component, the editor stays open. It saves the script and triggers a solution on the Grasshopper definition with the newly updated script. This behaviour can be changed using the **Grasshopper -> Toggle Close Editor On Save** menu. When enabled, choosing *File -> Save* or *Ctrl + S* will save the script and close the editor (This is the default behaviour of the legacy script editor in GHPython component). ### Layout Options Script editor used in C# script component, has a series of toggle menus to change the layout of the editor and make it more compact. These options can be accessed from **Window** menu in the editor, and can be used to dedicated more screen space to scripting area, and also visually differentiate the Grasshopper editor from the main editor in Rhino. They are also accessible from the **Tools -> Options** menu in the editor. Hover the mouse over the question mark icons to see more information on each option: ![](https://developer.rhino3d.com/en/guides/scripting/scripting-gh-csharp/csharp-component-layoutoptions.png) #### Toggle Dashboard You can completely hide the editor dashboard and open up more space for script: ![](https://developer.rhino3d.com/en/guides/scripting/scripting-gh-csharp/csharp-component-toggle-dashboard.png) #### Toggle Compact Dashboard When Dashboard is visible, you can save some space by making it more compact: ![](https://developer.rhino3d.com/en/guides/scripting/scripting-gh-csharp/csharp-component-toggle-compact-dashboard.png) #### Toggle Compact Script Tabs By default, Grasshopper editor does not show the script tabs, unless debug steps into a source file other than the main script. Toggle this option if you want the tabs to be always visible: ![](https://developer.rhino3d.com/en/guides/scripting/scripting-gh-csharp/csharp-component-toggle-compact-scripttabs.png) #### Toggle Compact Browser By default Browser tabs are NOT shown on the left side of the editor in Grasshopper. The tab selector buttons are shown on the status bar to save some space. Toggle this option if you want to see the browser tabs on the left side: ![](https://developer.rhino3d.com/en/guides/scripting/scripting-gh-csharp/csharp-component-toggle-compact-browser.png) #### Toggle Compact Console By default Console tabs are shown on the bottom edge of the editor in Grasshopper. Toggle this option if you want to see the tab selector buttons on the status bar to save some space: ![](https://developer.rhino3d.com/en/guides/scripting/scripting-gh-csharp/csharp-component-toggle-compact-console.png) ## Publishing Scripts If you are planning to publish your script components in a Grasshopper plugin, a few considerations are important. Right-Click on the script component and set appropriate values for **Tooltip**. This description is used for publishing the script and is shown on the published component. ![](https://developer.rhino3d.com/en/guides/scripting/scripting-gh-csharp/csharp-component-publishing-01.png) Right-Click on all input and output parameters and set a **Name** (Human-readable) and **Tooltip** for each parameter. The human-readable name is shown when **Display -> Draw Full Names** are enabled in Grasshopper. Setting a human-readable name and description helps understanding what inputs the component requires, and what outputs it provides and generally makes it easier to work with your published components. ![](https://developer.rhino3d.com/en/guides/scripting/scripting-gh-csharp/csharp-component-publishing-02.png) Check out [Creating Rhino and Grasshopper Plugins](/guides/scripting/projects-create) on how to publish your script components in a Grasshopper plugin. ## Template Scripts There are a few template scripts available in the **Templates** panel in the editor. You can Double-Click on any of these templates to replace the contents of your script with the template. This is a good way to start slightly more complicated scripts: ![](https://developer.rhino3d.com/en/guides/scripting/scripting-gh-csharp/csharp-component-templates-01.png) ### User Objects As Templates Another great way to create template scripts is to setup one script component with the desired inputs, outputs, and template script, and then save that as a Grasshopper **User Object**. You can set extra metadata on the component and customize its icon. Every time you would place an instance of this *User Object*, you are effectively creating a new script component instance with the template script and parameters. ### Resetting Icon If you have a script component that has an overriden, incorrect, or low-resolution icon, you can reset the icon back to the default for the scripting language using the **Reset Icon** menu button in the *Advanced* context menu (Shift + Right-Click). ## Shared Scripts *C# Script Component* is most commonly used with a C# script that is embedded inside the component. However you can share a single script between multiple *Script* components. ### Input Script You can create a C# script (SDK-Mode is not yet supported for shared scripts) in a Grasshopper panel, and pass that as an input to multiple *Script* components. Script components have a special `script` input parameter that can be activated from the advanced context menu. Shift + Right-Click on the component and choose **Script Input Parameter ("script")** to toggle this input: ![](https://developer.rhino3d.com/en/guides/scripting/scripting-gh-csharp/csharp-component-sharedscripts-01.png) ### Language Specifier Directive Notice that the scripts starts with `// #! csharp`. This is called a language specifier directive. Its purpose is to embed the expected language in the script code itself as a comment and a known pattern. Since scripts that are stored in this way do not have a file extension the language specifier is necessary for the script component to determine the language it should use to run the script. Alternatively you can Right-Click on the `script` parameter and choose a language from the menu, but that means all the input scripts must be of the chosen language. Also note that the component icon changes to a generic script icon. The reason is that a script component with an `script` input can executed any of the supported languages. Notice the language specifer for the second script is `#! python 3`: ![](https://developer.rhino3d.com/en/guides/scripting/scripting-gh-csharp/csharp-component-sharedscripts-02.png) ### Output Script Editing a script in a Grasshopper panel is not very convenient. Script components have a special `script` output parameter that can be activated from the advanced context menu. Shift + Right-Click on the component and choose **Script Output Parameter ("script")** to toggle this output: ![](https://developer.rhino3d.com/en/guides/scripting/scripting-gh-csharp/csharp-component-sharedscripts-03.png) In this manner, we can use the first component to create and use our script, and pass the same script to other components to ensure they all run the same script. And as shown above, you can pass scripts of other languages to the `script` input as well: ![](https://developer.rhino3d.com/en/guides/scripting/scripting-gh-csharp/csharp-component-sharedscripts-04.png) ## Value-Type Outputs C# is a type-safe language and differentiates between [Reference Types](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/reference-types) and [Value Types](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/builtin-types/value-types). We do not intent to get into the details here. It is only important to remember that *Value Types* can not be `null` and always have a default value. For example, in C# the statement below is invalid since `int` is a value type: ```csharp int x = null; ``` If your C# script (*SDK* or *Script-Mode*), has an input that is marked with a *Type Hint* representing a *Value Type* (e.g. `double` or `Point3d`), and is not connected in your Grasshopper definition, the input will adopt the default value. See this example. The C# script has two `double` inputs (`x` and `y`) but only assigns a value to the first input `x`. The `y` output will be set to the default value for the type `double` which is `0`: ![](https://developer.rhino3d.com/en/guides/scripting/scripting-gh-csharp/csharp-component-valuetypes-01.png) Similarly, if your C# script (*SDK* or *Script-Mode*), has an output that is marked with a *Type Hint* representing a *Value Type*, and this output is not set in your script, the output will adopt the default value: ![](https://developer.rhino3d.com/en/guides/scripting/scripting-gh-csharp/csharp-component-valuetypes-02.png) Keep this in mind when working with output Type Hints in C# script component. ## Output Previews Output parameters have their own individual *Preview* control. This option is on by default and Grasshopper renders previews for geometry values in output parameters: ![](https://developer.rhino3d.com/en/guides/scripting/scripting-gh-csharp/csharp-component-outputpreview-01.png) You can toggle this option off for any of the output parameters and hide the preview, using the **Preview** menu in the component context menu. Notice that the box preview does not show up while sphere is still previewed: ![](https://developer.rhino3d.com/en/guides/scripting/scripting-gh-csharp/csharp-component-outputpreview-02.png) ## Exporting Script You can save the script that is embedded in a C# script component, using the **Export Script** menu item from component context menu. When *Save Dialog* opens, choose a file name and location where you would like to save the script, and hit save. ## Script Cache C# script component compiles and caches the script, so it can execute faster when the script is not changed. Normally the cache is expired automatically when you make changes to the script or any of the parameters. However, sometimes it is desired to expire the cache manually to ensure component is using a fresh build. To expire the compile cache, choose **Discard Cache** from component context menu. -------------------------------------------------------------------------------- # Grasshopper Scripting: Python Source: https://developer.rhino3d.com/en/guides/scripting/scripting-gh-python/ Provides detailed information on Python scripting in Grasshopper This guide is meant to be a detailed reference on all the important aspects and features of the Grasshopper Python 3 or 2 Script component. If you would like a quick introduction to the script component, please check: [Grasshopper Script Component](/guides/scripting/scripting-component) This guide does not discuss Rhino or Grasshopper APIs. If you would like to know how to create complex geometries in Rhino and Grasshopper, please check out: [Python in Rhino & Grasshopper](/guides/rhinopython) ## Python Component Let's dive into Python scripting in Grasshopper by creating a Script component. Go to the **Maths** tab and **Script** panel and drop a Python 3 Script component onto the canvas. There is also an IronPython 2 Script component available that you can use. See above for the differences: ![](https://developer.rhino3d.com/en/guides/scripting/scripting-gh-python/python3-component.png) You can also use the generic *Script* component that can run any language, and choose Python 3 from the [ ● ● ● ] menu: ![](https://developer.rhino3d.com/en/guides/scripting/scripting-gh-python/python3-component-generic.png) ### Opening Script Editor Now we can double-click on the component to open a script editor. Note that the component draws a cone pointing to the editor that is associated with this component. ![](https://developer.rhino3d.com/en/guides/scripting/scripting-gh-python/python3-component-open.png) The script editor shows the version of Python language on the status bar: ![](https://developer.rhino3d.com/en/guides/scripting/scripting-gh-python/python-component-version.png) ### Component Options At any time, you can right-click on a script component to access a few options that would change how the component behaves. You know a couple of them that are common with other Grasshopper components like **Preview**, **Enable**, and **Bake**. We will discuss all the options that are specific to this script component in detail below: ![](https://developer.rhino3d.com/en/guides/scripting/scripting-gh-python/python3-component-menu.png) #### Advanced Options An extended (advanced) flavour if the context menu is accessible by holding the Shift key and right-clicking on the component. This menu has a series of more advanced options that are needed in special cases, and are discussed later in this guide. #### Script Name Scripts in Grasshopper are not stored as files. Most often they are embedded inside script components. To name a script, we basically renamed the component itself. The script tab and breakpoints panel reflect the script name: ![](https://developer.rhino3d.com/en/guides/scripting/scripting-gh-python/python3-component-name.png) ## Inputs, Outputs The most important concept on a script component is the inputs/outputs. The **Script** component supports *Zoomable User Interface* (*ZUI* for short). This means that you can modify the inputs and outputs of the component by zooming in until the **Insert** [ ⊕ ] and **Remove** [ ⊖ ] controls are visible on either side: ![](https://developer.rhino3d.com/en/guides/scripting/scripting-gh-python/python3-component-params-zui.png) By default a script component will have `x` and `y` inputs, and `out` and `a` as outputs. When all parameters on either side are removed, the component will draw a jagged edge on that side. This is completely okay as not all scripts require inputs or produce values as outputs: ![](https://developer.rhino3d.com/en/guides/scripting/scripting-gh-python/python3-component-params.png) Every time you add a parameter, a new temporary name is assigned to it. You can right-click on the parameter itself, to edit the name. It is good practice to assign a meaningful name to parameters so others can understand what kind of inputs to pass to your component or what these inputs are gonna be used for. ![](https://developer.rhino3d.com/en/guides/scripting/scripting-gh-python/python3-component-params-name.png) ### Reserved Names Every programming language has a set of reserved words (or keywords) that are used in its language constructs. For example in Python, the words `dir`, `filter`, or `str` are all reserved. The Python script component will warn you about using any of these keywords for input or output parameters, but does not stop you from using them. Python language itself does not stop you from assigning a value to builtin functions like `dir` or `filter` and this component follows this behaviour: ![](https://developer.rhino3d.com/en/guides/scripting/scripting-gh-python/python3-component-params-reserved-names.png) Check out [Python 3 Keywords](https://docs.python.org/3/reference/lexical_analysis.html#keywords) and [Python 3 Builtin Functions](https://docs.python.org/3/library/functions.html#built-in-functions) for more information on these keywords. ### Standard Output (out) The `out` output parameter is special. It captures anything that the script prints to the console (`print()`). The captured output is passed to this parameter as one string or multiple strings (one for each line). The default behaviour is that each line being printed to the console, becomes one item in the `out` parameter: ![](https://developer.rhino3d.com/en/guides/scripting/scripting-gh-python/python3-component-outparam-01.png) You can control the single vs multi-line behaviour using the **Avoid Grafting Output Lines**. When this option is checked, all the console output will be passed as one single item to the `out` parameter. ![](https://developer.rhino3d.com/en/guides/scripting/scripting-gh-python/python3-component-outparam-02.png) #### Toggling Output In case your script is not printing anything to the output, the `out` and be toggled using the **Standard Output/Error Parameter** option in the component context menu. When checked, the `out` parameter is added as the first output parameter. Otherwise, it would be removed: ![](https://developer.rhino3d.com/en/guides/scripting/scripting-gh-python/python3-component-outparam-03.png) Removing the `out` parameter may improve the performance of your script component by a small amount, as the component will not attempt to capture the output, process (split into lines), and set the results on the `out` parameter. This performance increase might not be meaningful for a single component, but it would possibly be noticable in a larger Grasshopper definitions with multiple script components, each running thousands of times to process your data. Genereally it is good practice to toggle off the `out` parameter when unused. ### Type Safety Python is NOT a type-safe language. This means a variable named `x` can be assigned a value if any type. This is very different from typed languages like C# where if variable `x` is defined as an `int`, a value of type `Sphere` can not be assigned to it. Lets assume a Python Script Component that contains this script: ```python a = x + y ``` If the `x` and `y` inputs are not connected and therefore do not carry any value (value of `None`), Python will throw an error when this component is executed since it does not know how to add a `None` to another `None`: ![](https://developer.rhino3d.com/en/guides/scripting/scripting-gh-python/python3-component-typesafety-01.png) Type-safety really means that the language would detect variable types during compile (before even executing the script) and will throw errors if operations or function calls does not support the specific variable type. But as we mentioned Python is not type-safe so during compilation it assumes you are gonna provide values that support the `+` operation in the `a = x + y`. This is why it throws the error shown above, during execution of the script. By providing values that support the addition, in this example integers, the component will run without any errors: ![](https://developer.rhino3d.com/en/guides/scripting/scripting-gh-python/python3-component-typesafety-02.png) ### Type Hints In plenty of cases we need to make conversions to the input values of our script. These are the well-known parameter conversions that we all know and love in Grasshopper. As an example, you can pass a *Line* parameter to a *Number* parameter and it will automatically grab the lenght of the line as the value for the *Number* parameter: ![](https://developer.rhino3d.com/en/guides/scripting/scripting-gh-python/python3-component-typehints-01.png) We can easily apply these automatic conversions to Script Component input parameters. In this example, two *Line* values are passed to a Python script `a = x + y`. Both `x` and `y` inputs have been assigned *Type Hint* of `float` which can hold floating point values (*Number* in Grasshopper): ![](https://developer.rhino3d.com/en/guides/scripting/scripting-gh-python/python3-component-typehints-02.png) Therefore before running the script, both inputs lines will be converted to `float` values by the *Type Hint* and the output `a` will be set to the sum of the line lenghts: ![](https://developer.rhino3d.com/en/guides/scripting/scripting-gh-python/python3-component-typehints-03.png) There are plenty of *Type Hints* to choose from. They are available on both input and output parameters. Check out [Advanced: Type Hints](/guides/scripting/advanced-typehints) for more information on these type hints and their use cases. ### Parameter Access Component input parameters have another useful option on their context menu. This feature is called **Parameter Access** and is part of Grasshopper SDK ([GH_ParamAccess](https://developer.rhino3d.com/api/grasshopper/html/T_Grasshopper_Kernel_GH_ParamAccess.htm)): | Access | Description | | --------- | :---------- | | **Item** | Every data item is to be treated individually | | **List** | All data branches will be treated at the same time | | **Tree** | The entire data structure will be treated at once | {.scriptTable} We can modify this option on the script component inputs as well: ![](https://developer.rhino3d.com/en/guides/scripting/scripting-gh-python/python3-component-paramaccess-01.png) Here is an example of the data type passed to the script component on the `x` parameter, for the three access kinds. Notice, on **Item** access, `x` is set passed as an individual `double` representing the number value, for **List** access, `x` is set to a `List` that contains all the number values in one branch, and for **Tree** access,`x` is set to a `DataTree` that provides access to all branches and items of the input: ![](https://developer.rhino3d.com/en/guides/scripting/scripting-gh-python/python3-component-paramaccess-02.png) Notice that the *Item* and *List* access are showing builtin Python types of `float` and `list`, however the *Tree* access is showing `Grasshopper.DataTree[Object]` type (See [DataTree](https://developer.rhino3d.com/api/grasshopper/html/T_Grasshopper_DataTree_1.htm)). This is an important topic that is discussed in [Marshalling](). To get the generic DataTree structure to use the correct type, we can apply a `float` *Type Hint* to input parameter `x`. Note that generic DataTree structure shows *Double* as the element type. This is the type for *Number* parameter in Grasshopper that can fit large floating point values and it somewhat similar to `float` in Python: ![](https://developer.rhino3d.com/en/guides/scripting/scripting-gh-python/python3-component-paramaccess-03.png) ### Required Inputs Script input parameters are always created as *Optional*. This means that your script runs whether wires are connected to the inputs or not. However, sometimes it is important to mark an input as **Required** (not *Optional*) to ensure there is a value available on that input before script runs. This is especially important if you are planning to [publish your scripts](#publishing-scripts). As of Rhino 8.14, there is a *Required* option on the input context menu that you could check to make an input required: ![](https://developer.rhino3d.com/en/guides/scripting/scripting-gh-python/python3-component-required.png) ### Extracting Parameters Grasshopper allows extracting an input parameter from a component. Parameters on a component are independent entities that could exist as inputs or outputs on a component or as floating parameters ([Types of Parameters](https://developer.rhino3d.com/guides/grasshopper/simple-parameters/#types-of-parameters)). You can extract a script input by choosing **Extract** from the right-click menu on the parameter: ![](https://developer.rhino3d.com/en/guides/scripting/scripting-gh-python/python3-component-extractparam-01.png) If you have a *Type Hint* set on a parameter, the extracted floating parameter will be of that data type: ![](https://developer.rhino3d.com/en/guides/scripting/scripting-gh-python/python3-component-extractparam-02.png) ## Script-Mode There are two ways you can create a Python script in Grasshopper. The first and the easiest is to write a Python script in the simplest form, and is called **Script-Mode**. For example, if we want to pass the sum of our two `x` and `y` inputs to the `a` output, we would create a script component with this script: ```python a = x + y ``` Here is a another simple example: ![](https://developer.rhino3d.com/en/guides/scripting/scripting-gh-python/python3-component-scriptmode-01.png) Notice that the `x` input parameter is magically defined and set before your script starts. Since this is a Python 3 Script Component, we can enjoy the new syntax features like [F-Strings](https://realpython.com/python-f-strings/) or [Walrus Operator](https://realpython.com/python-walrus-operator/). Check out [PyPI Packages]() for another example. See [SDK-Mode]() for creating Python Script Components that behave more like Grasshopper components. *SDK-Mode* and *Script-Mode* are both valid ways of writing scripts in Python script component. Choose the one you are comfortable with and is the most appropriate for the use case. ### Accessing Inputs As mentioned above the input parameters are magically defined and set before your script starts. You can reference the input values anywhere in your script. In the example above, the output `x` is already defined and set in the script scope, and its value can be used in the script: ```python a = f'Hello {x}' ``` You can also define functions in the script. These functions can access the defined parameters: ```python def Solve() -> float: return x + y a = Solve() ``` In more complicated cases, when accessing global variables, make sure to reference them as `global` to ensure there are no naming conflicts with other variables of the same name: ```python class Solver: def __init__(self): pass def Compute(self) -> float: global x global y return x + y s = Solver() a = s.Compute() ``` ### Settings Outputs As mentioned above, in *Script-Mode*, the output variables are magically defined in the script scope by the component. Assign the desired values to the outputs in your script and they will be set on the component outputs. ## SDK-Mode A typical Grasshopper component can: - Execute code *Before* component is asked to solve the inputs ([BeforeSolveInstance](https://developer.rhino3d.com/api/grasshopper/html/M_Grasshopper_Kernel_GH_Component_BeforeSolveInstance.htm)) - *Solve* the inputs and pass results to outputs ([SolveInstance](https://developer.rhino3d.com/api/grasshopper/html/M_Grasshopper_Kernel_GH_Component_SolveInstance.htm)) - Execute code *After* component is finishing solving the inputs ([AfterSolveInstance](https://developer.rhino3d.com/api/grasshopper/html/M_Grasshopper_Kernel_GH_Component_AfterSolveInstance.htm)) - Execute code to draw geometry wires on Rhino viewports ([DrawViewportWires](https://developer.rhino3d.com/api/grasshopper/html/M_Grasshopper_Kernel_GH_Component_DrawViewportWires.htm)) - Execute code to draw geometry meshes on Rhino viewports ([DrawViewportMeshes](https://developer.rhino3d.com/api/grasshopper/html/M_Grasshopper_Kernel_GH_Component_DrawViewportMeshes.htm)) The methods linked above are part of Grasshopper SDK for creating custom components. Every developer that creates a Grasshoper plugin is aware of these methods and might be using them to customize the component behaviour. In a Python script component, we can implement our scripts in a similar manner. That is why we are calling it the **SDK-Mode** as it provides similar functionality that is available in Grasshopper SDK. By default when you create a Python script component, the template script is in *Script-Mode* as this is how Python components before Rhino 8 have been working and we kept it the same in Rhino 8 and after. This mode does not support running code before and after the script or creating custom graphics on Rhino viewports, but it is great for any script that does not need these functionalities. See [Script-Mode](). This is how the default script looks like (actual script might not be identical): ```python """Grasshopper Script""" a = "Hello Python 3 in Grasshopper!" print(a) ``` To convert the default script into *SDK-Mode* click on the **Convert To GH_ScriptInstance** button on the editor dashboard: ![](https://developer.rhino3d.com/en/guides/scripting/scripting-gh-python/python3-component-sdkmode-01.png) The existing code will be placed inside an implementation of `GH_ScriptInstance` class. Notice the `RunScript` method is already added and has the component inputs in its signature: ![](https://developer.rhino3d.com/en/guides/scripting/scripting-gh-python/python3-component-sdkmode-02.png) `GH_ScriptInstance` is the base class that implements methods below, similar to Grasshopper components: - `BeforeRunScript`: Execute code *Before* component is asked to solve the inputs - `RunScript`: *Solve* the inputs and pass results to outputs - `AfterRunScript`: Execute code *After* component is finishing solving the inputs - `DrawViewportWires`: Execute code to draw geometry wires on Rhino viewports - `DrawViewportMeshes`: Execute code to draw geometry meshes on Rhino viewports This class provides base implementation for these methods except for `RunScript` that we must implement. In the example above, we subclass from `GH_ScriptInstance` and provide an empty implementation for `RunScript`. ### RunScript Signature The `RunScript` method signature is going to include all the component inputs and outputs by their name and data type (based on their *Type Hints*). Output values should be returned using the `return` statement: ![](https://developer.rhino3d.com/en/guides/scripting/scripting-gh-python/python3-component-sdkmode-03.png) You can write the logic of your component inside the `RunScript` block, take the input values, compute, and return the outputs. As with any other Grasshopper component, the `RunScript` method might be call multiple times based on the pairing of input data. #### Python 3 Type Hints Notice how the `x` and `y` inputs are *Hinted* with their type. This is a feature of Python 3 called [Type Hinting](https://realpython.com/lessons/type-hinting/) and must not be confused with [Type Hints]() in Grasshopper. Python 3 type hinting is only for static analysis of Python code (e.g. AutoCompletion, Diagnosis, etc.) and does not have any effect of the script execution. In the example above, the input `y` is hinted with `Rhino.Geometry.Plane` type and it helps the autocompletion determine the type of `y` and provide better autocompletion: ![](https://developer.rhino3d.com/en/guides/scripting/scripting-gh-python/python3-component-sdkmode-04.png) #### RunScript Returns A simple `return` statement can be used to return all the component outputs. In this example, `a` and `b` values are computed and returned from `RunScript`: ```python class MyComponent(Grasshopper.Kernel.GH_ScriptInstance): def RunScript(self, x: int, y: Rhino.Geometry.Plane): a = self.compute_a(x) b = self.compute_b(x) return a, b ``` It is also acceptable to return the values explicitly as a tuple: ```python return (a, b) ``` ### Changing RunScript Signature *Script* component is smart enough to update the RunScript signature when parameters on the component are changed. It is also capable of updating parameters on the component, when the RunScript signature is manually edited: Notice that input types will be used to apply an appropriate *Type Hint* to the parameter. The collection type of the input (`List`, or `DataTree`) is also used to apply the correct access kind to the associated input parameter. Also notice that even though you might use `list` as the type hint, it will be converted to `System.Collections.Generic.List[object]` automatically. This is discussed in detail in [Marshalling]() ### Before, After Solve Overrides You can easily add the `BeforeRunScript` and `AfterRunScript` methods to your `Script_Instance` implementation by: - Click on the **Add SolveInstance Overrides** button on the editor dashboard - Click on the **Add SolveInstance Overrides** menu inside the **Grasshopper** menu on the editor - Typing them yourself ![](https://developer.rhino3d.com/en/guides/scripting/scripting-gh-python/python3-component-sdkmode-05.png) These two methods will be added to the class implementation: ![](https://developer.rhino3d.com/en/guides/scripting/scripting-gh-python/python3-component-sdkmode-06.png) A good example of using these two methods would be to setup instance variables on the class instance during `BeforeRunScript` and clean them up after the execution during `AfterRunScript`. The component is not allowed to make changes to the output parameters inside these methods. Each one of these methods is executed only once, per one full execution of this component. We can put a few print statements in these methods, and check the order of execution: ![](https://developer.rhino3d.com/en/guides/scripting/scripting-gh-python/python3-component-sdkmode-07.png) There are two range components included in this example to provide inputs to the script component. Each range component outputs 3 items, and their associated input parameter on the script component has a `float` *Type Hint* assigned to it. This means the `RunScript` method is going to be executed 3 times for 3 pairs of `x` and `y`. Notice that the text *Before Solve* is printed on the same output item as *Solve #0* which is the first iteration of solving inputs. This is because `BeforeRunScript` runs before the script component is allowed to set values on its output parameters and therefore any output printed to the console are going to be captured by the first iteration of `RunScript` that runs right after. All other iterations of `RunScript` will continue after the first. The `AfterRunScript` is executed after all the iterations of `RunScript` have completed execution. Notice that the text *After Solve* is captured and appended to the last message on the `out` parameter which belongs to the last iteration of `RunScript` that ran right before. ### Preview Overrides *Preview Overrides* are continuously called as you are interacting with the Rhino viewports. See [Draw Calls]() for more information on how these overrides work. You can easily add the `DrawViewportWires` and `DrawViewportMeshes` methods to your `Script_Instance` implementation by: - Click on the **Add Preview Overrides** button on the editor dashboard - Click on the **Add Preview Overrides** menu inside the **Grasshopper** menu on the editor - Typing them yourself ![](https://developer.rhino3d.com/en/guides/scripting/scripting-gh-python/python3-component-sdkmode-08.png) These two methods will be added to the class implementation: ![](https://developer.rhino3d.com/en/guides/scripting/scripting-gh-python/python3-component-sdkmode-09.png) `DrawViewportWires` is called first and here you can draw points and curves. `DrawViewportMeshes` is called later and this is where you can draw transparent shapes, such as meshes. Notice there is also a `ClippingBox` property implementation that is added as well. The default value is `BoundingBox.Empty` but you should change that to a larger bounding box that bounds any custom geometry being drawn by your component. Here is an example of a component that draws a 2D filled rectangle at the top-left corner of Rhino viewport: ![](https://developer.rhino3d.com/en/guides/scripting/scripting-gh-python/python3-component-sdkmode-10.png) An argument of type [IGH_PreviewArgs](https://developer.rhino3d.com/api/grasshopper/html/T_Grasshopper_Kernel_GH_PreviewArgs.htm) is passed to these preview override methods. As you can see in the example above, you can access the `args.Display` property which is a Rhino [DisplayPipeline](https://developer.rhino3d.com/api/rhinocommon/rhino.display.displaypipeline) instance and has a lot of helpful draw methods. Notice that by adding the `args: Grasshopper.Kernel.IGH_PreviewArgs` type hint, we get better autocompletion results on the `args` variable: ![](https://developer.rhino3d.com/en/guides/scripting/scripting-gh-python/python3-component-sdkmode-11.png) Rhino 8 and above primarily use a UI framework called [Eto](https://github.com/picoe/Eto). However Grasshopper 1 predates this adoption and uses `System.Drawing` framework for its graphical interface. They both have similar data structures (e.g. `Rectangle`) but it is important to know when working with Grasshopper preview overrides to use `System.Drawing` data types. ### Draw Calls It is very important to know that **Preview Overrides** are fundamentally different from *Solve Overrides* (`BeforeRunScript`, `RunScript`, `AfterRunScript`) in a sense that they are executed outside of Grasshopper solution and are designed to work in tandem with the solve methods. When you interact with a component, Grasshopper triggers a new solution on the definition. When solution is completed, Grasshopper is ready to draw previews on all the geometries generated by the components on the canvas. Any interaction with Rhino viewports results in Grasshopper receiving a request from Rhino to draw its previews in the viewports. These draw requests happen anytime you interact with Rhino viewports and a Grasshopper definition is open. This is exactly where the *Preview Overrides* come into play. Their main purpose is to peform custom drawings in Rhino viewports based on the results of the computation done by *Solve Overrides*. Contradictory to the solve methods, the *Preview Overrides* are continuously called as you are interacting with the Rhino viewports. ## Marshalling ### Marshalling Guids The ubiquitous `rhinoscriptsyntax` modules (usually imported as `rs`) references Rhino document elements by their unique identifier (Guid). Python Script component has a few features to make life easier when dealing with `rhinoscriptsyntax` functions: - **Output** parameters, by default, automatically convert unique identifiers to their associated Rhino document elements. This capability can be toggled from the component context menu item **Avoid Marshalling Output Guids**: ![](https://developer.rhino3d.com/en/guides/scripting/scripting-gh-python/python3-component-marsh01.png) - **Inputs** parameter with *Type Hint* of **ghdoc Object** automatically marshall input Rhino document elements to their unique identifier so they can be passed to `rhinoscriptsyntax` functions. If we pass the output *Sphere* from the example above, into anther script component with input of `sphere` that has *Type Hint* of *ghdoc Object*, the actual value contained in `sphere` would be the unique identifier. We can easily pass this identifier to `rs.RebuildSurface` to rebuild the sphere: ![](https://developer.rhino3d.com/en/guides/scripting/scripting-gh-python/python3-component-marsh02.png) When marshalling input elements to their unique identifers, the elements are stored in a proxy headless document. This document is then assigned to `scriptcontext.doc` for the duration of the script execution (alongside setting `scriptcontext.id == 2` to denote Grasshopper context). All `rhinoscriptsyntax` functions use this proxy document for their operations, therefore everything runs smoothly. ### Marshalling Data Types (CPython) Current implementation of Python 3 in Rhino uses [CPython](https://www.python.org), and its data types are very different from dotnet types. For example a dotnet `List` has a `Count` property that reports the length of the list. However `list` type in Python 3 does not have such property and we normally use the built-in function `len()` to measure length of an iterable. #### Inputs When working with Python 3 scripts in [Script-Mode](), dotnet data types are automatically marshalled into Python 3 data types. So an input `List` will be converted into a python `list` containing only integers. This behaviour can be toggled using **Avoid Marshalling Inputs** item in component [Advanced Options](). In the example below, input parameter `x` has *Access* of *List*. The top component is defaulting to convert a dotnet input `List<>` to a python `list`, and the bottom component has *Avoid Marshalling Inputs* checked and is skipping the conversion. ![](https://developer.rhino3d.com/en/guides/scripting/scripting-gh-python/python3-component-marsh03.png) When working in [SDK-Mode](), any input parameter with *Access* of *List*, will be defined as a dotnet `List<>` in the *RunScript* syntax, and the *Avoid Marshalling Inputs* is checked by default. In the example below, input parameter `x` has an *Access* of *Item* and *Type Hint* of integer. Notice the parameter `x` in *RunScript* signature is hinted as `x: System.Collections.Generic.List`: ```python class MyComponent(Grasshopper.Kernel.GH_ScriptInstance): def RunScript(self, x: System.Collections.Generic.List): ... ``` #### Outputs Output parameters follow the same logic. Be default, an output of Python 3 `list` is converted to a dotnet `List` so other Grasshopper components can use the data. This behaviour can be toggled using **Avoid Marshalling Outputs** item in component advanced context menu. When multiple Python 3 components are working together, you have the option of avoiding the input and output marshalling since the data is flowing directly from one Python 3 component to another Python 3 component without involvement from any other components in between. In these cases, there is no need to convert a python `list` to a dotnet `List<>` and back to a python `list`. Just **Avoid Marshalling Outputs** on the upstream component and the exact same data will be passed downstream with no conversions. Notice that the Grasshopper wires between components are single lines as they now carry a single python `list` instance: ![](https://developer.rhino3d.com/en/guides/scripting/scripting-gh-python/python3-component-marsh04.png) ## Debugging Scripts You can debug your Python scripts in the script editor. During debug, we can execute the script line by line or pause the execution at certain lines called **Breakpoints** and inspect the values of global and local variables. Move your mouse cursor to the left side of any script line and click to add a **Breakpoint**: ![](https://developer.rhino3d.com/en/guides/scripting/scripting-gh-python/python3-component-debugging-01.png) The **Breakpoints** tray at the bottom will show all the breakpoints, and will provide buttons to *Enabled/Disable* or *Clear* them: ![](https://developer.rhino3d.com/en/guides/scripting/scripting-gh-python/python3-component-debugging-02.png) Use the **Toggle** button to activate or deactivate the breakpoints. Deactivated breakpoints will show up as gray dots in the editor: ![](https://developer.rhino3d.com/en/guides/scripting/scripting-gh-python/python3-component-debugging-03.png) When you add breakpoints, the editor makes a few UI changes and provides a few more utilities for debugging: - The **Run** button will change to **Debug** - **Variables**, **Watch**, and **Call Stack** trays will be added to the bottom tray bar Now click on the green **Debug** button on the editor dashboard. The editor will run the script and: - Stops at breakpoints - Highlights the breakpoint line in orange and shows an arrow on the left side of the line - Highlights status bar in orange to show we are debugging a script - Activates the debug control buttons on the editor dashboard - Opens the **Variables** tray at the bottom to show global and local variables ![](https://developer.rhino3d.com/en/guides/scripting/scripting-gh-python/python3-component-debugging-04.png) We can control the execution of script using the debug control buttons on the editor dashboard: ![](https://developer.rhino3d.com/en/guides/scripting/scripting-gh-python/python3-component-debugging-05.png) From left to right, they are: - **Continue:** continues running the script until it stops on another breakpoint - **Step Over:** executes current line and moves on the next line - **Step In:** if the current line includes a function call, this will step into the lines defining the function code - **Step Out:** if previously stepped into a function code, this will continue executing the function code until control is returned from the function to the calling code and will stop there - **Stop:** stops debugging the script and does not continue executing the rest Click on **Continue** to see the execution move to the next line: Notice that the **Variables** panel now shows new values for **x** and **y**. The panel header also shows **Run Script (2 of 11)** on the top-right meaning this is the second time Grasshopper is executing this component with a pair of **x** and **y** inputs: ![](https://developer.rhino3d.com/en/guides/scripting/scripting-gh-python/python3-component-debugging-06.png) Progressively clicking on **Continue** will continue executing the script and modifying the variables. At each stop, the **Variables** tray shows the current values of global and local variables. At any point during debug, the **Stop** button stops debugging. The script component will show an error marking with the message **Debug Stopped**: ![](https://developer.rhino3d.com/en/guides/scripting/scripting-gh-python/python3-component-debugging-07.png) Once the debug stops, the editor UI changes back to normal, and the **Variables** tray will show the last state of the variables. The tray will keep these data until another session of debugging is started. ### Variables Tray *Variables* tray is a great tool to inspect the current values of all the global and local variables in our script. Variables data is shown in a table with 3 columns: **Name**, **Value**, **Type**. For each variable you can see the current value and the type of data it is holding. A red marker will highlight the variables that changed during debug: ![](https://developer.rhino3d.com/en/guides/scripting/scripting-gh-python/python3-component-debugging-08.png) For more complicated data types with fields and properties, you can expand the variable to see current values of its members: ![](https://developer.rhino3d.com/en/guides/scripting/scripting-gh-python/python3-component-debugging-09.png) If a value is a collection of other values, you can expand the variable to see each item individually. The *Name* column shows the item index like `[0]`: ![](https://developer.rhino3d.com/en/guides/scripting/scripting-gh-python/python3-component-debugging-10.png) ### Watch Tray *Watch* tray is very similar to the variables tray. The primary difference is that *Watch* tray only shows the variables that you have specifically added to watch. Use the **Add Expression** button on the tray toolbar to add a new variable to watch. Hit Enter on the added *Expression* item to edit and type the variable name: ![](https://developer.rhino3d.com/en/guides/scripting/scripting-gh-python/python3-component-debugging-11.png) *Watch* tray will show a green checkmark when it can extract the variable value during debug. A yellow warning icon is shown when the variable is not in scope: ![](https://developer.rhino3d.com/en/guides/scripting/scripting-gh-python/python3-component-debugging-12.png) ### Call Stack Tray *Call Stack* tray shows the call stack frames. This loosly translates to functions calls in our script. The item at the top is the last function that the script is executing, and the list goes on to show other functions that called the current function In the example below, script execution started by running `RunScript`. Then the script called the `Sum` method and that is where we are paused during debug right now. *Call Stack* tray shows this function at the top of the list, with `RunScript` following right after. *Variable* and *Watch* trays also show the values of variables in the current call frame: ![](https://developer.rhino3d.com/en/guides/scripting/scripting-gh-python/python3-component-debugging-13.png) You can click on other stack frames, and switch to *Variables* or *Watch* tray to inspect the values in their scope: ![](https://developer.rhino3d.com/en/guides/scripting/scripting-gh-python/python3-component-debugging-14.png) *Call Stack* tray shows stack frames for different threads in your script independently. ## PyPI Packages Python 3 script can benefit from third-party packages that are published on [PyPI](https://pypi.org) package server. You can use the **Install Package** button to install any of these packages and use in your script: ![](https://developer.rhino3d.com/en/guides/scripting/scripting-gh-python/python3-component-packages-01.png) The Install Package dialog, shows a couple of example of how you can specify the package name and version requirements. The **Add Package Reference to Script** option, when check (default), adds a package reference to the script text. In this manner, the script always knows which packages it needs even when you send this script to others and they do not have the required packages installed. ```python # requirements: numpy import numpy as np # numpy array with random values a = list(np.random.rand(7)) ``` ### Module Search Paths Script editor has global configurations for Python 3 and 2 search paths. See [Editor Configs: Python Paths](/guides/scripting/editor-configs/#python-paths) for detailed information. You can also use the `# env` directive as shown below to specifically add a search path to your script: ```python #! python 3 # env: C:\Path\To\MyPythonModules\ import my_module ``` Installing multiple versions of the same package can get very complicated. Script Editor supports `# venv:` directive that attempts to simplify dependency trees for different scripts. Take a look at [Python Package Environments](/guides/scripting/advanced-pyvenvs) for detailed information on this topic. ## NuGet Packages Python script can benefit from third-party packages that are published on [NuGet](https://www.nuget.org) package server. You can use the **Install Package** dialog and change the **Package Source** option to **NuGet**: ![](https://developer.rhino3d.com/en/guides/scripting/scripting-gh-python/python3-component-packages-02.png) The Install Package dialog, shows a couple of example of how you can specify the package name and version requirements. The **Add Package Reference to Script** option, when check (default), adds a package reference to the script text. In this manner, the script always knows which packages it needs even when you send this script to others and they do not have the required packages installed. Notice the `#r "nuget: RestSharp, 110.2.0"` line in the example script below. The format follows the package reference for script on NuGet website: ![](https://developer.rhino3d.com/en/guides/scripting/scripting-gh-python/python3-component-packages-03.png) ```python #r "nuget: RestSharp, 110.2.0" import RestSharp as RS import RestSharp.Authenticators as RSA client = RS.RestClient("https://httpbin.org") request = RS.RestRequest("get") response = client.Execute(request) a = response.Content; ``` ## Assembly References Python scripts can also directly reference dotnet assembly files. You can use the **Install Package** dialog and change the **Package Source** option to **DLL Reference**: ![](https://developer.rhino3d.com/en/guides/scripting/scripting-gh-python/python3-component-packages-04.png) If the assembly is already loaded in Rhino, you can reference it by just typing the name of the assembly. Make sure the extension is included in the assembly name (e.g. `.dll`) A package reference like example below is optionally added to your script: ```python #r "System.Text.Json.dll" from System.Text.Json import JsonElement ``` As the *Install Package* dialog examples show, you can also provide a relative or absolute path to the assembly: ```python #r "https://developer.rhino3d.com/path/to/my/assemblies/MySharedAssembly.dll" from MySharedAssembly import MyData ``` ## Customizing Editor Script editor used in Python script component, is an embedded variant of the main script editor in Rhino that is launched from `ScriptEditor` command. The component script editor, has a *Grasshopper* menu and few other Grasshopper-specific buttons. We have already discussed the *SDK-Mode* related buttons in the editor dashboard. Here is a description of other editor options that are useful in Grasshopper: ### Close On Save By default, when *Saving* the script in Python script component, the editor stays open. It saves the script and triggers a solution on the Grasshopper definition with the newly updated script. This behaviour can be changed using the **Grasshopper -> Toggle Close Editor On Save** menu. When enabled, choosing *File -> Save* or *Ctrl + S* will save the script and close the editor (This is the default behaviour of the legacy script editor in GHPython component). ### Layout Options Script editor used in Python script component, has a series of toggle menus to change the layout of the editor and make it more compact. These options can be accessed from **Window** menu in the editor, and can be used to dedicated more screen space to scripting area, and also visually differentiate the Grasshopper editor from the main editor in Rhino. They are also accessible from the **Tools -> Options** menu in the editor. Hover the mouse over the question mark icons to see more information on each option: ![](https://developer.rhino3d.com/en/guides/scripting/scripting-gh-python/python3-component-layoutoptions.png) #### Toggle Dashboard You can completely hide the editor dashboard and open up more space for script: ![](https://developer.rhino3d.com/en/guides/scripting/scripting-gh-python/python3-component-toggle-dashboard.png) #### Toggle Compact Dashboard When Dashboard is visible, you can save some space by making it more compact: ![](https://developer.rhino3d.com/en/guides/scripting/scripting-gh-python/python3-component-toggle-compact-dashboard.png) #### Toggle Compact Script Tabs By default, Grasshopper editor does not show the script tabs, unless debug steps into a source file other than the main script. Toggle this option if you want the tabs to be always visible: ![](https://developer.rhino3d.com/en/guides/scripting/scripting-gh-python/python3-component-toggle-compact-scripttabs.png) #### Toggle Compact Browser By default Browser tabs are NOT shown on the left side of the editor in Grasshopper. The tab selector buttons are shown on the status bar to save some space. Toggle this option if you want to see the browser tabs on the left side: ![](https://developer.rhino3d.com/en/guides/scripting/scripting-gh-python/python3-component-toggle-compact-browser.png) #### Toggle Compact Console By default Console tabs are shown on the bottom edge of the editor in Grasshopper. Toggle this option if you want to see the tab selector buttons on the status bar to save some space: ![](https://developer.rhino3d.com/en/guides/scripting/scripting-gh-python/python3-component-toggle-compact-console.png) ## Publishing Scripts If you are planning to publish your script components in a Grasshopper plugin, a few considerations are important. Right-Click on the script component and set appropriate values for **Tooltip**. This description is used for publishing the script and is shown on the published component. ![](https://developer.rhino3d.com/en/guides/scripting/scripting-gh-python/python3-component-publishing-01.png) Right-Click on all input and output parameters and set a **Name** (Human-readable) and **Tooltip** for each parameter. The human-readable name is shown when **Display -> Draw Full Names** are enabled in Grasshopper. Setting a human-readable name and description helps understanding what inputs the component requires, and what outputs it provides and generally makes it easier to work with your published components. ![](https://developer.rhino3d.com/en/guides/scripting/scripting-gh-python/python3-component-publishing-02.png) Check out [Creating Rhino and Grasshopper Plugins](/guides/scripting/projects-create) on how to publish your script components in a Grasshopper plugin. ## Template Scripts There are a few template scripts available in the **Templates** panel in the editor. You can Double-Click on any of these templates to replace the contents of your script with the template. This is a good way to start slightly more complicated scripts: ![](https://developer.rhino3d.com/en/guides/scripting/scripting-gh-python/python3-component-templates-01.png) ### User Objects As Templates Another great way to create template scripts is to setup one script component with the desired inputs, outputs, and template script, and then save that as a Grasshopper **User Object**. You can set extra metadata on the component and customize its icon. Every time you would place an instance of this *User Object*, you are effectively creating a new script component instance with the template script and parameters. ### Resetting Icon If you have a script component that has an overriden, incorrect, or low-resolution icon, you can reset the icon back to the default for the scripting language using the **Reset Icon** menu button in the *Advanced* context menu (Shift + Right-Click). ## Shared Scripts *Python Script Component* is most commonly used with a Python script that is embedded inside the component. However you can share a single script between multiple *Script* components. ### Input Script You can create a Python script (SDK-Mode is not yet supported for shared scripts) in a Grasshopper panel, and pass that as an input to multiple *Script* components. Script components have a special `script` input parameter that can be activated from the advanced context menu. Shift + Right-Click on the component and choose **Script Input Parameter ("script")** to toggle this input: ![](https://developer.rhino3d.com/en/guides/scripting/scripting-gh-python/python3-component-sharedscripts-01.png) ### Language Specifier Directive Notice that the scripts starts with `#! python 3`. This is called a language specifier directive. Its purpose is to embed the expected language in the script code itself as a comment and a known pattern. Since scripts that are stored in this way do not have a file extension the language specifier is necessary for the script component to determine the language it should use to run the script. Alternatively you can Right-Click on the `script` parameter and choose a language from the menu, but that means all the input scripts must be of the chosen language. Use `#! python 2` to specify IronPython as language. Also note that the component icon changes to a generic script icon. The reason is that a script component with an `script` input can executed any of the supported languages. Notice the language specifer for the second script is `// #! csharp`: ![](https://developer.rhino3d.com/en/guides/scripting/scripting-gh-python/python3-component-sharedscripts-02.png) ### Output Script Editing a script in a Grasshopper panel is not very convenient. Script components have a special `script` output parameter that can be activated from the advanced context menu. Shift + Right-Click on the component and choose **Script Output Parameter ("script")** to toggle this output: ![](https://developer.rhino3d.com/en/guides/scripting/scripting-gh-python/python3-component-sharedscripts-03.png) In this manner, we can use the first component to create and use our script, and pass the same script to other components to ensure they all run the same script. And as shown above, you can pass scripts of other languages to the `script` input as well: ![](https://developer.rhino3d.com/en/guides/scripting/scripting-gh-python/python3-component-sharedscripts-04.png) ## Output Previews Output parameters have their own individual *Preview* control. This option is on by default and Grasshopper renders previews for geometry values in output parameters: ![](https://developer.rhino3d.com/en/guides/scripting/scripting-gh-python/python3-component-outputpreview-01.png) You can toggle this option off for any of the output parameters and hide the preview, using the **Preview** menu in the component context menu. Notice that the box preview does not show up while sphere is still previewed: ![](https://developer.rhino3d.com/en/guides/scripting/scripting-gh-python/python3-component-outputpreview-02.png) ## Exporting Script You can save the script that is embedded in a Python script component, using the **Export Script** menu item from component context menu. When *Save Dialog* opens, choose a file name and location where you would like to save the script, and hit save. ## Script Cache Python script component compiles and caches the script, so it can execute faster when the script is not changed. Normally the cache is expired automatically when you make changes to the script or any of the parameters. However, sometimes it is desired to expire the cache manually to ensure component is using a fresh build. To expire the compile cache, choose **Discard Cache** from component context menu. -------------------------------------------------------------------------------- # MIT License Source: https://developer.rhino3d.com/en/license/ Rhino developer tools are royalty free and include support. Copyright (c) 1993- Robert McNeel & Associates. All Rights Reserved. Permission is hereby granted, free of charge, to any person obtaining a copy of Rhino SDKs and APIs, including openNURBS, rhino3dm, RhinoCommon, the Grasshopper API, and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- # Oscar Quick Start Source: https://developer.rhino3d.com/en/guides/general/oscar-quick-start/ This guide shows you how to pull a good shot of espresso from Oscar, McNeel espresso machine. > *No matter. Try again. Fail again. Fail better. -Beckett* ## Steps to Success 1. Grind enough coffee to just about fill two "wedges" in the doser. 1. Get some into the basket, maybe half, and then optionally lightly tap the PortaFilter on the table once or twice to "declump" a bit and settle the grounds: ![Clumpy and settled and less clumpy](https://developer.rhino3d.com/images/oscar_quick_start_02.png) 1. Fill to about level with, or a bit below, the rim. If you’ve tapped & settled, you’ll want the level lower before you start to tamp; if not, even with the rim is OK, as there will be more compression on tamping: 1. Tamp...firmly but, especially, *levelly*. The tradition says ~30 lbs. of pressure, but the geeks will tell you consistency is more important than the exact pressure. The sweet spot with our setup seems to be to get the tamper shoulder at, or just slightly below, the rim of the basket. 1. If in doubt about the amount, lock the PortaFilter in and then take it out - if there is an imprint from the shower screen screw, there is too much coffee. ![The Imprint](https://developer.rhino3d.com/images/oscar_quick_start_05.png) 1. Scratch off a mm or two and polish with the tamper. 1. When you’re ready to go, lock in the PortaFilter and push the left hand button to start the pump - it will ramp up and then be steady - push again to stop the pump and have your shot. Ideally, on starting the pump, there will be a few seconds pause before a thin trickle starts and you’ll get a double shot in 20-30 seconds. 1. Unlock the PortaFilter and knock out the puck, rinse the PortaFilter. Use the brush to get grounds off of the shower screen and group gasket, then give it a last burst of clear water from the pump to rinse. Put the PortaFilter back so it stays hot. 1. Check the water level and fill it with the pitcher from the filtered water at the sink, if needed. Oscar misbehaves if it runs out of water. ## Extra Credit - Heat Exchange machines like Oscar tend to super-heat the water that is waiting to go into the group when you start the shot well above boiling due to pressure in the boiler. Since the ideal temperature is somewhat below boiling, it pays to run a "cooling flush" of 3-5 seconds with no PortaFilter in place if the machine has not pulled a shot in several minutes (pretty much always the case here) You’ll see the initial burst of steam, then that will diminish. Start your shot within the next 30 seconds or so. Needless to say, this uses water up faster than just making a shot, so keep an eye on the tank and refill if it gets low. - If your shot chokes, and only ever puts out a few drops, or nothing, rather than a few drop followed by a thin stream, stop and "do over." That is usually a sign of over-packing/over tamping. If it gushes a light colored faster stream, you may have under-dosed or under tamped OR you got the dreaded "channeling" where the water under pressure finds one or two paths through the puck that have the least resistance - extracting way too much locally from the grounds as it goes and ignoring the rest. This can happen if the distribution of the grounds is uneven, the tamp is not level (one side thinner) or a crack in the puck or between the puck and the sides of the basket. If you’re pretty sure of the dose then, probably, you got some form of channeling. It’s *tricky*. The puck prep seems to be the crux of getting a good shot, there is not a lot else we can control on the Oscar. - When you’re done, if the puck is sloppy and muddy, you could probably dose a wee bit more; if you see the screw mark, just barely, on the top of the puck you’re at the upper limit - (the puck will expand some and may push up against the screen when wet). It’s a pretty narrow window as far as I can tell, between too little and too much - I find that if I just barely see a mark from the screw and the pull was 20-30 seconds I’ve probably got a good coffee but ideally you get a good coffee without that print. - It’s hardly ever perfect - too many variables - but it’s almost always pretty good. - Double extra 'spro-'bro points if, once in a while, when you’re done, you pop the blank PortaFilter in and run the pump for five to ten seconds to "backflush" the machine - it clears Oscar’s sinuses and helps keep gunk from building up. I do this more systematically most Fridays, but it is good to do it occasionally in between as well. ## Friends of Oscar Who to talk to about Oscar: - [Pascal Golay](/authors/pascal) - [Dan Rigdon-Bel](/authors/dan) - [Brian Gillespie](/authors/brian) - [Will Pearson](/authors/will) -------------------------------------------------------------------------------- # Publishing Rhino/Grasshopper Script Plugins Source: https://developer.rhino3d.com/en/guides/scripting/projects-publish/ Provides information on publishing plugin projects in Script Editor [Creating Rhino Projects](/guides/scripting/projects-create) for information on creating projects in Script Editor. ## Build Plugins from Script Editor You can build a project directly from Script Editor: - Open Script Editor - Open Project (`File > Open Project` menu) - Choose **Publish Project** (`File > Publish Project` or *Publish* button in editor dashboard) ![](https://developer.rhino3d.com/en/guides/scripting/projects-publish/project-build-editor.png) Project information fields are discussed in detail in [Create Project](/guides/scripting/projects-create). Here we focus on choosing a *Build Target* and *Build Path*. - Choose a **Build Target**: This is the minimum version of Rhino required to run your plugin. The available versions are queried from [Rhino NuGet packages](https://www.nuget.org/profiles/McNeel). You can see `macOS` and `Windows` specific targets as well. ![](https://developer.rhino3d.com/en/guides/scripting/projects-publish/project-build-editor-buildtarget.png) - Choose a **Build Path**: This is where all generated assemblies and files are placed. Depending on the *Build Target* a subpath is added to this build path to avoid conflicts (e.g `build/rh8/`) ![](https://developer.rhino3d.com/en/guides/scripting/projects-publish/project-build-editor-buildpath.png) - Choose **Build Package** to build the project: On a successful build, status tray will show success message in green: ![](https://developer.rhino3d.com/en/guides/scripting/projects-publish/project-build-success.png) ## Build Plugins from Terminal To build a project in terminal, use the `rhinocode` command line utility shipped with Rhino. See [RhinoCode Command Line Interface](/guides/scripting/advanced-cli) for more information on setting up the build environment. - Open Terminal - Use `rhinocode` command line utility to build the project: ![](https://developer.rhino3d.com/en/guides/scripting/projects-publish/project-build-terminal.png) ```text $ rhinocode project build ~/MyProject.rhproj 0% - Preparing project 10% - Preparing build path 20% - Preparing plugin assembly 50% - Preparing grasshopper plugin assembly 60% - Adding shared resources 90% - Creating yak package 100% - Complete ``` See [RhinoCode: Build a Project](/guides/scripting/advanced-cli#build-a-project) for more information. ## Build Artifacts Once project is built, the target path will contain all the generated artifacts: ![](https://developer.rhino3d.com/en/guides/scripting/projects-publish/project-build-artifacts.png) A Yak package is generated that contains both Rhino and Grasshopper plugins. See [Pushing a Package to the Server](/guides/yak/pushing-a-package-to-the-server) on how to publish `.yak` files to package server ## Project Solution On successful build, a Visual Studio solution is automatically generated that contains the source code for both Rhino and Grasshopper plugins. This solution is created to allow full customization of Rhino and Grasshopper plugins. You can add extra commands and components and make any other modifications. Use [Visual Studio](https://visualstudio.microsoft.com/) or [Visual Studio Code](https://code.visualstudio.com/) to edit the project. Use `dotnet` command line utility to build the project from command line: ![](https://developer.rhino3d.com/en/guides/scripting/projects-publish/project-build-terminal-dotnet.png) Once the project is built, compiled Rhino and Grasshopper plugins are under `bin/` directory of their respective projects: ![](https://developer.rhino3d.com/en/guides/scripting/projects-publish/project-build-artifacts-dotnet.png) -------------------------------------------------------------------------------- # Python Package Environments Source: https://developer.rhino3d.com/en/guides/scripting/advanced-pyvenvs/ Provides information on managing multiple python package environments ## Package Conflicts Sometimes scripts need different versions of the same package either intentionally or through nested dependencies. Imagine this scenario: - File *script_a.py* requires *pkg_a* version 1 - File *script_b.py* requires *pkg_b* version 2 which is dependent on *pkg_a* version 2 (newer than pkg_a of *script_a*) - On a clean Rhino, running *script_a* installs and loads *pkg_a* v1 - On a clean Rhino, running *script_b* installs and loads *pkg_b* + *pkg_a* v2 Here is a few ways that package version conflicts can happen: ### A) PIP Install Failure Following the scenario above, if *script_a* is executed successfully in a Rhino session, running *script_b* will probably fail when installing *pkg_b*. Script Editor is using *pip* to install packages. Since, by default, all the packages for all the various scripts are installed in the same directory (as python normally does), this creates a conflict when pip is trying to remove *pkg_a* v1 to install *pkg_a* v2. Remember Rhino has already executed *script_a*, therefore *pkg_a* libraries are loaded in memory and at least on Windows, these library files are locked and can not be deleted while Rhino is running. Any pip install error will mark the environment as *Invalid* and will cleanup after Rhino is restarted. ### B) Runtime Conflicts Following the scenario above, even if the two version of *pkg_a* can be installed at the same time, *pkg_a* is already loaded in Python 3 runtime and loading another version of this package (that is in the same directory) might lead to runtime errors and unexpected behaviour. ## Virtual Environments Tools like *pipenv* and other environment managers for Python, normally solve this problem by creating multiple **Virtual Environment**s. Each virtual environment has a dedicated folder with its own `site-packages` (where pip installs packages by default). Python core binaries are then linked into this folder to create a fully functional Python environment. Following the scenario above: - File *script_a.py* requires *pkg_a* version 1 - installed in *venv_a* - File *script_b.py* requires *pkg_a* version 2 through *pkg_b* version 2 - installed in *venv_b* So each script is created inside a unique virtual environment and are run independently of each other: ![](https://developer.rhino3d.com/en/guides/scripting/advanced-pyvenvs/venvs-python.jpg) ### "Virtual" Environments in Rhino Virtual Environments (vnev) in Rhino are implemented differently. The main reason is that in normal venvs, separate python processes run *script_a* and *script_b* so there is no runtime conflict between these two processes. They each have their own memory and python interpretter state. In Rhino, however, both *script_a* and *script_b* are executed inside the same process, same memory, and same python interpretter state. So we can not really have true virtual environments unless you are running separate Rhino processes: ![](https://developer.rhino3d.com/en/guides/scripting/advanced-pyvenvs/venvs-rhino-conflict.jpg) Virtual Environments in Rhino are usually helpful in avoiding to reinstall packages. If you are running a script with conflicting packages, pip has to uninstall the existing to install new versions. This means that your scripts keep uninstalling and reinstalling packages and will eventually overwrite each other, corrupting the installed environment. `venv` tag helps keeping these install packages separate and is great when running scripts in separate Rhino processes: ![](https://developer.rhino3d.com/en/guides/scripting/advanced-pyvenvs/venvs-rhino-noconflict.jpg) Note that if you are running two scripts with two different versions of torch library in the same Rhino instance you might/will run into runtime conflicts as described above. ### Default Environment By default, all python packages are installed to `site-envs/default-` directory inside Python 3 runtime directory. This path is added to `sys.path` and is the first path to be searched when importing installed modules: ```text /Users/*/.rhinocode/py39-rh8/site-envs/default-A6YRvqqN /Users/*/.rhinocode/py39-rh8/site-rhinoghpython /Users/*/.rhinocode/py39-rh8/site-rhinopython /Users/*/.rhinocode/py39-rh8/site-interop /Users/*/.rhinocode/py39-rh8/lib/python39.zip /Users/*/.rhinocode/py39-rh8/lib/python3.9 ``` ### Custom Environments You can configure your script to install required packages in a custom environment using `# venv: ` tag. When running this example script, Python 3 will create new folder `site-envs/my-pytorch-tools-` to install pytorch v2.4.1 in that environment. `` is automatically assigned to the environment to avoid conflicts: ```python #! python 3 # venv: my-pytorch-tools # r: torch==2.4.1 import pytorch ``` This will ensure that the requirements for your package do not override any existing installed packages for other scripts. If you are fixing the version of a package in your script or use packages that might cause conflicts, it is good practice to specify a unique name for this environment to avoid conflicts. ### Invalid Environments When a pip install error occurs, the target environment is marked as *Invalid*. Therefore it is necesary to restart Rhino so editor can cleanup the environment and start fresh. ### Site-Packages As mentioned above, by default, all python packages are installed to `site-envs/default-` directory. Sometimes it is necessary to install packages under the default python `site-packages` folder. Normally this folder is reserved for packages that Python 3 runtime inside of Rhino requires to work e.g. `pip` itself. There is a special environment id `site-packages`. This id can be used in your scripts to force install packages into `site-packages` directory. ```python #! python 3 # venv: site-packages ``` In case any install errors occured, please run *Tools > Advanced > Reset Python 3 (CPython) Runtime* to reset the complete runtime and install a fresh deployment of Python 3 with clean `site-packages` and `site-envs` directories. ### Python 3 Shell When using Python 3 shell (from *Tools > Advanced > Open Python 3 Shell*), you might want to manually install packages using *pip*. As the notes printed on the shell describe, you would need to specify the `--target` option and point `pip` to a specific environment under `site-envs` that you want to install the packages to. If `--target` is not specified, `pip` automatically installs packages under `site-packages`. ![](https://developer.rhino3d.com/en/guides/scripting/advanced-pyvenvs/python-shell-target.png) -------------------------------------------------------------------------------- # Python Path Files Source: https://developer.rhino3d.com/en/guides/scripting/advanced-pthfiles/ Provides information on system and user python search path files ## Path Files Path files are simple ascii files with one search path per line. These files are similar to [Python 3 path configuration files](https://docs.python.org/3/library/site.html) except that they can only contain search paths and no code inside these files will be parsed or executed. First path in the file would be the first path to be search for a module, so the order is important. Script editor stores search paths configured in the options window, in these files: - `.rhinocode/python-2.pth` - `.rhinocode/python-3.pth` You can edit these files manually and Rhino will use the paths on next launch. By default, Rhino adds all the paths specified in python path files (*.pth) under these locations to Python 2 and 3 search paths (`sys.path`): On Windows: - `%PROGRAMDATA%\McNeel\Rhinoceros\.0\scripts` (if exists) - `%APPDATA%\McNeel\Rhinoceros\.0\scripts` - `.rhinocode` On macOS: - `/Users/Shared/McNeel/Rhinoceros/.0/scripts` (if exists) - `~/Library/Application Support/McNeel/Rhinoceros/.0/scripts` - `.rhinocode` ## Path File Naming As you might have noticed, the name of the `.pth` file specifies language specification and follows the format below with minor and patch numbers being optional: `-..` So `python-3.pth` specifies Python of version 3. The names can be more specific as in `python-3.9.10.pth`. You can also include other text after the language specification as in `python-3_dev.pth`. Any text after the language specification is only for creating unique path file names and will be ignored. For example, to make sure custom python modules are importable, a Rhino plugin developer can: - place their python modules under shared `scripts/` OR - place a `python-3_pluginname.pth` file under shared `scripts/` and list more that one search path pointing to where the python modules are located -------------------------------------------------------------------------------- # Rhino Developer - Search Results Source: https://developer.rhino3d.com/en/searchresults/ -------------------------------------------------------------------------------- # rhino3dm Source: https://developer.rhino3d.com/en/api/rhino3dm/ Classes and namespaces available from Rhino3dmIO Not all of these have been exposed in rhino3dm.py and rhino3dm.js, yet... * [Rhino.Runtime.HostUtils](https://developer.rhino3d.com/api/RhinoCommon/html/T_Rhino_Runtime_HostUtils.htm) * [Rhino.Runtime.Interop](https://developer.rhino3d.com/api/RhinoCommon/html/T_Rhino_Runtime_Interop.htm) * [Rhino.Runtime.InteropWrappers.IntPtrSafeHandle](https://developer.rhino3d.com/api/RhinoCommon/html/T_Rhino_Runtime_InteropWrappers_IntPtrSafeHandle.htm) * [Rhino.DocObjects.ViewInfo](https://developer.rhino3d.com/api/RhinoCommon/html/T_Rhino_DocObjects_ViewInfo.htm) * [Rhino.DocObjects.EarthAnchorPoint](https://developer.rhino3d.com/api/RhinoCommon/html/T_Rhino_DocObjects_EarthAnchorPoint.htm) * [Rhino.DocObjects.AnimationProperties](https://developer.rhino3d.com/api/RhinoCommon/html/T_Rhino_DocObjects_AnimationProperties.htm) * [Rhino.Geometry.AnnotationBase](https://developer.rhino3d.com/api/RhinoCommon/html/T_Rhino_Geometry_AnnotationBase.htm) * [Rhino.Geometry.Arc](https://developer.rhino3d.com/api/RhinoCommon/html/T_Rhino_Geometry_Arc.htm) * [Rhino.Collections.ArchivableDictionary](https://developer.rhino3d.com/api/RhinoCommon/html/T_Rhino_Collections_ArchivableDictionary.htm) * [DictionaryItem](https://developer.rhino3d.com/api/RhinoCommon/html/T_DictionaryItem.htm) * [Rhino.FileIO.BinaryArchiveWriter](https://developer.rhino3d.com/api/RhinoCommon/html/T_Rhino_FileIO_BinaryArchiveWriter.htm) * [Rhino.FileIO.BinaryArchiveReader](https://developer.rhino3d.com/api/RhinoCommon/html/T_Rhino_FileIO_BinaryArchiveReader.htm) * [Rhino.FileIO.BinaryArchiveFile](https://developer.rhino3d.com/api/RhinoCommon/html/T_Rhino_FileIO_BinaryArchiveFile.htm) * [Rhino.Runtime.InteropWrappers.INTERNAL_ComponentIndexArray](https://developer.rhino3d.com/api/RhinoCommon/html/T_Rhino_Runtime_InteropWrappers_INTERNAL_ComponentIndexArray.htm) * [Rhino.Runtime.InteropWrappers.StringHolder](https://developer.rhino3d.com/api/RhinoCommon/html/T_Rhino_Runtime_InteropWrappers_StringHolder.htm) * [Rhino.Runtime.InteropWrappers.SimpleArrayInt](https://developer.rhino3d.com/api/RhinoCommon/html/T_Rhino_Runtime_InteropWrappers_SimpleArrayInt.htm) * [Rhino.Runtime.InteropWrappers.SimpleArrayUint](https://developer.rhino3d.com/api/RhinoCommon/html/T_Rhino_Runtime_InteropWrappers_SimpleArrayUint.htm) * [Rhino.Runtime.InteropWrappers.SimpleArrayGuidPointer](https://developer.rhino3d.com/api/RhinoCommon/html/T_Rhino_Runtime_InteropWrappers_SimpleArrayGuidPointer.htm) * [Rhino.Runtime.InteropWrappers.SimpleArrayGuid](https://developer.rhino3d.com/api/RhinoCommon/html/T_Rhino_Runtime_InteropWrappers_SimpleArrayGuid.htm) * [Rhino.Runtime.InteropWrappers.SimpleArrayInterval](https://developer.rhino3d.com/api/RhinoCommon/html/T_Rhino_Runtime_InteropWrappers_SimpleArrayInterval.htm) * [Rhino.Runtime.InteropWrappers.SimpleArrayDouble](https://developer.rhino3d.com/api/RhinoCommon/html/T_Rhino_Runtime_InteropWrappers_SimpleArrayDouble.htm) * [Rhino.Runtime.InteropWrappers.SimpleArrayPoint2d](https://developer.rhino3d.com/api/RhinoCommon/html/T_Rhino_Runtime_InteropWrappers_SimpleArrayPoint2d.htm) * [Rhino.Runtime.InteropWrappers.SimpleArrayPoint3d](https://developer.rhino3d.com/api/RhinoCommon/html/T_Rhino_Runtime_InteropWrappers_SimpleArrayPoint3d.htm) * [Rhino.Runtime.InteropWrappers.SimpleArrayArrayPoint3d](https://developer.rhino3d.com/api/RhinoCommon/html/T_Rhino_Runtime_InteropWrappers_SimpleArrayArrayPoint3d.htm) * [Rhino.Runtime.InteropWrappers.SimpleArrayPlane](https://developer.rhino3d.com/api/RhinoCommon/html/T_Rhino_Runtime_InteropWrappers_SimpleArrayPlane.htm) * [Rhino.Runtime.InteropWrappers.SimpleArrayLine](https://developer.rhino3d.com/api/RhinoCommon/html/T_Rhino_Runtime_InteropWrappers_SimpleArrayLine.htm) * [Rhino.Runtime.InteropWrappers.SimpleArray2dex](https://developer.rhino3d.com/api/RhinoCommon/html/T_Rhino_Runtime_InteropWrappers_SimpleArray2dex.htm) * [Rhino.Runtime.InteropWrappers.SimpleArraySurfacePointer](https://developer.rhino3d.com/api/RhinoCommon/html/T_Rhino_Runtime_InteropWrappers_SimpleArraySurfacePointer.htm) * [Rhino.Runtime.InteropWrappers.SimpleArrayCurvePointer](https://developer.rhino3d.com/api/RhinoCommon/html/T_Rhino_Runtime_InteropWrappers_SimpleArrayCurvePointer.htm) * [Rhino.Runtime.InteropWrappers.SimpleArrayGeometryPointer](https://developer.rhino3d.com/api/RhinoCommon/html/T_Rhino_Runtime_InteropWrappers_SimpleArrayGeometryPointer.htm) * [Rhino.Runtime.InteropWrappers.SimpleArrayMeshPointer](https://developer.rhino3d.com/api/RhinoCommon/html/T_Rhino_Runtime_InteropWrappers_SimpleArrayMeshPointer.htm) * [Rhino.Runtime.InteropWrappers.SimpleArrayBrepPointer](https://developer.rhino3d.com/api/RhinoCommon/html/T_Rhino_Runtime_InteropWrappers_SimpleArrayBrepPointer.htm) * [Rhino.Runtime.InteropWrappers.SimpleArrayLinetypePointer](https://developer.rhino3d.com/api/RhinoCommon/html/T_Rhino_Runtime_InteropWrappers_SimpleArrayLinetypePointer.htm) * [Rhino.Runtime.InteropWrappers.SimpleArrayExtrusionPointer](https://developer.rhino3d.com/api/RhinoCommon/html/T_Rhino_Runtime_InteropWrappers_SimpleArrayExtrusionPointer.htm) * [Rhino.Runtime.InteropWrappers.SimpleArrayBinaryArchiveReader](https://developer.rhino3d.com/api/RhinoCommon/html/T_Rhino_Runtime_InteropWrappers_SimpleArrayBinaryArchiveReader.htm) * [Rhino.Runtime.InteropWrappers.ClassArrayString](https://developer.rhino3d.com/api/RhinoCommon/html/T_Rhino_Runtime_InteropWrappers_ClassArrayString.htm) * [Rhino.Runtime.InteropWrappers.ArrayOfTArrayMarshal](https://developer.rhino3d.com/api/RhinoCommon/html/T_Rhino_Runtime_InteropWrappers_ArrayOfTArrayMarshal.htm) * [Rhino.Geometry.Extrusion](https://developer.rhino3d.com/api/RhinoCommon/html/T_Rhino_Geometry_Extrusion.htm) * [Rhino.Geometry.BezierCurve](https://developer.rhino3d.com/api/RhinoCommon/html/T_Rhino_Geometry_BezierCurve.htm) * [Rhino.Geometry.Brep](https://developer.rhino3d.com/api/RhinoCommon/html/T_Rhino_Geometry_Brep.htm) * [Rhino.Geometry.BrepVertex](https://developer.rhino3d.com/api/RhinoCommon/html/T_Rhino_Geometry_BrepVertex.htm) * [Rhino.Geometry.BrepEdge](https://developer.rhino3d.com/api/RhinoCommon/html/T_Rhino_Geometry_BrepEdge.htm) * [Rhino.Geometry.BrepTrim](https://developer.rhino3d.com/api/RhinoCommon/html/T_Rhino_Geometry_BrepTrim.htm) * [Rhino.Geometry.BrepLoop](https://developer.rhino3d.com/api/RhinoCommon/html/T_Rhino_Geometry_BrepLoop.htm) * [Rhino.Geometry.BrepFace](https://developer.rhino3d.com/api/RhinoCommon/html/T_Rhino_Geometry_BrepFace.htm) * [Rhino.Geometry.SurfaceOfHolder](https://developer.rhino3d.com/api/RhinoCommon/html/T_Rhino_Geometry_SurfaceOfHolder.htm) * [Rhino.Geometry.SurfaceHolder](https://developer.rhino3d.com/api/RhinoCommon/html/T_Rhino_Geometry_SurfaceHolder.htm) * [Rhino.Geometry.CurveHolder](https://developer.rhino3d.com/api/RhinoCommon/html/T_Rhino_Geometry_CurveHolder.htm) * [Rhino.Geometry.Collections.BrepVertexList](https://developer.rhino3d.com/api/RhinoCommon/html/T_Rhino_Geometry_Collections_BrepVertexList.htm) * [Rhino.Geometry.Collections.BrepFaceList](https://developer.rhino3d.com/api/RhinoCommon/html/T_Rhino_Geometry_Collections_BrepFaceList.htm) * [Rhino.Geometry.Collections.BrepSurfaceList](https://developer.rhino3d.com/api/RhinoCommon/html/T_Rhino_Geometry_Collections_BrepSurfaceList.htm) * [Rhino.Geometry.Collections.BrepCurveList](https://developer.rhino3d.com/api/RhinoCommon/html/T_Rhino_Geometry_Collections_BrepCurveList.htm) * [Rhino.Geometry.Collections.BrepEdgeList](https://developer.rhino3d.com/api/RhinoCommon/html/T_Rhino_Geometry_Collections_BrepEdgeList.htm) * [Rhino.Geometry.Collections.BrepTrimList](https://developer.rhino3d.com/api/RhinoCommon/html/T_Rhino_Geometry_Collections_BrepTrimList.htm) * [Rhino.Geometry.Collections.BrepLoopList](https://developer.rhino3d.com/api/RhinoCommon/html/T_Rhino_Geometry_Collections_BrepLoopList.htm) * [Rhino.Geometry.Circle](https://developer.rhino3d.com/api/RhinoCommon/html/T_Rhino_Geometry_Circle.htm) * [Rhino.Geometry.Curve](https://developer.rhino3d.com/api/RhinoCommon/html/T_Rhino_Geometry_Curve.htm) * [Rhino.IndexPair](https://developer.rhino3d.com/api/RhinoCommon/html/T_Rhino_IndexPair.htm) * [Rhino.RhinoMath](https://developer.rhino3d.com/api/RhinoCommon/html/T_Rhino_RhinoMath.htm) * [Rhino.Geometry.ComponentIndex](https://developer.rhino3d.com/api/RhinoCommon/html/T_Rhino_Geometry_ComponentIndex.htm) * [Rhino.Geometry.Dimension](https://developer.rhino3d.com/api/RhinoCommon/html/T_Rhino_Geometry_Dimension.htm) * [Rhino.Geometry.LinearDimension](https://developer.rhino3d.com/api/RhinoCommon/html/T_Rhino_Geometry_LinearDimension.htm) * [Rhino.Geometry.AngularDimension](https://developer.rhino3d.com/api/RhinoCommon/html/T_Rhino_Geometry_AngularDimension.htm) * [Rhino.Geometry.RadialDimension](https://developer.rhino3d.com/api/RhinoCommon/html/T_Rhino_Geometry_RadialDimension.htm) * [Rhino.Geometry.OrdinateDimension](https://developer.rhino3d.com/api/RhinoCommon/html/T_Rhino_Geometry_OrdinateDimension.htm) * [Rhino.Geometry.Centermark](https://developer.rhino3d.com/api/RhinoCommon/html/T_Rhino_Geometry_Centermark.htm) * [Rhino.FileIO.File3dm](https://developer.rhino3d.com/api/RhinoCommon/html/T_Rhino_FileIO_File3dm.htm) * [Rhino.FileIO.File3dmWriteOptions](https://developer.rhino3d.com/api/RhinoCommon/html/T_Rhino_FileIO_File3dmWriteOptions.htm) * [Rhino.FileIO.File3dmObject](https://developer.rhino3d.com/api/RhinoCommon/html/T_Rhino_FileIO_File3dmObject.htm) * [Rhino.FileIO.CommonComponentTable](https://developer.rhino3d.com/api/RhinoCommon/html/T_Rhino_FileIO_CommonComponentTable.htm) * [Rhino.FileIO.File3dmCommonComponentTable](https://developer.rhino3d.com/api/RhinoCommon/html/T_Rhino_FileIO_File3dmCommonComponentTable.htm) * [Rhino.FileIO.ManifestTable](https://developer.rhino3d.com/api/RhinoCommon/html/T_Rhino_FileIO_ManifestTable.htm) * [Rhino.FileIO.File3dmManifestTable](https://developer.rhino3d.com/api/RhinoCommon/html/T_Rhino_FileIO_File3dmManifestTable.htm) * [Rhino.FileIO.File3dmObjectTable](https://developer.rhino3d.com/api/RhinoCommon/html/T_Rhino_FileIO_File3dmObjectTable.htm) * [Rhino.FileIO.File3dmPlugInDataTable](https://developer.rhino3d.com/api/RhinoCommon/html/T_Rhino_FileIO_File3dmPlugInDataTable.htm) * [Rhino.FileIO.File3dmMaterialTable](https://developer.rhino3d.com/api/RhinoCommon/html/T_Rhino_FileIO_File3dmMaterialTable.htm) * [Rhino.FileIO.File3dmLinetypeTable](https://developer.rhino3d.com/api/RhinoCommon/html/T_Rhino_FileIO_File3dmLinetypeTable.htm) * [Rhino.FileIO.File3dmLayerTable](https://developer.rhino3d.com/api/RhinoCommon/html/T_Rhino_FileIO_File3dmLayerTable.htm) * [Rhino.FileIO.File3dmGroupTable](https://developer.rhino3d.com/api/RhinoCommon/html/T_Rhino_FileIO_File3dmGroupTable.htm) * [Rhino.FileIO.File3dmDimStyleTable](https://developer.rhino3d.com/api/RhinoCommon/html/T_Rhino_FileIO_File3dmDimStyleTable.htm) * [Rhino.FileIO.File3dmHatchPatternTable](https://developer.rhino3d.com/api/RhinoCommon/html/T_Rhino_FileIO_File3dmHatchPatternTable.htm) * [Rhino.FileIO.File3dmInstanceDefinitionTable](https://developer.rhino3d.com/api/RhinoCommon/html/T_Rhino_FileIO_File3dmInstanceDefinitionTable.htm) * [Rhino.FileIO.File3dmViewTable](https://developer.rhino3d.com/api/RhinoCommon/html/T_Rhino_FileIO_File3dmViewTable.htm) * [Rhino.FileIO.File3dmNamedConstructionPlanes](https://developer.rhino3d.com/api/RhinoCommon/html/T_Rhino_FileIO_File3dmNamedConstructionPlanes.htm) * [Rhino.FileIO.File3dmStringTable](https://developer.rhino3d.com/api/RhinoCommon/html/T_Rhino_FileIO_File3dmStringTable.htm) * [Rhino.Geometry.GeometryBase](https://developer.rhino3d.com/api/RhinoCommon/html/T_Rhino_Geometry_GeometryBase.htm) * [Rhino.Geometry.Hatch](https://developer.rhino3d.com/api/RhinoCommon/html/T_Rhino_Geometry_Hatch.htm) * [Rhino.Geometry.InstanceDefinitionGeometry](https://developer.rhino3d.com/api/RhinoCommon/html/T_Rhino_Geometry_InstanceDefinitionGeometry.htm) * [Rhino.Geometry.Intersect.Intersection](https://developer.rhino3d.com/api/RhinoCommon/html/T_Rhino_Geometry_Intersect_Intersection.htm) * [Rhino.Geometry.Light](https://developer.rhino3d.com/api/RhinoCommon/html/T_Rhino_Geometry_Light.htm) * [Rhino.Geometry.Line](https://developer.rhino3d.com/api/RhinoCommon/html/T_Rhino_Geometry_Line.htm) * [Rhino.Render.CachedTextureCoordinates](https://developer.rhino3d.com/api/RhinoCommon/html/T_Rhino_Render_CachedTextureCoordinates.htm) * [Rhino.Render.CachedTextureCoordinatesEnumerator](https://developer.rhino3d.com/api/RhinoCommon/html/T_Rhino_Render_CachedTextureCoordinatesEnumerator.htm) * [Rhino.Geometry.MeshingParameters](https://developer.rhino3d.com/api/RhinoCommon/html/T_Rhino_Geometry_MeshingParameters.htm) * [Rhino.Geometry.Mesh](https://developer.rhino3d.com/api/RhinoCommon/html/T_Rhino_Geometry_Mesh.htm) * [MeshNgonCompleteEnumerable](https://developer.rhino3d.com/api/RhinoCommon/html/T_MeshNgonCompleteEnumerable.htm) * [MeshNgonCompleteEnumerator](https://developer.rhino3d.com/api/RhinoCommon/html/T_MeshNgonCompleteEnumerator.htm) * [Rhino.Geometry.Collections.MeshVertexList](https://developer.rhino3d.com/api/RhinoCommon/html/T_Rhino_Geometry_Collections_MeshVertexList.htm) * [Rhino.Geometry.Collections.MeshTopologyVertexList](https://developer.rhino3d.com/api/RhinoCommon/html/T_Rhino_Geometry_Collections_MeshTopologyVertexList.htm) * [Rhino.Geometry.Collections.MeshTopologyEdgeList](https://developer.rhino3d.com/api/RhinoCommon/html/T_Rhino_Geometry_Collections_MeshTopologyEdgeList.htm) * [Rhino.Geometry.Collections.MeshVertexNormalList](https://developer.rhino3d.com/api/RhinoCommon/html/T_Rhino_Geometry_Collections_MeshVertexNormalList.htm) * [Rhino.Geometry.Collections.MeshFaceList](https://developer.rhino3d.com/api/RhinoCommon/html/T_Rhino_Geometry_Collections_MeshFaceList.htm) * [Rhino.Geometry.Collections.MeshNgonList](https://developer.rhino3d.com/api/RhinoCommon/html/T_Rhino_Geometry_Collections_MeshNgonList.htm) * [Rhino.Geometry.Collections.MeshFaceNormalList](https://developer.rhino3d.com/api/RhinoCommon/html/T_Rhino_Geometry_Collections_MeshFaceNormalList.htm) * [Rhino.Geometry.Collections.MeshVertexColorList](https://developer.rhino3d.com/api/RhinoCommon/html/T_Rhino_Geometry_Collections_MeshVertexColorList.htm) * [Rhino.Geometry.Collections.MeshTextureCoordinateList](https://developer.rhino3d.com/api/RhinoCommon/html/T_Rhino_Geometry_Collections_MeshTextureCoordinateList.htm) * [Rhino.Geometry.Collections.MeshVertexStatusList](https://developer.rhino3d.com/api/RhinoCommon/html/T_Rhino_Geometry_Collections_MeshVertexStatusList.htm) * [Rhino.Geometry.MeshFace](https://developer.rhino3d.com/api/RhinoCommon/html/T_Rhino_Geometry_MeshFace.htm) * [Rhino.Geometry.MeshNgon](https://developer.rhino3d.com/api/RhinoCommon/html/T_Rhino_Geometry_MeshNgon.htm) * [Rhino.Geometry.NurbsCurve](https://developer.rhino3d.com/api/RhinoCommon/html/T_Rhino_Geometry_NurbsCurve.htm) * [Rhino.Geometry.ControlPoint](https://developer.rhino3d.com/api/RhinoCommon/html/T_Rhino_Geometry_ControlPoint.htm) * [Rhino.Geometry.Collections.NurbsCurveKnotList](https://developer.rhino3d.com/api/RhinoCommon/html/T_Rhino_Geometry_Collections_NurbsCurveKnotList.htm) * [Rhino.Geometry.Collections.NurbsCurvePointList](https://developer.rhino3d.com/api/RhinoCommon/html/T_Rhino_Geometry_Collections_NurbsCurvePointList.htm) * [Rhino.Geometry.NurbsSurface](https://developer.rhino3d.com/api/RhinoCommon/html/T_Rhino_Geometry_NurbsSurface.htm) * [Rhino.Geometry.Collections.NurbsSurfacePointList](https://developer.rhino3d.com/api/RhinoCommon/html/T_Rhino_Geometry_Collections_NurbsSurfacePointList.htm) * [NurbsSrfEnum](https://developer.rhino3d.com/api/RhinoCommon/html/T_NurbsSrfEnum.htm) * [Rhino.Geometry.Collections.NurbsSurfaceKnotList](https://developer.rhino3d.com/api/RhinoCommon/html/T_Rhino_Geometry_Collections_NurbsSurfaceKnotList.htm) * [Rhino.Runtime.CommonObject](https://developer.rhino3d.com/api/RhinoCommon/html/T_Rhino_Runtime_CommonObject.htm) * [Rhino.Geometry.Plane](https://developer.rhino3d.com/api/RhinoCommon/html/T_Rhino_Geometry_Plane.htm) * [Rhino.Geometry.ClippingPlaneSurface](https://developer.rhino3d.com/api/RhinoCommon/html/T_Rhino_Geometry_ClippingPlaneSurface.htm) * [Rhino.Geometry.Interval](https://developer.rhino3d.com/api/RhinoCommon/html/T_Rhino_Geometry_Interval.htm) * [Rhino.Geometry.Point2d](https://developer.rhino3d.com/api/RhinoCommon/html/T_Rhino_Geometry_Point2d.htm) * [Rhino.Geometry.Point3d](https://developer.rhino3d.com/api/RhinoCommon/html/T_Rhino_Geometry_Point3d.htm) * [Rhino.Geometry.Point4d](https://developer.rhino3d.com/api/RhinoCommon/html/T_Rhino_Geometry_Point4d.htm) * [Rhino.Geometry.Vector2d](https://developer.rhino3d.com/api/RhinoCommon/html/T_Rhino_Geometry_Vector2d.htm) * [Rhino.Geometry.Vector3d](https://developer.rhino3d.com/api/RhinoCommon/html/T_Rhino_Geometry_Vector3d.htm) * [Rhino.Geometry.Ray3d](https://developer.rhino3d.com/api/RhinoCommon/html/T_Rhino_Geometry_Ray3d.htm) * [Rhino.Geometry.PointCloud](https://developer.rhino3d.com/api/RhinoCommon/html/T_Rhino_Geometry_PointCloud.htm) * [PointCloudPoints](https://developer.rhino3d.com/api/RhinoCommon/html/T_PointCloudPoints.htm) * [PointCloudItemEnumerator](https://developer.rhino3d.com/api/RhinoCommon/html/T_PointCloudItemEnumerator.htm) * [Rhino.Geometry.PolyCurve](https://developer.rhino3d.com/api/RhinoCommon/html/T_Rhino_Geometry_PolyCurve.htm) * [Rhino.Geometry.Polyline](https://developer.rhino3d.com/api/RhinoCommon/html/T_Rhino_Geometry_Polyline.htm) * [Rhino.Geometry.PolylineCurve](https://developer.rhino3d.com/api/RhinoCommon/html/T_Rhino_Geometry_PolylineCurve.htm) * [Rhino.Geometry.Quaternion](https://developer.rhino3d.com/api/RhinoCommon/html/T_Rhino_Geometry_Quaternion.htm) * [Rhino.Geometry.Sphere](https://developer.rhino3d.com/api/RhinoCommon/html/T_Rhino_Geometry_Sphere.htm) * [Rhino.Geometry.SurfaceCurvature](https://developer.rhino3d.com/api/RhinoCommon/html/T_Rhino_Geometry_SurfaceCurvature.htm) * [Rhino.Geometry.Surface](https://developer.rhino3d.com/api/RhinoCommon/html/T_Rhino_Geometry_Surface.htm) * [Rhino.Geometry.TextEntity](https://developer.rhino3d.com/api/RhinoCommon/html/T_Rhino_Geometry_TextEntity.htm) * [Rhino.DocObjects.Texture](https://developer.rhino3d.com/api/RhinoCommon/html/T_Rhino_DocObjects_Texture.htm) * [Rhino.DocObjects.Custom.UserData](https://developer.rhino3d.com/api/RhinoCommon/html/T_Rhino_DocObjects_Custom_UserData.htm) * [Rhino.DocObjects.Custom.UserDataListEnumerator](https://developer.rhino3d.com/api/RhinoCommon/html/T_Rhino_DocObjects_Custom_UserDataListEnumerator.htm) * [Rhino.DocObjects.Custom.UserDataList](https://developer.rhino3d.com/api/RhinoCommon/html/T_Rhino_DocObjects_Custom_UserDataList.htm) * [Rhino.DocObjects.ViewportInfo](https://developer.rhino3d.com/api/RhinoCommon/html/T_Rhino_DocObjects_ViewportInfo.htm) * [Rhino.Geometry.Transform](https://developer.rhino3d.com/api/RhinoCommon/html/T_Rhino_Geometry_Transform.htm) * [Rhino.Geometry.SpaceMorph](https://developer.rhino3d.com/api/RhinoCommon/html/T_Rhino_Geometry_SpaceMorph.htm) * [Rhino.Render.ChangeQueue.Skylight](https://developer.rhino3d.com/api/RhinoCommon/html/T_Rhino_Render_ChangeQueue_Skylight.htm) * [Rhino.Render.ChangeQueue.DynamicObjectTransform](https://developer.rhino3d.com/api/RhinoCommon/html/T_Rhino_Render_ChangeQueue_DynamicObjectTransform.htm) * [Rhino.Commands.Command](https://developer.rhino3d.com/api/RhinoCommon/html/T_Rhino_Commands_Command.htm) * [Rhino.DocObjects.HistoryRecord](https://developer.rhino3d.com/api/RhinoCommon/html/T_Rhino_DocObjects_HistoryRecord.htm) * [Rhino.DocObjects.ReplayHistoryData](https://developer.rhino3d.com/api/RhinoCommon/html/T_Rhino_DocObjects_ReplayHistoryData.htm) * [Rhino.DocObjects.ReplayHistoryResult](https://developer.rhino3d.com/api/RhinoCommon/html/T_Rhino_DocObjects_ReplayHistoryResult.htm) * [Rhino.DocObjects.DimensionStyle](https://developer.rhino3d.com/api/RhinoCommon/html/T_Rhino_DocObjects_DimensionStyle.htm) * [Rhino.RhinoDoc](https://developer.rhino3d.com/api/RhinoCommon/html/T_Rhino_RhinoDoc.htm) * [Rhino.DocObjects.Tables.RuntimeDocumentDataTable](https://developer.rhino3d.com/api/RhinoCommon/html/T_Rhino_DocObjects_Tables_RuntimeDocumentDataTable.htm) * [Rhino.DocObjects.Tables.ViewTable](https://developer.rhino3d.com/api/RhinoCommon/html/T_Rhino_DocObjects_Tables_ViewTable.htm) * [Rhino.DocObjects.Tables.ObjectTable](https://developer.rhino3d.com/api/RhinoCommon/html/T_Rhino_DocObjects_Tables_ObjectTable.htm) * [GripMap](https://developer.rhino3d.com/api/RhinoCommon/html/T_GripMap.htm) * [Rhino.DocObjects.InstanceDefinition](https://developer.rhino3d.com/api/RhinoCommon/html/T_Rhino_DocObjects_InstanceDefinition.htm) * [LayerHolder](https://developer.rhino3d.com/api/RhinoCommon/html/T_LayerHolder.htm) * [Rhino.DocObjects.Layer](https://developer.rhino3d.com/api/RhinoCommon/html/T_Rhino_DocObjects_Layer.htm) * [Rhino.DocObjects.RhinoObject](https://developer.rhino3d.com/api/RhinoCommon/html/T_Rhino_DocObjects_RhinoObject.htm) * [Rhino.DocObjects.ObjRef](https://developer.rhino3d.com/api/RhinoCommon/html/T_Rhino_DocObjects_ObjRef.htm) * [Rhino.DocObjects.ObjectAttributes](https://developer.rhino3d.com/api/RhinoCommon/html/T_Rhino_DocObjects_ObjectAttributes.htm) * [Rhino.Runtime.DefaultSettingsWriteErrorService](https://developer.rhino3d.com/api/RhinoCommon/html/T_Rhino_Runtime_DefaultSettingsWriteErrorService.htm) * [Rhino.Runtime.FileSettingsService](https://developer.rhino3d.com/api/RhinoCommon/html/T_Rhino_Runtime_FileSettingsService.htm) * [Rhino.PersistentSettingsConverter](https://developer.rhino3d.com/api/RhinoCommon/html/T_Rhino_PersistentSettingsConverter.htm) * [Rhino.SettingValue](https://developer.rhino3d.com/api/RhinoCommon/html/T_Rhino_SettingValue.htm) * [Rhino.PersistentSettingsSavedEventArgs](https://developer.rhino3d.com/api/RhinoCommon/html/T_Rhino_PersistentSettingsSavedEventArgs.htm) * [Rhino.PersistentSettings](https://developer.rhino3d.com/api/RhinoCommon/html/T_Rhino_PersistentSettings.htm) -------------------------------------------------------------------------------- # RhinoCode Command Line Interface Source: https://developer.rhino3d.com/en/guides/scripting/advanced-cli/ Provides information on using rhinocode command line utility that shipped with rhino Rhino3D (>=8.11) ships `rhinocode` command line utility (cli) that provides access to Rhino's scripting infrastructure. This utility communicates with a script server that is running inside of Rhino. For performance reasons, the script server is not started on Rhino launch. You can use the `StartScriptServer` command to start the server. Add it to Rhino startup commands to force starting server when starting Rhino `rhinocode` can provide information about running instances of Rhino, run scripts of any supported language in a Rhino instance, and build projects created in Rhino script editor into Rhino and Grasshopper plugins. To run `rhinocode`: - Make sure script server is started by running `StartScriptServer` in Rhino. On Windows: - Add `%PROGRAMFILES%\Rhino 8\System` to user or system `PATH` environment variable. Then open a terminal and run `rhinocode` On macOS: - Depending on the type of shell you are using, add `/Applications/Rhino 8.app/Contents/Resources/bin` to the `PATH` environment variable. Then reload the terminal and run `rhinocode` If, for whatever reason, you do not want to modify the `PATH` environment variable, navigate to Rhino installation folder inside terminal and run `.\rhinocode` from there. Test running `rhinocode` by specifying `-V` argument to get the version. Under current implementation, the version matches the version of Rhino that shipped this utility: ```text $ rhinocode -V 8.11.24224 ``` ## Overview Running `rhinocode --help` will print out an overview of subcommands and their descriptions (Actual message might be different from below depending on the utility version): ```text $ rhinocode --help Usage: rhinocode [] RhinoCode command line interface Commands: list List all running instances of Rhino3D command Run given command in Rhino3D script Run given script in Rhino3D project Manage and publish Rhino3D projects Options: -V, --version Print version of this utility -v, --verbose Enable verbose reporting -r, --rhino Use this instance of Rhino3D (get id from list command) -d, --debug Debug outputs -t, --trace Trace outputs Run 'rhinocode COMMAND --help' for more information on a command ``` You can get further help in each individual subcommand by adding the `--help` flag: ```text $ rhinocode script --help Usage: rhinocode script Run given script in Rhino3D Arguments: Full path of script to be executed Examples: > rhinocode script C:\path\to\script.py ``` ## Listing Rhino Instances Use `list` subcommand to list all running instances of Rhino. Each instance has a Process ID (PID) and a unique identifier (ID). The report also shows the name and path of any document that is open inside the Rhino instance to better help identify each Rhino: You can use ID with `--rhino` option to run a subcommand on that specific Rhino instance. ```text $ rhinocode list PID ID DOC PATH 75029 rhinocode_remotepipe_75029 Sphere.3dm C:\path\to\rhinofiles\Sphere.3dm ``` Add the `--json` flag to get results in JSON. The below command can be used to parse the results of the `--json` flag, making it easier to read for custom integrations: ```text $ rhinocode list --json | python3 -m json.tool --indent 2 [ { "pipeId": "rhinocode_remotepipe_75029", "processId": 75029, "processName": "Rhinoceros", "processVersion": "8.11.24224.1000", "processAge": 103, "activeDoc": { "title": "Sphere.3dm", "location": "C:\\path\\to\\rhinofiles\\Sphere.3dm" }, "activeViewport": "Perspective", "$meta": { "version": "1.0" }, "$type": "status" } ] ``` ## Running Rhino Commands Use `command` subcommand to run a rhino command in an instance of Rhino. This example runs the `Circle` command in Rhino with a few arguments to draw a circle: ```text $ rhinocode command "_circle 0 0 0 20" ``` If you have multiple instances of Rhino running, you can specify which instance to run the command on using the `--rhino` option: ```text $ rhinocode --rhino rhinocode_remotepipe_75029 command "_circle 0 0 0 20" ``` ## Running Scripts Use `command` subcommand to run a rhino command in an instance of Rhino: ```text $ rhinocode script C:\path\to\scripts\foo.py $ rhinocode script C:\path\to\scripts\foo.cs $ rhinocode script C:\path\to\scripts\foo.py2 ``` If you have multiple instances of Rhino running, you can specify which instance to run the command on using the `--rhino` option: ```text $ rhinocode --rhino rhinocode_remotepipe_75029 script C:\path\to\scripts\foo.py ``` ## Working With Projects - `project` command builds projects without requiring a running instance of Rhino. It can be used in build scripts in your CI/CD pipeline. - See [Creating Rhino Projects](/guides/scripting/projects-create) and [Creating Rhino and Grasshopper Plugins](/guides/scripting/projects-publish) for more information on creating projects in the Rhino script editor. Use `project` subcommand to work with projects created in the Rhino script editor. ### Build a Project The `build` subcommand builds Rhino and Grasshopper plugins from a project. The project file only stores references to scripts, Grasshopper definitions, and language libraries so these resources can be updated outside of the project without affecting the project file itself. The most recent state of these references is used when building a project. ```text $ rhinocode project build C:\path\to\projects\MyTools.rhproj 0% - Preparing project 10% - Preparing build path 20% - Preparing plugin assembly 50% - Preparing grasshopper plugin assembly 60% - Adding shared resources 90% - Creating yak package 100% - Complete ``` You can furthur customize the build by providing these options: - Build Version `--buildversion ` - Build Target `--buildtarget ` - Build Path `--buildpath ` See `rhinocode project --help` for more information and examples on each of these options: ```text Options: -bv, --buildversion Build version formatted as . with optional , , and numbers formatted as ..-+ Examples: 0.1 0.1.1234 0.1.1234-beta 0.1.1234+8989 0.1.1234-beta+8989 -bt, --buildtarget Target Rhino version formatted as . with optional formatted as .- Examples: 8.* 8.10 8.8-macOS 8.9-mac -bp, --buildpath Absolute or relative build path Examples: .\mybuild C:\path\to\mybuild ``` The build folder (depends on build target) will contain the generated plugins as well as a `.yak` package that packs the plugins and other resources: - YAK Package: `mytools-0.1.18292.8990-rh8-any.yak` - Rhino Plugin containing commands: `MyTools.rhp` - Rhino Toolbar Definition: `MyTools.rui` - Grasshopper plugin containing components: `MyTools.Components.gha` Use `yak` command line utility that is shipped with Rhino to push the `.yak` package to your desired package server. See [Pushing a Package to the Server](/guides/yak/pushing-a-package-to-the-server) on how to publish `.yak` files to package server To get a more verbose report during the build process use the `-d|--debug` or `-t|--trace` options: ```text rhinocode -t project build C:\path\to\projects\MyTools.rhproj ``` -------------------------------------------------------------------------------- # RhinoScriptSyntax Source: https://developer.rhino3d.com/en/api/RhinoScriptSyntax/ -------------------------------------------------------------------------------- # Script Component: Grasshopper Source: https://developer.rhino3d.com/en/guides/scripting/scripting-component/ Provides an overview on how to edit and run scripts inside of Grasshopper ## Create Script Component To access the unified script editor in Grasshopper, go to the **Maths** tab and **Script** panel and drop a Script component onto the canvas: ![](https://developer.rhino3d.com/en/guides/scripting/scripting-component/01.png) This component is designed to work with any of the supported languages. While being completely new and internally different from the legacy GHPython and C# components, it tries to replicate the same behaviors as those legacy components and stay familiar as much as possible. ## First Script Once you place an instance of the **Script** component on the canvas, it will show a warning that it is missing a script. We first need to choose the programming language we would like to script in to initialize the component: Double-click on the component to quickly start a new script using the most-recently used language. ![](https://developer.rhino3d.com/en/guides/scripting/scripting-component/03.png) The first button at the bottom of the component shows the default or last used language for this component. Double-clicking on the component itself will automatically use this language to initialize the component and start a new script: ![](https://developer.rhino3d.com/en/guides/scripting/scripting-component/04.png) Other supported languages could be selected from the [ ● ● ● ] menu: ![](https://developer.rhino3d.com/en/guides/scripting/scripting-component/05.png) Choose **Python 3** to start a new script: ![](https://developer.rhino3d.com/en/guides/scripting/scripting-component/06.png) The default script looks like this: ```python """Grasshopper Script""" a = "Hello Python 3 in Grasshopper!" print(a) ``` ## Script Inputs and Outputs The **Script** component supports *Zoomable User Interface* (*ZUI* for short). This means that you can modify the inputs and outputs of the component by zooming in until the **Insert** [ ⊕ ] and **Remove** [ ⊖ ] controls are visible on either side: ![](https://developer.rhino3d.com/en/guides/scripting/scripting-component/07.png) When all parameters on either side are removed, the component will draw a jagged edge on that side. This is completely okay as not all scripts require inputs or produce values on the other output: ![](https://developer.rhino3d.com/en/guides/scripting/scripting-component/08.png) For this example lets keep the **x** and **y** inputs and the **a** as output: ![](https://developer.rhino3d.com/en/guides/scripting/scripting-component/09.png) ## Edit Script Let's remove the script contents and type a new line instead. The editor will help autocomplete the names: ```python import Rhino a = Rhino.RhinoApp.Version ``` ![](https://developer.rhino3d.com/en/guides/scripting/scripting-component/10.png) Close the editor and click on Yes to apply the script to the component: ![](https://developer.rhino3d.com/en/guides/scripting/scripting-component/11.png) ## Run Scripts Connect a panel to the output **a** and run the Grasshopper canvas. The script will output the current Rhino version: ![](https://developer.rhino3d.com/en/guides/scripting/scripting-component/12.png) Let's add another output named **total** and add a few more lines to the script to compute a total of the given inputs: ```python import Rhino a = Rhino.RhinoApp.Version total = x + y ``` The editor will pop up an execution error since it does not know the types for **x** and **y** because no values are assigned to these inputs yet: ![](https://developer.rhino3d.com/en/guides/scripting/scripting-component/13.png) Right-click on each input and choose the **float** option from the **Type Hints** submenu. This will ensure the inputs are initialized with the default value for the chosen type: ![](https://developer.rhino3d.com/en/guides/scripting/scripting-component/14.png) Apply the script, connect a Range component to inputs and see a list of sums on the **total** output parameter: ![](https://developer.rhino3d.com/en/guides/scripting/scripting-component/15.png) ## Debugging Scripts The script editor can debug scripts of any supported language. During debug, we can execute the script line by line or pause the execution at certain lines called **Breakpoints** and inspect the values of global and local variables. Move your mouse cursor to the left side of the line number column on line 5 and click. This should add a red dot and mark that line as a **Breakpoint**: ![](https://developer.rhino3d.com/en/guides/scripting/scripting-component/16.png) The **Breakpoints** tray at the bottom will show all the active breakpoints, and will provide buttons to Clear or Toggle all of them or individual breakpoints: ![](https://developer.rhino3d.com/en/guides/scripting/scripting-component/17.png) When you add breakpoints, the editor makes a few UI changes and provides a few more utilities for debugging: - The **Run** button will change to **Debug** - **Variables**, **Watch**, and **Call Stack** trays will be added to the bottom tray bar Now click on the green **Debug** button on the Dashboard. The editor will run the script and: - Stops at breakpoint on line 5 - Highlights the breakpoint line in orange and shows an arrow on the left side of the line - Highlights Status Bar in orange to show we are debugging a script - Activates the debug control buttons on the Dashboard - Opens the **Variables** tray at the bottom to show global and local variables ![](https://developer.rhino3d.com/en/guides/scripting/scripting-component/18.png) We can control the execution of script using the debug control buttons on the Dashboard: ![](https://developer.rhino3d.com/en/guides/scripting/scripting-component/19.png) From left to right, they are: - **Continue:** continues running the script until it stops on another breakpoint - **Step Over:** executes current line and moves on the next line - **Step In:** if the current line includes a function call, this will step into the lines defining the function code - **Step Out:** if previously stepped into a function code, this will continue executing the function code until control is returned from the function to the calling code and will stop there - **Stop:** stops debugging the script and does not continue executing the rest Click on **Continue** to see the execution move to the next line: Notice that the **Variables** panel now shows new values for **x** and **y**. The panel header also shows **Run Script (2 of 11)** on the top-right meaning this is the second time Grasshopper is executing this component with a pair of **x** and **y** inputs: ![](https://developer.rhino3d.com/en/guides/scripting/scripting-component/20.png) Progressively clicking on **Continue** will continue executing the script and modifying the variables. At each stop, the **Variables** tray shows the current values of global and local variables. Now click on **Stop** button to stop the debugging. The script component will show an error marking with the message **Debug Stopped**: ![](https://developer.rhino3d.com/en/guides/scripting/scripting-component/21.png) Once the debug stops, the editor UI changes back to normal, and the **Variables** tray will show the last state of the variables. The tray will keep these data until another session of debugging is started. At any point in time, you can use the **Toggle** button in the **Breakpoints** panel to activate or deactivate the breakpoints. Deactivated breakpoints will show up as gray dots in the editor: ![](https://developer.rhino3d.com/en/guides/scripting/scripting-component/22.png) ## Using Packages ### Python Packages You can specify the packages required for your scripts inside the script source. This creates self-contained scripts that carry all their requirements with them. - Python 3 will use **pip** to install packages from [PyPI.org](https://pypi.org) - **pip** does not support Python 2 anymore, so we are limited to the packages used in IronPython - C# will use **NuGet** to install packages from [NuGet.org](https://www.nuget.org) The default script for each language has a NOTE section at the top that describes how to specify the requirements in your scripts. Looking at the Python 3 default script, we can specify required packages using this syntax: ```python # r: numpy ``` or ```python # requirements: numpy ``` Let's create a new Python 3 script and add `numpy` as a package and use that in our script: ```python # requirements: numpy import numpy print(f"using numpy: {numpy.version.full_version}\n") a = numpy.random.rand(10) ``` Click Run, and the script editor will attempt to install the required packages before running the script. This process might take some time and the editor is going to be disabled. Once the packages are installed, the editor will continue to execute the script: ![](https://developer.rhino3d.com/en/guides/scripting/scripting-component/23.png) ### Python Libraries (Modules) Another method of adding local packages to Python scripts is by adding their path to the `sys.path`. You can simplify this step by using the `# env:` specifier in your scripts to automatically add a path to the `sys.path` before running your script: ```python # env: C:/Path/To/Where/My/Library/Is/Located/ import mylibrary mylibrary.do_something() ``` ### NuGet Packages The same convention applies to C# scripts. They use a different syntax to specify packages that match the format provided in NuGet.org page for the package you want to use: ![](https://developer.rhino3d.com/en/guides/scripting/scripting-component/24.png) Here is an example script that uses the [RestSharp](https://www.nuget.org/packages/RestSharp/110.2.0) NuGet package to grab some data from a website. Note that the first line of the script specifies the **RestSharp** package version `110.2.0` ```csharp #r "nuget: RestSharp, 110.2.0" using System; using System.Collections.Generic; using Rhino; using RestSharp; using RestSharp.Authenticators; var client = new RestClient("https://httpbin.org"); var request = new RestRequest("get"); var response = client.Get(request); a = response.Content; ``` ![](https://developer.rhino3d.com/en/guides/scripting/scripting-component/25.png) ## Editor Features Script Editor has other noteworthy features. Here we highlight a few that are used more often: ### Search Click on the **Search** tab to open the Search panel. Searching for a keyword will show all the matches in the panel. You can click on any matched item to navigate to: ![](https://developer.rhino3d.com/en/guides/scripting/scripting-component/26.png) ### Help Click on the **Help** tab to open the Help panel. This panel provides a simple method to get help on Rhino and Grasshopper APIs and provided Python modules: ![](https://developer.rhino3d.com/en/guides/scripting/scripting-component/27.png) Use the search field to search for class or method names. The info panel at the bottom provides some info about the method and its parameters: ![](https://developer.rhino3d.com/en/guides/scripting/scripting-component/28.png) -------------------------------------------------------------------------------- # Script Editing Features Source: https://developer.rhino3d.com/en/guides/scripting/editor-editing/ Provides information on editing features in Script Editor Script Editor has a few editing features that can be changed or toggled for each specific language. These settings are toggled/set using two different methods: - **Edit > Toggle** menu items: Affect the current session and are forgotten when Rhino restarts. - **Editor Options** Dialog: Are persistent and are stored in editor configuration file to disk. Depending on the type of feature, there might be a *Toggle* menu item or a setting in *Editor Options*, or *both*. Some features are language-specific and can be set independently by scripting language. ## Tabs or Spaces? Choose **Edit > Toggle White Spaces** to toggle visibilty of white spaces (Tab or Space) in your scripts. See **Show White Spaces** in [Editing Options](/guides/scripting/editor-configs/#editing-options). This option is *Off* by default: ![](https://developer.rhino3d.com/en/guides/scripting/editor-editing/editor-tabsspaces.png) **Edit > Convert Indentation to Tabs/Spaces** commands can be used to convert between Space and Tab indentations. When changing your scripts to [SDK-Mode](/guides/scripting/scripting-gh-python#sdk-mode) in Grasshopper, the indentation is detected and used in modified script: ![](https://developer.rhino3d.com/en/guides/scripting/editor-editing/editor-tabsspaces-gh.png) ## Indentation Guides Indentation guides are vertical lines drawn at different indentation levels to help visually identifying scopes and blocks. See **Show Indentation Guides** in [Editing Options](/guides/scripting/editor-configs/#editing-options). This option is *On* by default: ![](https://developer.rhino3d.com/en/guides/scripting/editor-editing/editor-indentGuides.png) ## Minimap Choose **Edit > Toggle Minimap** to toggle visibilty of minimap. It is effectively a thicker vertical scrollbar that shows a tiny preview of your script. You can scroll up and down the script using minimap. See **Show Minimap** in [Editing Options](/guides/scripting/editor-configs/#editing-options). This option is *Off* by default: ![](https://developer.rhino3d.com/en/guides/scripting/editor-editing/editor-minimap.png) ## Line Numbers Pretty clear what they are! Choose **Edit > Toggle Line Numbers** to toggle their visibility: ![](https://developer.rhino3d.com/en/guides/scripting/editor-editing/editor-lineNumbers.png) ## Diagnostics (Linting) As you are typing your script, the editor is continiously checking for syntax errors using the static analyzers provided by the language. These messages are categorized as **Errors**, **Warnings**, or **Info** and show up in the *Problems* panel at the bottom of the editor. The script text area also shows squiggly lines where these errors are located. You can click on each item in *Problems* panel to navigate the cursor to where the problem is: ![](https://developer.rhino3d.com/en/guides/scripting/editor-editing/editor-diags.png) When there are messages in the *Problems* panel, its tab shows an icon with a count of messages listed in the panel: ![](https://developer.rhino3d.com/en/guides/scripting/editor-editing/editor-diags-problemsTab.png) By default, the *Problems* panel only shows error messages. You can use the filter toggles on the panel to see other message types as well: ![](https://developer.rhino3d.com/en/guides/scripting/editor-editing/editor-diags-problemsFilter.png) See **Diagnostics (Linting)** in [Language Support Options](/guides/scripting/editor-configs/#language-support-options) to turn diagnostics off for a specific language. This option is *On* by default. ## Autocompletion Script editor has completion support for all languages. There are a few kinds of autocompletion: - **Word Completion:** usually triggers when typing space or `.` and provides completion for methods, properties, function calls, etc: ![](https://developer.rhino3d.com/en/guides/scripting/editor-editing/editor-autocomplete-word.png) - **Signature Completion** usually triggers when opening a `(` and provides function help and parameter list, as well as tracking and highlighting arguments as you type: ![](https://developer.rhino3d.com/en/guides/scripting/editor-editing/editor-autocomplete-signature.png) - **Snippet Completion** usually triggers when typing space after a keyword and provides templates for common language constructs like `if` statements or `foreach` loops. It is *Off* by default. Choose **Edit > Toggle Snippet in Autocomplete** to toggle snippets on. ![](https://developer.rhino3d.com/en/guides/scripting/editor-editing/editor-autocomplete-snippet.png) ### Word-based Autocomplete Editor can collect words already used in the script to help with autocomplete. However as it does not have a full understanding of the context and meaning of these collected words, this type of autocompletion is not always useful. This option is *Off* by default. Choose **Edit > Toggle Word-based Autocomplete** to turn it on. See **Autocompletion** in [Language Support Options](/guides/scripting/editor-configs/#language-support-options) to toggle this option on for a specific language: ![](https://developer.rhino3d.com/en/guides/scripting/editor-editing/editor-autocomplete-wordBased.png) ### Autocomplete in Function Help Usually when typing up arguments to a function, we would like to keep the *Signature Completion* open so it can track the arguments being typed and help with each argument type or expected values. For this reason, autocompletion is momentarily stopped to keep the *Signature Completion* popup open: ![](https://developer.rhino3d.com/en/guides/scripting/editor-editing/editor-autocomplete-insignature.png) To change this behaviour - for example to get the autocompletion for `os.` in example above - choose **Edit > Toggle Autocomplete in Function Help**. See **Autocompletion** in [Language Support Options](/guides/scripting/editor-configs/#language-support-options) to toggle this option for a specific language. -------------------------------------------------------------------------------- # Script Editor Logs Source: https://developer.rhino3d.com/en/guides/scripting/editor-logs/ Provides information on Script Editor logs viewer window ## Opening Log Viewer *Script Editor* writes log messages during its operation. These log messages are useful in detecting any internal errors that might have happened when working with the script editor. You can view these logs by clicking on the *Open Logger* button on the far right side of editor status bar, or by running **RhinoCodeLogs** command: ![](https://developer.rhino3d.com/en/guides/scripting/editor-logs/logs-editorbutton.png) The same button is also available on a few other editor dialogs like *Publish Project* dialog: ![](https://developer.rhino3d.com/en/guides/scripting/editor-logs/logs-publishbutton.png) This opens the **RhinoCode Logs** window: ![](https://developer.rhino3d.com/en/guides/scripting/editor-logs/logs-window.png) ## Saving Logs To save all the log messages into a file, choose *Logs > Save Contents* menu item and save the logs to disk. By default the contents are saved to a log file named `ScriptEditor.log` -------------------------------------------------------------------------------- # ScriptEditor Command Source: https://developer.rhino3d.com/en/guides/scripting/scripting-command/ Provides an overview on how to edit and run scripts inside of Rhino ## Opening Script Editor To access the unified script editor in Rhino, type `ScriptEditor` in the command prompt and select enter to run the command: ![](https://developer.rhino3d.com/en/guides/scripting/scripting-command/01.png) If this is the first time you are using the scripting features in Rhino, the editor will immediately start initializing the languages. Setting up the Python 3 ([CPython](https://www.python.org)) environment is the most important and normally the longest task here. The editor will display a progress bar showing each initialization task. All editor features are disabled at this point so you should wait for the initialization process to complete. ![](https://developer.rhino3d.com/en/guides/scripting/scripting-command/02.png) Once the initialization is complete, the editor will load all the previously open scripts (if any), and will enable the buttons and menus: ![](https://developer.rhino3d.com/en/guides/scripting/scripting-command/03.png) ## First Script Let's create our first script. We will use IronPython for this example. From the Dashboard (row of buttons at the top of the editor window), click on the **New** button and choose "Iron Python" to create the first script: ![](https://developer.rhino3d.com/en/guides/scripting/scripting-command/04.png) The default script contains a good amount of information under the `NOTES:` multiline comment (or *docstring*). We can skip that for now, as we will discuss these more advanced topics later in this guide. Let's remove those comments. ![](https://developer.rhino3d.com/en/guides/scripting/scripting-command/05.png) Click on the **Save** button on the Dashboard to save the script: ![](https://developer.rhino3d.com/en/guides/scripting/scripting-command/06.png) ## Edit Script Let's type a new line at the bottom of the script. The editor will help autocomplete the names: ```python print(Rhino.RhinoApp.Version) ``` ![](https://developer.rhino3d.com/en/guides/scripting/scripting-command/07.png) ![](https://developer.rhino3d.com/en/guides/scripting/scripting-command/08.png) In this example, the script tab showing the script's name (`FirstScript.py`) now shows a dark circle by the name denoting that the script has been modified but not saved. ![](https://developer.rhino3d.com/en/guides/scripting/scripting-command/09.png) ## Running Scripts Click the green **Run** button on the Dashboard (or hit **F5**) to run the script. The only line in our script that produces a result is the `print` statement. This will print the version of Rhino we are using in the terminal. Click on the **Terminal** tab at the bottom of the screen to open up the terminal tray and see the printed results. ![](https://developer.rhino3d.com/en/guides/scripting/scripting-command/10.png) The Terminal tray has a series of buttons on the top-right side to **Copy** and **Clear Contents** of terminal. Most panels and trays in the editor follow the same design, with buttons for the most-used actions in the top-right corner. ![](https://developer.rhino3d.com/en/guides/scripting/scripting-command/11.png) ## Debugging Scripts The script editor can debug scripts of any supported language. During debug, we can execute the script line by line or pause the execution at certain lines called **Breakpoints** and inspect the values of global and local variables. Let's add these lines to our script: ```python total = 0 for i in range(5): total += i ``` ![](https://developer.rhino3d.com/en/guides/scripting/scripting-command/12.png) Move your mouse cursor to the left side of the line number column on line 12 and click. This should add a red dot and mark that line as a **Breakpoint**. Let's do the same for line number 14: ![](https://developer.rhino3d.com/en/guides/scripting/scripting-command/13.png) The **Breakpoints** tray at the bottom will show all the active breakpoints, and will provide buttons to Clear or Toggle either all of them or individual breakpoints: ![](https://developer.rhino3d.com/en/guides/scripting/scripting-command/14.png) When you add breakpoints, the editor makes a few UI changes and provides a few more utilities for debugging: - The **Run** button will change to **Debug** - **Variables**, **Watch**, and **Call Stack** trays will be added to the bottom tray bar Now click on the green **Debug** button on the Dashboard. The editor will run the script and will: - Stop at the first breakpoint on line 12 - Highlight the breakpoint line in orange and show an arrow on the left side of the line - Highlight the Status Bar in orange to show we are debugging a script - Activate the debug control buttons on the Dashboard - Open the **Variables** tray at the bottom to show global and local variables ![](https://developer.rhino3d.com/en/guides/scripting/scripting-command/15.png) We can control the execution of script using the debug control buttons on the Dashboard: ![](https://developer.rhino3d.com/en/guides/scripting/scripting-command/16.png) From left to right, they are: - **Continue:** continues running the script until it stops on another breakpoint - **Step Over:** executes current line and moves on the next line - **Step In:** if the current line includes a function call, this will step into the lines defining the function code - **Step Out:** if previously stepped into a function code, this will continue executing the function code until control is returned from the function to the calling code and will stop there - **Stop:** stops debugging the script and does not continue executing the rest Click on **Step Over** to see the execution move to the next line: ![](https://developer.rhino3d.com/en/guides/scripting/scripting-command/17.png) Click on **Step Over** one more time and script should stop on line 14. At this point the **Variables** tray will be showing the current values (and their data types) for `i` and `total` variables: ![](https://developer.rhino3d.com/en/guides/scripting/scripting-command/18.png) Progressively clicking on **Step Over**, will continue executing the script and modifying the variables. At each stop, **Variables** tray highlights the modified value by a red dot to left of the variable name: ![](https://developer.rhino3d.com/en/guides/scripting/scripting-command/19.png) ![](https://developer.rhino3d.com/en/guides/scripting/scripting-command/20.png) ![](https://developer.rhino3d.com/en/guides/scripting/scripting-command/21.png) Once the script ends, the editor UI changes back to normal, and the **Variables** tray will show the last state of the variables. The tray will keep these data until the script is closed or another session of debugging is started: ![](https://developer.rhino3d.com/en/guides/scripting/scripting-command/22.png) At any point in time, you can use the **Toggle** button in the **Breakpoints** panel to activate or deactivate the breakpoints. Deactivated breakpoints will show up as gray dots in the editor: ![](https://developer.rhino3d.com/en/guides/scripting/scripting-command/23.png) ## Using Packages ### Python Packages You can specify the packages required for your scripts inside the script source. This creates self-contained scripts that carry all their requirements with them. - Python 3 will use **pip** to install packages from [PyPI.org](https://pypi.org) - **pip** does not support Python 2 anymore so we are limited to the packages used in IronPython - C# will use **NuGet** to install packages from [NuGet.org](https://www.nuget.org) The default script for each language has a NOTE section at the top that describes how to specify the requirements in your scripts. Looking at Python 3 default script, we can specify required packages using this syntax: ```python # r: numpy ``` or ```python # requirements: numpy ``` Let's create a new Python 3 script and add `numpy` as a package and use that in our script: ```python # requirements: numpy import numpy print(f"using numpy: {numpy.version.full_version}\n") for i in numpy.random.rand(10): print(i) ``` ![](https://developer.rhino3d.com/en/guides/scripting/scripting-command/24.png) Click Run, and the script editor will attempt to install the required packages before running the script. This process might take some time and the editor is going to be disabled. Once the packages are installed, editor will continue to execute the script: ![](https://developer.rhino3d.com/en/guides/scripting/scripting-command/25.png) ### Python Libraries (Modules) Another method of adding local packages to Python scripts is by adding their path to the `sys.path`. You can simplify this step by using the `# env:` specifier in your scripts to automatically add a path to the `sys.path` before running your script: ```python # env: C:/Path/To/Where/My/Library/Is/Located/ import mylibrary mylibrary.do_something() ``` ### NuGet Packages The same convention applies to C# scripts. They use a different syntax to specify packages that match the format provided on the NuGet.org page for the package you want to use: ![](https://developer.rhino3d.com/en/guides/scripting/scripting-command/26.png) Here is an example script that uses the [RestSharp](https://www.nuget.org/packages/RestSharp/110.2.0) NuGet package to grab some data from a website. Note the first line of the script specifies the **RestSharp** package version `110.2.0` ```csharp #r "nuget: RestSharp, 110.2.0" using System; using System.Collections.Generic; using Rhino; using RestSharp; using RestSharp.Authenticators; var client = new RestClient("https://httpbin.org"); var request = new RestRequest("get"); var response = client.Get(request); Console.WriteLine(response.Content); ``` ![](https://developer.rhino3d.com/en/guides/scripting/scripting-command/27.png) ## Editor Features Script Editor has other noteworthy features. Here we highlight a few that are used more often: ### Explorer Use the **Explorer** panel to the left of the editor window to browse and edit your local scripts. Click the **Explorer** tab to open the panel, and click on the folder icon on the top-right corner of the panel to browser to a folder: ![](https://developer.rhino3d.com/en/guides/scripting/scripting-command/28.png) Once a folder is set, Explorer will show the scripts in that folder and all subfolders and will activate a new set of buttons to manage the scripts: ![](https://developer.rhino3d.com/en/guides/scripting/scripting-command/29.png) ### Search Click on the **Search** tab to open the Search panel. Searching for a keyword will show all the matches for all the open scripts in the panel. You can click on any matched item to navigate to the script and line: ![](https://developer.rhino3d.com/en/guides/scripting/scripting-command/30.png) ### Help Click on the **Help** tab to open the Help panel. This panel provides a simple method to get help on Rhino and Grasshopper APIs and provides Python modules: ![](https://developer.rhino3d.com/en/guides/scripting/scripting-command/31.png) Use the search field to search for class or method names. The info panel at the bottom provides some info about the method and its parameters: ![](https://developer.rhino3d.com/en/guides/scripting/scripting-command/32.png) If the selected member has online documentation, a preview tab opens in the editor showing the contents: ![](https://developer.rhino3d.com/en/guides/scripting/scripting-command/33.png) -------------------------------------------------------------------------------- # ScriptEditor Command in Macros Source: https://developer.rhino3d.com/en/guides/scripting/advanced-scripteditor-macros/ Provides information on using ScriptEditor command in macros ## ScriptEditor Command Options *ScriptEditor* command has a few options that could be used in macros. Run `_-ScriptEditor` command in Rhino prompt to see these options (`-` is for non-interactive mode, and `_` ensures command works in all Rhino UI languages. [See Command Macros & Scripting](https://docs.mcneel.com/rhino/8/help/en-us/information/rhinoscripting.htm) for more): - **Edit (E):** This is the default option when running ScriptEditor command in Rhino. It opens the standalone Script Editor and initialized all languages. - **Run (R):** Runs given script using the new scripting infrastructure in Rhino 8. It can run scripts or script files of any supported language. - **Browse (B):** Using this suboption, allows to browse and select a script file - Run a script file in a macro this way 👉 `_-ScriptEditor _R "C:\path\to\script.py"` - Run a script in a macro this way 👇. Note that the language specifier (`#! python 3`, `#! python 2`, or `// #! csharp`) is necessary so the command can determine which language to run the script with: ```text _-ScriptEditor _R ( #! python 3 print("Hello Rhino") ) ``` - **Open (O):**: Opens given script file in ScriptEditor - **Browse (B):** Using this suboption, allows to browse and open a script file - Open a script file to edit this way 👉 `_-ScriptEditor _O "C:\path\to\script.py"` ## Script Search Paths Normally you have specify the full script path in a macro like `_-ScriptEditor _R "C:\path\to\HelloWorld.py"` to run the script. However, you can place your most common scripts under a directory, and add the path of that directory to [Rhino File Search Paths](https://docs.mcneel.com/rhino/8/help/en-us/options/files_search_paths.htm). When search paths are set up, you can run `_-ScriptEditor _R "HelloWorld.py"` and the command can find the script under specified search paths. Please note that [Python Module Search Paths](/guides/scripting/editor-configs/#python-paths) are not searched for scripts. They are purely to search and find python modules. -------------------------------------------------------------------------------- # Scripting Languages Initialization Source: https://developer.rhino3d.com/en/guides/scripting/advanced-langinit/ Provides information on initialization process of various language runtimes ## Language Loading The scripting infrastructure (script editor, scripting languages, etc.) is accessed from a variety of sources depending on how it is used. Loading scripting languages uses memory so Rhino only loads languages when they are needed: - Running `ScriptEditor` command loads all the available languages and opens the script editor. Rhino progress bar (in status bar) shows the progress of loading languages: ![]() - Dropping a Script Component on Grasshopper canvas loads the language associated with that component. The component draws a progress bar under the capsule (or on the canvas if it is zoomed out) reporting language load progress: ![]() - Running `-_ScriptEditor _R "path/to/script.py"` command runs the given script file ([See ScriptEditor Macros](guides/scripting/advanced-scripteditor-macros)). Rhino progress bar (in status bar) shows the progress of loading languages: ![]() This is not an exhaustive list and depending on the use case, the interface provides feedback. ## Language Initialization Once a language is loaded it might need a first-time initialization. For example Python 3 prepares its runtime and installs a few useful pip packages. Rhino or Grasshopper interface shows a progress bar reporting initialization progress: - Running `ScriptEditor` loads languages and opens script editor. Script editor's main window shows independent progress bars reporting language initialization progress: ![]() - Dropping a Script Component on Grasshopper canvas loads and initializes the language associated with that component. The component draws a progress bar under the capsule (or on the canvas if it is zoomed out) reporting language initialization progress: ![]() - Running `-_ScriptEditor _R "path/to/script.py"` command runs the given script file ([See ScriptEditor Macros](guides/scripting/advanced-scripteditor-macros)). Rhino progress bar (in status bar) shows the progress of initializing languages: ![]() This is not an exhaustive list and depending on the use case, the interface provides feedback. ## Scripting Root Directory All languages initialize their runtimes under the scripting root directory. This is usually placed under: - `%USERPROFILE%\.rhinocode` on Window - `~/.rhinocode` on macOS (and other Unix-like) For example, in Rhino 8, Python 3 (CPython) and Python 2 (IronPython) deploy their runtimes and modules under these directories respectively: - `~/.rhinocode/py39-rh8/` for Python 3 - `~/.rhinocode/py27-rh8/` for Python 2 Alongside language runtimes, root directory also holds: - `logs/` for application log files - `stage/` for temporary scripts - `libs/` for cache for script libraries - `component.json` for configurations for Script Components - `editor.json` for configurations for Script Editor ### Changing Root Directory It is sometimes necessary to change the location of this directory. As mentioned above, Python 3 deploys its runtime in the scripting root directory and needs to run Python executive binary (`python.exe` on Windows) to start the language server and install packages from PyPI.org If this is something you need to block for security reasons, you can change the scripting root directory to a different location with execute priviledges. To change the scripting root directory: - Open Rhino - Do not interact with the scripting tools, meaning do not open ScriptEditor, Grasshopper, and do not run RunPythonCommand - Go to **Rhino -> Tools -> Options -> Advanced**, and override the default empty value of `RhinoCodePlugin.RootPath` with your desired root directory path. This needs to be an absolute path and must be writable with execute priviledges for user running Rhino - Close and Reopen Rhino - Open ScriptEditor and let it initialize languages in this new root directory - Open Grasshopper and drop a Script component on the canvas so the `component.json` file is also created in root directory ![](https://developer.rhino3d.com/en/guides/scripting/advanced-langinit/01.png) ### Resetting Root Directory To change the scripting root directory back to default, clear the override set on `RhinoCodePlugin.RootPath` in **Advanced** options as shown above. -------------------------------------------------------------------------------- # Terminal Panel Source: https://developer.rhino3d.com/en/guides/scripting/editor-terminal/ Provides information on terminal panel in script editor ## Terminal Panel Terminal panel shows printed output of last executed script. You can toggle the terminal using the toggle button at the top-right of editor window. The two *Copy* and *Clear* buttons can be used to copy or clear the terminal contents. ![](https://developer.rhino3d.com/en/guides/scripting/editor-terminal/terminal.png) ## Line By Line Printing By default, the output of a script is captured during execution and printed on the terminal all at once when execution is completed. This way the script is not slowed down by frequent UI updates. When debugging a script, this behaviour changes and script output is printed on the terminal as the script is running. This helps seeing the messages being printed from your script during debug. Sometimes it is desired to see the script output during normal execution. An examples is when a script is processing a series of files and needs to print its progress to the terminal. Waiting for the script to end without seeing any reports negates the point of reporting progress. Use the *Line by Line* toggle on the terminal header to print script output during normal execution. You may also want to toggle auto-minimization from *Run > Toggle Minimize Editor On Execute* menu item to keep editor on screen: ![](https://developer.rhino3d.com/en/guides/scripting/editor-terminal/terminal-linebyline.png)