Monday, November 17, 2008

Rhinoscript Classes Introduction

Here's a simple example showing the use of VBScript's classes in a Rhino script. It assumes some knowledge of the basics of scripting. VBScript's classes offer a very primitive form of object-oriented programming, but it is enough to be helpful in organizing your scripts.

Why and when should you use classes? I'm not going to give you the theories you can read elsewhere about "abstraction" and the like, I'll say that it's fine, probably best to stick with basic functions and procedures when you're hacking out basic functionality, but once you get to a stage where you're struggling over what variables to make global and which to keep passing as arguments back and forth among several functions, it's time to think about packaging things up in objects. It's not necessarily going to make your code smaller, but it will make it a lot easier to manage, if you do it right(a bit of a caveat there, yes,)you'll smoothly construct really complex functionality from simple, robust components.

When you're making an "object" what you're making is your own custom type of variable, made up one or more of the basic variable types(integers, strings, etc.)or other objects. In this example, I've made a simple "Cylinder" object.
Download and load this rvb file into Rhino. As soon as you do that, a cylinder will appear. That's thanks to this code down on line 63, after defining the Cylinder Class:
Dim objPersistentCylinder
'create the cylinder object on loading the script file
Set objPersistentCylinder=New Cylinder
objPersistentCylinder.Height=10
Looks pretty clear, doesn't? We've made a new Cylinder, without having to worry about how, and set it's Height to 10. Much more elegant than without classes, if you wanted to keep track of these things you'd have to store the required parameters to build the cylinder and the identifying string for the solid as separate variables and pass them to a "ModifyCylinder" function. Then if you wanted to build an array of cylinders...ugh!
Now run the RunScript command in Rhino. You'll be given two subroutines to choose from, MakeTemporaryCylinder and ChangePersistentCylinder.
Line 68
Sub MakeTemporaryCylinder()
Dim objMyCylinder
Set objMyCylinder=New Cylinder
objMyCylinder.Radius=3
objMyCylinder.Pick
'pause for user input, just to show it exists
Dim strInput
strInput=Rhino.GetString("The volume of this cylinder is " & objMyCylinder.Volume &". Press any key to continue...")
'after this sub exists, the object ceases to exist and the cylinder is deleted as per Class_Terminate()
End Sub
This illustrates how objects have "scope" just like normal variables. The objMyCylinder variable was declared inside the function, so as soon as you press any key at the prompt, the object ceases to exist. In the class definition there's a special subroutine called Class_Terminate() you can use to execute any cleanup code you like at that time, like in this case deleting the actual solid.

You'll also notice the Pick method I added to highlight the cylinder, it's on line 57:
Public Sub Pick()
Rhino.SelectObject int_strSolid
End Sub
Functions, Subs, and variables can be defined as Public or Private. Pick is Public, so that the rest of the program can call it, but int_strSolid, the GUID of the cylinder solid itself, is one of the Private properties.

Now I could have made the GUID of the solid Public and used it to call Rhino.SelectObject outside the class, but the idea of classes--of course there have been books and books written about this stuff, pay little attention to my limited experience!--is that you want to keep as much private as possible so that you can change how things work inside the class without breaking the code that actually uses it.

There's also the problem that if the property is public, then any other code can modify it willy-nilly, which may or may not be a good idea. In this case it is probably a bad idea, if the GUID of the cylinder changes, what happened to the old one? Was it deleted or is it still hanging around? It's sort of the "responsibility" of the Cylinder class to keep track of it, so it needs to control it.

Now there is a way around it, if some outside functions did need the GUID but we didn't want it to be changeable. Notice how the volume of the cylinder is added to the command line prompt, and on line 42, how that property is called inside the class:
Public Property Get Volume
'Retrieve the volume of the cylinder
Volume=Rhino.SurfaceVolume(int_strSolid)(0)
End Property
So, we could make a Property Get for the GUID like this:
Public Property Get GUID
GUID=int_strSolid
End property

This gives the same result as if we returned a value from a function:
Public Function GetGUID()
GetGUID=int_strSolid
End Function
The difference is that as far as the calling code is concerned, "GUID" is just a property like any other rather than the result of a function. So both more elegant, isolating the logic of what you want to do with your object from the nasty dirty business of making it work, plus it means that if at some stage your GUID was a simple public property, and you decided to do something more complex with it, then that change would be transparent to the rest of your code.

Moving on to the ChangePersistentCylinder subroutine on line 79.
Sub ChangePersistentCylinder()
'randomly change radius and height
Randomize
Dim dblRadius,dblHeight
dblRadius=1+Rnd*7
dblHeight=5+Rnd*10
objPersistentCylinder.Radius=dblRadius
objPersistentCylinder.Height=dblHeight
End Sub
This function randomly modifies the radius and height of the cylinder, automatically redrawing. The last chunk of code made use of Public Property Get, this makes use of Public Property Let, and shows why you would use it.

Setting the Radius of the cylinder calls this code on line 36:
Public Property Let Radius(dblRadius)
'Set the radius
int_dblRadius=dblRadius
Redraw
End Property
The Property Let stores the entered radius in the internal radius variable, so that it can't get changed unexpectedly by outside code, and calls a Redraw function that deletes the cylinder and rebuilds it with the new dimensions.

There's a whole lot more to object-oriented programming, but this pretty much covers the features of VBScript classes. The only major feature I neglected is Property Set, which is just like Property Let except it takes an object as a parameter instead of a regular variable.

You can go crazy with arrays of objects and objects inside objects, and there are some details about using them that may trip you up, but these mechanisms are your building blocks.

Sunday, November 16, 2008

Recent Scripting Project



I've been working with Tube Guage Inspection Fixtures, Inc. on some scripting to assist with the Rhino design of their inspection fixtures, sets of wood or metal forms used for checking tolerances on piping, usually used in the automotive industry. A simple enough concept, a perfect situation for scripting automation, but there were enough details to handle that it was still about 130Kb of script.

The script works from a user-defined centerline and chosen dimensions to make a set of blocks under the straight sections of the pipe. It automatically adds clearances for tube bends, applies specific profiles to the end blocks, and formats 2D output with particular tool path requirements. It wasn't considered a good use of time to try to automatically adapt to all possible details, since they can vary quite a lot and these are made working from often dubious imported geometry, so I simply made it as simple as possible to make modifications.