Scripting and Computational Geometry
Scripting and Computational Geometry
Introduction
In this document I collected some basic information about scripting, hoping that to help some people interested in finding an
easy way to access computation for design purposes. The thing is that I cannot explain everything because either it will take
too long (and it would be painful for both of us) but also because there are many things that I am not aware of. Anyway this
paper runs fast and in breadth (rather than depth), it covers pretty much most of the commonalities (yet banalities) of coding,
so after this maybe you will be a better coder (I cannot promise for anything else; it's up to you though).
I will use the Rhino as a CAD platform because it contains all essential back end/front end of computational geometry and also
it is an easy and relatively open environment for experimentation. Rhino uses vbscript as an embedded scripting language.
Even though vbscript is not one of the most powerful computer languages, it has wide range of applicability, it is fairly easy to
learn and pretty flexible. Anyway, once you know one language it is easier to jump into any other.
Structure
It turns out that most of the computer languages are founded in only four basic conceptual components. Even though the
syntax changes from one language to the other, these concepts remain the same. So, once you learn one language
adequately, it will be easier to access any other.
Information in computers is manipulated through variables. A variable is something like a container of a value (in the memory
of the computer) that we can access and modify by using a text-name in a piece of code. So, a variable just allows you to
store and access a value. In vbscript you use the keyword "dim" to declare a variable. Even though you are not forced to do it
in vbscript, I think it is a very good idea to do so.
dim identifier
Where, identifier is the variable's name. Sometimes people use the term identifier instead of variable. There are two kinds of
rules for naming new variables:
This means that if these rules/conventions are broken (by you), then there will be error(s) reported (for sure). You will get
pissed off, might want to break stuff, etc.
[ERROR]
dim my variable
dim this is another one
[TIP]
use the underscore character "_" instead..
[CORRECT]
dim my_variable
dim this_is_another_one
b. must not start with a numerical digit (0,1,2,3,4,5,6,7,8,9)
[ERRORS]
dim 12monkeys
dim 2001_a_space_odyssey
[TIP]
but you can use digits after the first character of the
variable's name...
[CORRECT]
dim apollo13
dim friday_the_13th
[ERROR]
dim tango+cash
dim sub-traction
[TIP]
in general avoid using symbols in variables' names
except for underscore
[ERROR]
dim _not_valid_
dim @stake
[ERROR]
dim dim
dim function
[TIP]
programming languages do not reserve too many words for
key-words. Some of them are: dim, sub, function, for, next
while, until, do, call, to, redim, end, if, then, is, as
These are not actually rules, the ones that cause all sorts of strange errors to be thrown if violated, but they encapsulate good
practices that people decided to use after many painful hours/years trying to find what went wrong, and turned out to be a
sketchy name given to a variable, that was misspelled, conflicted or reminded of something else etc...
dim polygon_index
dim old_vertex
dim new_vertex
dim the_third_day_of_the_month
dim once_upon_a_time_blah_blah_blah
c. even though it is not mandatory, avoid capital characters
dim PaInFuL
dim PEOPLE_KICK_ME_OUT_OF_CHAT_ROOMS
dim FirstVariable
dim secondVariable
Numbers
Numbers are the intrinsic data type of all computer languages. Computers, actually understand only integer numbers (-1, 0, 1)
but they can simulate real numbers (-0.5, 0.0, 0.5). In vbScript there is no distinction between a real and an integer because
the language takes care of the conversions. So, here is how to use numbers:
Already you have enough information to make and run your first script. A script is a text file that is loaded and
executed by the host-application. You can create and edit a script file in any plain-text editor, such as the notepad. A
better solution is to use a specialized code editor which highlights the keywords of the script and make code more
legible. There are many code editor on the net that you can download.
Assuming that you already have a text editor, you can create your first script by just copying the previous code. The
only trick involved in this code editing process is that you have to save the file by giving and extension that the host
application requires. VbScripts for the scripting host or the internet explorer have to use the extension ".vbs", whereas
scripts for rhino have to use the extension ".rvb". Now, copy the previous piece of code in your editor and save it as
"[Link]". (Notice, that you have to set the "Save as type" option to "All files" in notepad's save dialog box because
otherwise it will save the file as "[Link]"). Open rhino and either select the menu Tools/RhinoScript/Load... or just
type lo and hit enter. A dialog box will pop up where you can click the add... button and locate the script file you just
saved. Once the script is located it will appear in the scripts list. If you want to save the file you just opened in the list,
select the "Save list" option. In order to run the script just select it from the list and hit the load button. Hmmm, nothing
happened. Well you just declared a few variables. There were not procedures or functions called in the script so it just
did nothing.
Simple Expressions
In vbScript each line of code defines a single expression. An expression is the simplest form of computer command/instruction.
Expressions are evaluated from right to left, which means that the computer reads the right part first, executes its, and then
handles the left part. For example, the most basic expression type is value assignment in a variable.
dim var
var = 1
var = var + 1
The previous sample shows a variable declaration and its assignment, a compound version of the same type of expression and
finally a way to increment the value of a variable. The only difference between the first two formats is the colon symbol which
allows two or more expressions to be in the same line of code. Now, coming back to the left and right parts of an expression;
there are a couple of things to notice. First, the symbol of equality "=" does not imply any sort of mathematical equation like
"f( x ) = 0" or "x 2 + 2x + 1 = 0" that we are trying to solve, but rather a straight assignment of value. In other words, the results
of the addition "5 + 5" will be assigned in the variable "var". Remember that a variable is a rather a temporary container of an
accessible, in contrast to unknown, value.
Now, once this rule is set, it is easier to identify the meaning of left and right parts of an expression. Since, a value has to be
first calculated before being assigned, it is rather obvious that the right part of an expression "5 + 5" has to be prioritized over
the left "var". In the second example, with the compound form, it is also important to notice that "1/2" is actually an expression
rather than a numeric value. As you might recall, computers know only about integer and real numbers. Therefore, "1/2" is not
directly a number but the result of the division "0.5" is. Finally, the third example is perfectly correct even though you might
think "var = var + 1<=> 0 = 1". It simply means add one to whatever "var" contains and re-assign it in the same variable.
Implications: You cannot have a constant value on the left hand of an expression! Observe the following example. This is a
very basic error. Try it and observe the error message: "Expected Statement at Line Number ##". Some error messages like
this don't make sense at all, so in the beginning is a little bit frustrating to figure out what went wrong.
Code is not the most user-friendly medium because even though it is explicit and readable, it contains a lot of jargon
and personal conventions that makes it difficult for most people to parse. Even you will have trouble to figure out
what were you trying to do with a piece of code that you haven't seen for a while. Comments is text, as code is text,
which is ignored during the execution of a program. Comments in vbscript are considered the lines which start with a
single quote (apostrophe) or in general whatever text follows an apostrophe up to the end of the line. Comments
have only one reason: to remind you and inform other people how you program / script works. Use comments for
documenting the ideas behind the code, rather then the code in itself, and your conventions and mannerism
concerning of how you interpret data/information. If you can make your code clear and legible by picking good
naming practices then that's even better. Keep in mind that too many comments become annoying after a while.
Booleans
Booleans are numeric values just as the previous but there is a convention for their significance. A boolean represents a
true/false, yes/no, on/off kind of value. By convention a boolean can be either "true" or "false". There is no special way to
declare a boolean but the word "true" and "false" are reserved as keyword by the language. Another detail with booleans is that
by convention zero is interpreted as "false" and non zero as "true". There is a small catch with booleans which originates from
older programming languages. False is usually numerically represented as zero, while true is may be any number other than
zero (typically minus one). Just keep in mind that false = 0 and true <> 0.
Strings
Strings or alphanumerics (please don't use this word it's corny) are sequences of characters, numbers and symbols, that is,
what we usually call just-plain-text. There are a couple of difficulties in getting along with strings. First you might ask where is
text is coming from while as mentioned previously all computer information is numeric. Actually, text is composed by numbers
and each character has a unique numeric value defined by some standard, ANSI or UNICODE for instance. The great thing
about vbScript is that you don't have to care at all about all these, except if you want to for some reason. Now, because there
is an invisible layer between text and numbers, you cannot directly change the content of a string as well as mix it with other
types of information, such as numbers.
Another problematic topic is the common confusion between a text-based script and a text-based string. A script is a sequence
of commands that a computer program converts into a machine friendly scheme (p-code) and then executes. A string is a data
type that encapsulates a piece of text-based information which might describe code, as well as anything else, but it is not
directly something executable.
'' Declaring and assigning strings
''
dim str
str = "this is a string"
[ERROR]
dim careful: careful = "bad " idea"
In order to declare a string, you just follow the previous syntax. For assigning a string literally you must use the double quotes
scheme. The characters between the pair of double quotes is the actual string value. A double quote inside a string literal must
be followed immediately by another one, otherwise the script interpreter cannot understand where the string starts and ends. If
you use a code editor that highlights strings, it is easy to notice a fix errors like this. An empty pair of double quotes is a
perfectly valid string also known as the empty string.
Notice that the "text" variable's name has nothing to do with the "text" string value. The variable's name lives inside the code
space/time, that is, while you are typing a piece of text. This is also called edit-time. The variable's contents live after the code
is compiled into machine code, where there are no variable names anymore, and while the script in running, aka run-time. So,
there is no connection (for now). The same rule applies to the "number" variable. It does not contain a number, but its textual
representation. So, again you have to treat it as a string value.
There are many things that you can do with strings which usually fall under the topic of text manipulation. Text is a very
powerful medium because it is human-friendly, compared with raw numeric data, and accessible in general. You can create
text files describing any other type of information, given that there is a standard or custom-made set of conventions for the
translation between them. For instance, web pages are text-based governed by the HTML standard, which the browsers are
capable of translating into graphics.
[ERROR]
dim noconv: noconv = "number: " + num
dim incomp: incomp = "number: " + 5
dim no_way: no_way = "are you" - "nuts"
The simplest thing you can do with strings is to join/concatenate them. The "str" string after being executed will contain "one
two three" as its value. Adding strings is not always easy, depending on the programming language. Hopefully, vbScript makes
it easy by reusing the "+" symbol for signifying string concatenation.
While this is handy, as mentioned previously, you cannot just add a string and a number. For this reason you have to use the
"cstr( )" function which is provided by the language. A function is reusable piece of code located elsewhere. A function is a like
a black box that you feed/pass information, it processes them (you don't have to know how), and it gives back/returns some
sort of a result. Lets just say that "cstr" is the common way to convert between numeric data types and strings.
Notice also the error cases. In the first and second instances, the conversion function is missing and the interpreter will
complain. The last case illustrates that because adding strings using "+", which is used usually for numbers, doesn't mean that
you can simply subtract or multiply text (even though it might be interesting).
Like all computer languages, vbscript comes with a collection of common integrated functions. Another common
theme is people generously offering their code to the public domain. In both cases there is usually an accompanying
documentation that explains what does what. It makes sense since code is not the easiest text to read. These
documentation usually follow some standard conventions which describe the usage of the code.
You can browse the official guide and reference of vbscript which can be found at MSDN. In the meanwhile, it might
be confusing to understand the conventions of documentation. So, here is a typical example:
Prototype
function mid( string, start, [length] )
Parameters/Arguments
string
A string, portion of which is going to be given back/returned
start
The beginning character from which mid starts copying. If start is greater than the length of the input string in
characters, then mid returns an empty string.
length
The number of characters to copy from the start position. If it is not defined or larger than the length of the input
string, then mid copies all characters from the start position to the end of the string.
Returns/Result
Selects a portion of the input string, copies it and returns it.
Example
dim str: str = "first part, second part"
dim prt: prt = mid( str, 12, 11 ) '' The prt string will contain "second part"
In plain english
The prototype section informs you about the name of the function and the number of its parameters (a.k.a.
arguments) and their syntactics. In other words the specific function is called mid and expects you to pass three
parameters. But wait! The square brackets imply that the parameter inside them may be omitted (typically in cases
that it can be inferred). These optional parameters always come last in the parameter list.
The following section explains the meaning (semantics) of each parameter usually accompanied by the extreme
cases of the use of each parameter and how will be interpreted in that weird case. Typically the parameters'
description are accompanied by a short explanation about the kind of information they are expected. For instance, the
first parameter in the example has to be a string. In any other case there will be an error.
Which brings us to the returns section, in which is described the process of the function and its result (for better of
worse). In the example, the function requests a string, a starting point and a length of characters to copy.
User input
Strings are extensively used in rhinoscripting for many purposes. A typical one is when you are requesting some information
such as picking a point from the viewport. The string is directly transferred to the command line and the program gets into an
idle mode until you give it what it asks or escape the command. You can also give some feedback of what your script is doing
by sending text/strings either to rhino's command line or pop up windows.
Sending commands
There are other more important uses, such as sending rhino commands. Examine the following examples. In the first two lines
there are two commands sent to rhino. The first one is for selecting all visible objects and the second one for deleting them. As
you can see there is no practical difference between typing the commands in the application than sending them through a script
(wrapped in a string).
The second couple of lines draws a circle and a square. The syntax for writing drawing commands is pretty straight forward: it
is exactly what you have been doing if there was no mouse but just a keyboard in you disposal! You would have to type the
name of the command, press enter (or hit the space bar), enter a point (x, y, z), hit again enter or space and finally give a
radius (or another point for the square). Well, in this case you don't have to hit enter for the last parameter. The circle
command has an extra parameter which defines that the last parameter is a radius rather than a diameter. Notice also that the
points described in the string must not have spaces between the coordinate components (x,y,z) but rather a comma as
separator.
For using commands in this fashion just run them once in rhino (just press/select a menu item or a toolbar button) and observe
the command sent to the command text window. Then write down the options and the sequence of what is asked and put all
these in a string and use the command vbscript function.
The next example is a little bit more complicated but that's all there is to sending commands through strings to rhino. The first
two lines follow the previous rules but the third one has a couple of weird things going on. The hyphen in the beginning of the
command inhibits the dialog box with the options popped up every time you loft curves. A popup window grabs the control
focus from the main application window and stops the script from executing unless you select "ok" or "cancel" (or whatever).
Commands that pop up windows can be bypassed using the hyphen syntax and your script can be totally automatic (no user
intervention unless needed). Even the open, save and export/import dialogs can be bypassed by these means. The other
interesting thing about this command is the two "typed enters" inside it (the string). These force the command to stop
requesting more information and just do what it has to do.
The final line contains one more syntax trick you might want to know for sending commands. Because the space character is
understood as hitting the enter key (or the right mouse button) in rhino, when you want to send a command that contains a
space " " you will have to actually wrap it around double quotes. For example if you want to plot some text that contains
spaces then you must use the double quote scheme or some weird error will happen. Now, because a double quote is a
symbol of vbscript (for strings) you have to double it as explained previously. In order to double check your command and
figure out what rhino is gonna see imagine that you remove the outer quotes from your string and make all double double
quotes singles. The text that will remain has to be a valid command that once you type (manually) in the rhino command line it
will work with out any problems. Another example of this kind is the case when you want to save/export or open/import files
from the command line: you will have to quote the filename in order to be sure of what is gonna happen. For instance if the
filename is something like (c:\folder\drawing.3dm) then everything is ok. If the filename is (c:\my documents\the final
drawing.3dm) then you are in trouble if you don't put the filename is quotes because it just contains these ambiguous spaces.
call [Link]( "-text 0,0,0 ""a b c""" ) '' Ploting some text
Even though sending commands directly to rhino by using the command function is not a very, lets say "elegant", way of doing
things, there are a few more details that you might want to be aware of. Whenever you want to create commands by
incorporating data (from variables) you have to convert everything to strings and add/concatenate them together. Remember
that you cannot directly add a number to a string. More over you can see that the conversion may be a little bit obscure
sometimes. In the first example we have to convert all numbers to strings using the cstr function and then append the strings
into a compound command. In the second example the numbers are reals which causes a problem if you use the cstr function
because it converts them to strings alright but it uses a comma instead of a dot to separate the decimal from the fractional
parts. This would normally cause a problem because rhino understands commas as separators of point coordinates, but thanks
to the formatnumber function we can convert real numbers to strings correctly for rhino's conventions.
'' Careful
''
dim x: x = 1.1
dim y: y = 2.2
dim z: z = 3.3
The most important use of strings in rhino-scripting is for object identifiers. An object identifier is a unique id assigned by rhino
for each object. The metaphor of a rfid or barcode is suitable for explaining the purpose of these sort of identifiers: they are just
some sort of internal names for objects. Even though you cannot use them directly, you can manipulate the objects they
identify passing them as parameters in rhino functions. If you know the id of an object then you can access it, get its
information, modify it and even delete it. Most of rhino specific commands either request from you to pass them an object
identifier or just give you one back.
The following example illustrate the difference of an object, its identifier, and its internal data. The first line of code uses a rhino
function (not command) for asking you to pick a point object from the viewport. A point object is just a plotted point you would
"draw" by clicking the point toolbar button. The function gives you back the identifier of the point (see documentation). Then the
next function passes the object's id to the pointcoordinate function which give you back the actual coordinates of the point. You
might wonder why is this distinction between the object, its id and its data. There are many reasons some of which are
illustrated in the next few lines. The next line asks again for user input, but this time not for a point object but just for a point in
space / viewport. In other words it asks you click somewhere in space so it can give you back its coordinates. The getpoint
function does not create a point object but the next one, addpoint actually plots it in space. The same function give you back
the id of the newly created object, which you can then pass on to the objectcolor function to set a color to the point.
A few things may be more clear now; Initially, each object has a unique id which has nothing to do with its actual data but only
distinguishes it from all other objects. You can access an objects data if you know its id. An object is more complex thing that
its mere information, for instance the point except of its geometrical information, the coordinates, it has a color, a layer, a
hidden/shown tag and many more.
Arrays
Arrays are a bit more complicated form of data. They actually represent a sequence of variables. I use the term sequence of
variables in order to stress the idea of the container. So, an array has a number of cells/items/place-holders that enables us to
put/store/assign values in some sort of sequential order. There are two types of arrays, linear/one-dimensional and matrix-
flavored/multi-dimensional. Here is an example of an array with nine cells. Each cell can store a variable whatsoever, even
another array. Also notice that the cell numbering start always from zero rather than one.
0 1 2 3 4 5 6 7 8
Another example of a two dimensional array with 3 by 9 cells. Two dimensional arrays are also known as matrices. The same
rules apply here, but beware of the dimensions: a three dimensional array would resemble a rectilinear box of cells and a four
dimensional array is virtually impossible to be understood, maybe as a four-dimensional thingy (hyper-box, polychoron of
cubes etc). Most of the times people use linear and planar arrays. so we will just stick to them.
0 1 2 3 4 5 6 7 8
Arrays are important because they allow to create groups of values, which by convention represent something more than
primitive data. A point coordinates are represented as an array of three elements (x, y, z). The convention in this case is that
the first element is "x", the second is "y" and the third is "z".
x y z
So, in order to assign something in a cell you will have to use the "name of array + index number enclosed in parentheses"
scheme.
And in order to get the value from a specific cell, you will have to use the same scheme, but this time the array will have to
appear on the right part of the assignment.
Remarks:
An array may not be necessarily homogeneous (in the types of data that each element/cell contains) but homogeneity is
usually preferred for avoiding confusions.
You can also use the dim keyword to declare an array but redim is usually preferred. If you want to know the reason then: a
dim keyword/statement makes/defines/declares a static array, i.e. you cannot change the number of cells once the array is
declared, whereas by using redim you can do this (if necessary) later on. So, because redim is more general it is better to be
used in any case.
This an easier way to make arrays as long as you know how many elements they will have. As mentioned before, an array can
contain another array(s). You can make them by using the array function.
Even though the semantic of an n-dimensional array and an array containing arrays are the same, the language doesn't treat
them as equal. The following two arrays may look the same but they are of different kinds. The first one is called n-dimensional
array, while the second is know as jagged.
points( 1, 0 ) = 1.3
points( 1, 1 ) = 4.25
points( 1, 2 ) = 123.33
points2( 1 )( 0 ) = 1.3
points2( 1 )( 1 ) = 4.25
points2( 1 )( 2 ) = 123.33
0 1 2 j 0 1 ... i
0 1 2 0 1 2 0 1 2 0 1 2
0 0.34 4.0 34.3 ... ...
1 1.3 4.25 123.33 ... ... 0.34 4.0 34.3 1.3 4.25 123.33 ... ... ... ... ... ...
Now that you have a basic understanding of most data representations, and a glimpse of how things work in rhino, it is time
for some basic geometric ideas which are independent of software and computer language. Because we are working with a
specific programming language and a specific software though, the examples have to adapt to the local conditions. Since,
rhino understands points as arrays with three real number elements we will make three convention variables which will help
making the code a little bit more legible. I use capitals letters for naming variables that I will not change their value and
furthermore they will remain global and constant through out this document.
It is important to understand that the whole geometrical construct that computers know how to do is based on the idea of the
point: identity of position. If you know how to handle points, then you are able to handle everything else. For example lines,
planes, meshes are all point constructs, NURBS too even though there is an indirection (control points). Modifying a higher
level object always comes down to manipulating its points. So, here are the most basic yet important point arithmetics.
A global coordinate system defined A point in space defined by Projection of point on planes The (x, y, z) coordinates represent
by three axes coordinates defined by the axes the distance of the point per axis
from the origin
The distance between points or length of the linear segment they define is given by the square root of their coordinate
difference squared. You can think of it as the three dimensional version of the Pythagorean theorem.
redim midpoint( 2 )
midpoint( VERTEX_X ) = ( pa( VERTEX_X ) + pb( VERTEX_X ) ) / 2.0
midpoint( VERTEX_Y ) = ( pa( VERTEX_Y ) + pb( VERTEX_Y ) ) / 2.0
midpoint( VERTEX_Z ) = ( pa( VERTEX_Z ) + pb( VERTEX_Z ) ) / 2.0
redim centroid( 2 )
centroid( VERTEX_X ) = ( pa( VERTEX_X ) + pb( VERTEX_X ) + pc( VERTEX_X ) ) / 3.0
centroid( VERTEX_Y ) = ( pa( VERTEX_Y ) + pb( VERTEX_Y ) + pc( VERTEX_X ) ) / 3.0
centroid( VERTEX_Z ) = ( pa( VERTEX_Z ) + pb( VERTEX_Z ) + pc( VERTEX_X ) ) / 3.0
Translation
Whenever you move some geometry from one place to the other, you usually have to specify a starting and an ending point.
The mathematical expression for moving stuff around is translation. Translation can be defined in multiple ways: distances per
direction / axis, a starting and an ending point, a vector. We are used to the second definition because points are geometrically
representable whereas directions and vectors are a little bit more fuzzy. The easier way computationally for implementing
translation of a point/object in space is by summing its coordinates (x, y, z) with distances per directions / axis (dx, dy, dz). In
the case of two points, we can subtract the coordinates of the reference points (endpoint - startpoint) in order to get the
distances per direction and apply the previously described sum of the coordinates. The last case of the vector is actually a
generalization of the previous. A vector is just the distances per axis between two points (dx, dy, dz).
Now if we symbolize a translation as a function "t( )" of a point and we have two translations "t1" and "t2" and a point "p", then
we can infer from the previous information that "t1( t2( p ) ) = t2( t1( p ) )", because "dx1 + dx2 + x = dx2 + dx1 + x" etc. In
other words, the sequence that you apply translations if indifferent. You can also understand that "t + t -1 = 0", which means
that if you move a point somewhere, and then move it back, then it is like you didn't move it at all.
Two points define a translation; the The difference of their coordinates You may think a point's In this case you may think
order of points defines the direction define the distances coordinates describing a translation translation as a shift of the
of the origin(0, 0, 0) to (x, y, z) coordinate system to a new
position
dim s_point: s_point = array( 1.10, 2.34, 3.72 ) '' start point
dim e_point: e_point = array( 3.44, 5.90, 6.89 ) '' end point
dim point: point = array( 7.55, 8.77, 9.37 ) '' the point to move
dim point: point = array( 7.55, 8.77, 9.37 ) '' The point to move
dim moved: moved = array( _ '' Create the translated point
point( VERTEX_X ) + vector( VERTEX_X ), _
point( VERTEX_Y ) + vector( VERTEX_Y ), _
point( VERTEX_Z ) + vector( VERTEX_Z ) _
)
Scaling
While scaling is as easy as translation, rotation is a bit complicated and I will skip it for now. Scaling implies a multiplication
(like 1:200 or 1/16'' scale of a model or plan). For scaling you just have to multiply a points/objects coordinates by a scaling
factor. If the factor is same for all axes, then it is called uniform scaling, otherwise it is also known as non-uniform scaling.
Actually the commands scale1D and scale2D, just keep the scaling factor for two or one axes (respectively) zero while allow
the other(s) to change. Again, there are many ways to define a scaling action: by a single factor, by reference length and new
length, by reference points etc.
There is though a tricky part with scaling, which was implied in the translation transformation but it didn't affect the results. A
scaling must be defined according to a point of reference. If you just multiply the (x,y,z) coordinates of a point with a scale
factor (x * f, y * f, z * f), then you have to understand that you actually imply a scaling in reference to the origin point of the
global coordinate system. When CAD software ask you for a reference point they translate the coordinate system to your point,
they perform the scaling multiplication, and then move the coordinate system back in place (all these behind the scenes).
Again if we symbolize a scaling as a function "s( )" and we have a couple of them "s1" and "s2" and a point "p", then we can
infer that "s1( s2( p ) ) = s2( s1( p ) )", because it all comes down to "f1 * f2 * coordinate = f2 * f1 * coordinate", where "f1" and
"f2" are the scaling factors for "s1" and "s2", respectively. But if we have a point "p", a translation "t" and a scaling "s", then "t(
s( p ) ) <> s( t( p ) )", that is the order of appling transformations is important.
Projections of a point (and a vector) Scaling through the origin of the Scaling is calculated by a multiplication of the coordinates of a point
to the planes of the coordinate coordinate system. by a scaling factor.
system.
point( VERTEX_X ) = point( VERTEX_X ) * scale_x '' Scale the point: notice that
point( VERTEX_Y ) = point( VERTEX_Y ) * scale_y '' the scaling is uniform
point( VERTEX_Z ) = point( VERTEX_Z ) * scale_z
'' Since we already know how to scale about the origin (0,0,0)
'' we can make our life easier by assuming that the reference
'' point is the origin of another coordinate system. Then if
'' we make its coordinates (0,0,0) we can scale in the same
'' fashion as before. Hmmm... which transformation can zero
'' out the coordinates of the reference point? A translation
'' with the negative coordinate values!!!
''
dim origin: origin = array( _ '' Redundant but just for clarity...
refpnt( VERTEX_X ) - refpnt( VERTEX_X ), _
refpnt( VERTEX_Y ) - refpnt( VERTEX_Y ), _
refpnt( VERTEX_Z ) - refpnt( VERTEX_Z ) _
)
dim moved: moved = array( _ '' Now lets move the other point too
vertex( VERTEX_X ) - refpnt( VERTEX_X ), _
vertex( VERTEX_Y ) - refpnt( VERTEX_Y ), _
vertex( VERTEX_Z ) - refpnt( VERTEX_Z ) _
)
While the previous example seems a little bit complicated, a little bit too much code for too little, painful and not meaningful,
etc, it captures a very important geometrical idea that you might want to know even you deside to drop computation right
away. The previous transformation is also known as global to local, local to global coordinate system transformation. The
process conceptually is pretty simple: 1. you push down the current coordinate system by a translation to a local origin, 2. you
do stuff as if you were in global coordinates and 3. you undo / pop the translation back to the original coordinate system. This
method is very important if you want to implement relative motion in space. Inverse kinematics, for example, push and pop the
coordinate system along points in space, which happen to belong to a skeleton-structure. Now, if this sounds complicated wait
until you learn about rotation ;) On the other hand if you just wait a little bit you will find out how to suppress all these
geometrical / computational paraphernalia by modularization and abstraction, that is, you will never have to write such long
coordinate manipulations again.
Since the right part of any part of a line of code/expression is calculated first, there are some more strict rules that control what
gets first evaluated and what next. These rules are called precedence resolution rules.
(1 ) Expressions enclosed in parentheses are always evaluated first. More specifically if you use multiple parentheses
(pairs), the most inner stuff gets calculated first and then the most outer. So, the general rule is: the computer reads/runs from
inside to outside.
'' Examples
''
dim x: x = 2 + 3 * 5 '' The result is... = 2 + 15 = 17
dim y: y = ( 3 + 5 ) / 8 '' The result is... = 8 / 8 = 1
dim z: z = ( -2 * 1 ) / ( 3 + 5 ) '' The result is... = -2 / 8 = -0.25
Conditional processing
The next key concept in scripting is flow control of execution / conditional processing. The general idea of decision making is
simple: if this is true then do that or under these conditions the preferable action is this one. It is not that far away coding these
expressions from writing in english.
'' if then else
''
if( condition_is_true )then
'' do something
else
'' do something else
end if
The most basic way for controlling a program's execution (under specific criteria) is by using the if-then-else trilogy. This
applies to all programming languages.
Once an if is found during execution, the computer evaluates/calculates the value inside the parentheses.. if the result turns out
to be true (remember booleans) then it "jumps in the if block" and performs the code that comes in the following lines until it
reaches an else keyword or the end if keyword which means: that it, jump out of the if / end if and continue as usual.
Now, if the conditions turn out be false, then it skips all lines of code until an else keyword is found. Once an else is found then
the computer jumps in the else / end if block and runs the lines that follow the after the else until the end if, where it jumps out.
After end if everything goes as usual i.e. line by line execution. You can actually omit the else part if you don't care what
happens in any other case and make a simple if-then-end if branch. So, only if the conditions are met, then something special
happens. In any other case what exists between if-then and end-if will be just skipped/ignored.
You can use as many if-then-(else)s as you want, sequentially or one inside the other... (this is called branching or nesting).
The crazy ritual of putting leading spaces before ifs (commonly know as indentation) helps a bit visually to follow the flow just
by looking at the code. It has no other meaning but try to do it or you will be sorry.
Conditionals (themselves)
Conditional tests are like right parts of an assignment; A part of code/expression that is calculated, and in every case it must
come down to a true of a false result (I mean a boolean value true or false here).
'' conditionals
''
dim x: x = 4
dim y: y = 5
if( x = y )then
'' ( equal to ) it is not going to happen
end if
These are the most usual conditional tests. They are actually pretty straight forward excluding the not-equal test, which might
be a little bit weird (as a symbolic notation). If you want to test more than one conditions in the same time you may join tests by
using the boolean operators instead of nesting if's one inside the other.
and true false or true false xor true false not true false
true true false true true true true true false false true
false false false false true false false false true
Lets use the conditionals for some realistic case now. First will do a point equality test, then a point approximation test and
finally a point proximity test. Two points are equal only if their coordinates are equal.
While this might be a reasonable testing in math, it is not always the case with computational geometry because real numbers
are not very perfect: they have some precision but not absolute precision. Sometimes it is not enough to test for equality of
points in this way, because there may be a fractional digit that went off, by a little bit, due to some rounding problem. You will
just get a wrong result in that case (a.k.a. you have encountered a numerical stability problem). For simple geometric
manipulation the coordinate to coordinate testing is enough, but for intersections for instance it is very risky to depend on
simple tests. How would you do that then? You will have to rephrase the test. Instead of testing for equality "coordinate1 =
coordinate2", you will first test if the difference of the coordinates is zero "coordinate2 - coordinate1 = 0". Then you will have to
use a precision threshold number that is really really close the zero but not zero, say "0.00001". Then you will have to reformat
the test "coordinate2 - coordinate1 <= 0.0001". Finally, in order to be safe in case the coordinates are negative numbers you
will have to ensure them with an absolute value wrapping: "| coordinate2 - coordinate1 | <= 0.00001"
Proximity testing is actually very simple: you just test if the distance between two points is less than a proximity distance. You
can also picture it as a test of a point being inside a sphere.
Iterative processing
Well, what computers are the best with, is doing the same stuff, the same way, many many times per second. Here is how:
they repeat themselves. There are two kinds of loops in vbscript which are actually common in every programming language:
the for/next and the do-while/loop.
This translates to: when "the computer" sees the for keyword in the beginning of a new line of code (always in the beginning)
then it assigns to the control_variable the intitial_value just like in an assignment statement. After that it jumps inside the loop's
body and starts executing the lines below, running whatever exists in between for and next. When it reaches the next keyword,
it suddenly jumps back to the line which contained the for everything started from, it adds one (the number 1) to the control
variable and continues in the same fashion. Well, the whole thing is repeated as many times as needed in order the control
variable to become equal to the final value. When this happens the program "goes out of the loop" and executes whatever
code is left after the next keyword.
The rules:
(1 ) The control variable is always a number, because in each loop it increments by one. So, if you try to give a string,
guess what, the program will start crying.
dim control_variable
for control_variable = "A" to "Z"
'' This wont work, at all
next
(2 ) You have to declare the control variable somewhere before the for loop. And of course the names selected here
(control_variable, intial_value and final_value) where chosen in order to protect the witnesses (i.e. it is only an example). You
can use constant numbers / literals instead of the initial and final variables but you cannot do that with the control variable.
dim index
for index = 0 to 100
'' This will run for 101 times
next
(3 ) The control variable is just as any other variable, i.e. you can use it inside the for/next loop for doing stuff.
dim j: j = 0
dim i
for i = 0 to 100
'' This will sum (in j) the numbers between 0 and 100
''
j = j + i
next
(4 ) You can even fool around with the control variable (say add stuff) but avoid doing this unless you have good evidence
that this action will lead to a better world. In any other case see below for the while/loop which what you want.
dim i
for i = 0 to 100
'' I wouldn't do such stuff
''
i = i + 1
next
(5 ) As with if/then you can nest as many loops as you want; just be careful to match the for/next pair correctly. Again
indentation of the code is used for quickly identifying which for pairs with which next.
dim i
for i = 0 to 100
dim j
for j = 0 to 100
'' Keep in mind that this will run 101 * 101 = 10201 times!
next
next
(6 ) Sometimes you may want to exit a for loop before it actually ends. You can do this by using the exit for command. A
typical reason for exiting a loop before it ends is because some secondary condition was met, and it is not necessary to keep
iterating any more.
(8 ) You can loop backwards by setting up negative stepping and you can also step through real numbers by setting a
fractional step factor.
do while( condition_is_true )
'' process stuff
loop
'' alternatively
do
'' process stuff
loop until( condition_becomes_false )
What's different about this kind of loop is that there are no control, initial and final variables. What controls when to stop
looping here is a conditional test, just like in the if-then case. The difference with the while and until alternatives is that in the
first case you check before entering the loop, while in the second case (until) the code in the loop is going to run at least once
before it hits the test (and determine if there is gonna be an iteration). The do while / until loop is more general than for / next
and its use is preferable when the exact amount of iterations needed is not known from the beginning. If you know or you can
calculate the number of iterations needed, then try using the for / next loop which is rather more safe and stable.
The rules:
(1 ) It is very easy to crash your computer by getting stuck in an infinite loop. Actually you might want to try this, it will be
the first time that you intentionally and/or predictably you crashed it.
do while( true )
'' no exit situation
loop
(2 ) Since while is more general than for / next you can actually simulate its behavior with only a few variables.
Geometry Continued
Here are some examples of using loops from performing geometric operations. Lets start with some basics of translating the
control variable from one domain of abstract numbers to something more meaningful. The following example converts the
control variable to angle for plotting points of a circle.
next
Grids
The following example illustrates the process of making a grid of points in the xy plane by using two nested loops and
interpreting the control variables are columns and rows of the grid, which then are translated in coordinates of the grid's points.
''
dim column
for column = 0 to columns - 1
dim row
for row = 0 to rows - 1
'' calculate (x, y) of each point based of the column and row
''
dim x: x = column
dim y: y = row
'' create a new point on the xy plane (z = 0.0)
''
dim point: point = array( x, y, 0.0 )
next
next
NURBS
The next example plots a grid of point on a NURBS surface. But before doing that you might want to know a little bit more
about NURBS surfaces. NURBS surfaces provide the means for creating and manipulating curved geometry. Non Uniform
Rational B-Spline surfaces are defined by two curved directions: "u" and "v". Apart from many interesting properties (see
bibliography), NURBS surfaces make possible to draw on them as if drawing on a plane. Instead of using ( x, y )as in the
previous example, you can use ( u, v ) and draw directly on any surface. In order to do that; there has to be an intermediate
step of translation between the "uv" coordinates and the "xyz" coordinates. By "evaluating" the surface at a ( u, v ) point you
extract a point in ( x, y, z ) space. You may want to think that the first example with the circle, already introduced you to the
idea of mapping from one domain to another. In a sense, converting the control variable of a loop to angle for using it as a
parameter to functions that give you points of a circle is not that far way from getting points of NURBS curves and surfaces.
The only difference is that you don't know the function that produces them (but can learn more if you look on the net).
The "u" and "v" parameters of a surface have both a minimum and a maximum value. In the example with the circle the
minimum and maximum values for the "angle" parameter were [0, 360] or [0, 2π]. The actual values of the minimum and
maximum for NURBS are surface-specific. A pair of (min, max) is usually called domain, so a surface has two domain values
(one pair per direction). Reparameterizing a surface allows you to setup the domain manually. Usually the range from 0.0 to 1.0
is preferred because it is easier to manipulate (you can think in terms of percents along a direction etc).
Have in mind that "uv" coordinates are not necessarily correlating proportionally to the "distance traveled" on a surface. In
other words, the uv pair (0.5, 0.5) is not certain that it would coincide with some sort of central position on the surface. The
density of the of the "uv" coordinates depends on the parameterization, knot vector and other underlying properties of the
NURBS surface.
There are a couple of conceptual difficulties with NURBS which spring from its topological nature. For instance, while once you
are operating on NURBS it becomes easier to encode geometry and change the underlying placeholder (which is the surface),
but on the other hand there are trade offs, as for example the difficulty in thinking in terms of distances. In the circle example,
for instance, the distance between two "angle" parameters is not equal to their difference but equal to the length of the arc they
define.
The following example illustrates the process of creating a grid on top of a surface. The first new part of the code, outside the
grid iteration, sends a command to rhino in order to reparameterize the surface. The reason for doing this is to simplify the
manipulation of "uv" coordinates, so that the minimum uvs are going to be ( 0, 0 ) and the maximum ( 1, 1 ). Inside the nested
loops, there is a translation part from columns and rows to u and v. The operation is fairly simple if you consider that we are
mapping proportionally a "column" range of [0 to columns - 1] to a "uv" range of [0, 1]. So, the minimum "column" value, which
is zero, results to zero "u" and the maximun (columns - 1) results to 1. Finally, the function "evaluatesurface" converts from a
"uv" pair to a "xyz" point. In the circle example the "x = radius * cos( angle ), y = radius * sin( angle )" are the "evaluate curve"
functions.
dim column
for column = 0 to columns - 1
dim row
for row = 0 to rows - 1
next
next
The last linguistic feature presented in this page explains all about procedural thinking. Procedures and functions are means
for modularization. In essence procedures are used in order to avoid rewriting the same code again and again. A good rule of
the thumb in order to identify when a procedure is needed is to track when you are copying and pasting code. All this extra text
could be wrapped in a function and reused in multiple places in the same or another piece of code. Procedures and functions
allow you to think in terms of "actions" and by reducing the overall amount of code they make easier for you to handle more
complex processes.
Reintroduction!
You have already encountered a lot of functions built in the language, such as cstr( ), formatnumber( ) or others provided by
rhino, such as [Link]( ), [Link]( ) etc. The general syntax for using function is:
You can also use one function inside another. In that case the rule of the parentheses apply: the most inner function is
calculated first, its result is passed to the next one etc.
A function returns a value, therefore it cannot be present as the left part of an expression. It is an error.
vertex_distance = sqr( dx * dx + dy * dy + dz * dz )
end function
So, in other words, you can just ignore the returned value of a function. In that case you can use the keyword "call". Call just
inform you / the language to ignore the returned result, or that you are calling a sub, which doesn't return anything anyway.
Bug me not!
Functions that are defined in your code, but you are not calling them from anywhere are simply ignored by the language. Thus
you cannot know if there are errors in them or if they work at all.
Another thing you might want to know about code scope is that you can actually use a variable that was defined outside a
function from within the function (but not belonging to another function's body). This is not very safe this thing to do though. For
example the VERTEX_X variable was defined outside the function vertex_distance( ), but we were able to use it inside. These
variables, that are defined outside of all functions, are also known as global variables because you can access them from
everywhere. While it is safe to read their value, it is not very wise or safe to modify them.
end function
dim x: x = 0
x = x + 1 '' q: which x is actually modified??? a: the x inside the function
end function
function inner_function( )
end function
end function
THE END