top of page
Search
  • Writer's pictureWill

How to use Eto.Forms in Rhino/Grasshopper (part 2)

Updated: Jun 7, 2021

UPDATE: I've made a plugin that exposes Eto.Forms all in the form of Grasshopper components. Take a look here!


Here are some other thoughts on making Eto.Forms in Grasshopper. If you haven't seen part 1 please head over and take a look. Lots of introductory information.

In the last post I mentioned that GhPython has a nice and easy compiling procedure to speed up runs plus protecting source code. For those curious, this procedure also bypasses huge setup work, in my opinion, in Visual Studio, or any IDE of choice. Unless your daily task is developing plugins and every couple of weeks there is a new software development, it is much easier to stay within the script component provided by Grasshopper.

When I dabble in developing plugins for Grasshopper in an IDE, I struggle to remember the programming patterns or boilerplate stuff necessary to compile a .gha file. Granted that the lower level languages you use in an IDE the more access to deeper customization, I find there are however too many steps to follow before even a small function can run, which is incidentally most of my scripts. I simply do not have the need to fully whip up standalone-caliber plugins.

That said, I do believe that if you'd like to code more sophisticated plugins and whole suites of tools, and even wish to do this on a sustainable scale, it's better to sooner or later graduate onto IDEs. My humble little GhPython trick can only take you so far. It's a neat place to start but it is rather niche and only did I find it useful when I have my specific use case in my architecture office. That is: extending by bits and pieces the functionalities of an already robust software (Rhino).

Now that we've clarified the scope, we shall once again dive in. I covered the basics of starting up an interface in the last post but how do we make the UI element do anything? You've probably noticed that the buttons we created were there for show. Not interactive at all. That's because we didn't set up methods listening to events. I cannot possibly do a good job explaining events in programming, but for the Eto.Forms controls to work, all you need to know is the mechanism.

When an Eto.Forms window is run, each control (a button, a slider, a text box etc.) starts a number of "event listeners" in your computer. If the listener "hears" an event happening, it would trigger one or many functions or methods. A click of the mouse would be an event. The listener behind a button would listen for the moment you click on it. Once you do, it fires up whatever you want it to run. So your task is telling the button that when the click event happens do the following. Here is the one line of code to do that.

eventName += whatMustBeDone

Pretty simple. Let's see that in action. A little change from last post's script. Because of GhPython's way of remember object references and scopes, which I don't fully understand yet, we need to wrap things in a class so we can refer to these things elsewhere. If you simply use last post's script and add the OnClick as a global scope function, it cannot be found by the Eto.Forms.Form object. So we attach it as a method to the Eto.Forms.Form


Other than that the bulk of the script is the same. Just look at line 18 to 28. By the way, line 20 and 21 are kinda the answer to my last post's challenge. All you needed to do was casting button to StackLayoutItem() so it has a HorizontalAlignment attribute, which you could modify to centered.

Before long, you see line 23? That's where the event is registered with method OnClick. When you define methods used in event handling, always start one like line 29. Parameter "sender" will be the control upon which your event/action takes place, parameter "event" the actual event itself (sort of a special method). You can change alias by doing something like def OnMouseOver(self,s,e) or def OnClick(self,control,action)but it should always be that pattern - your second parameter always the control element, the third parameter the event.

In our script, line 23 registers the event of a mouse click on button "b1" to method OnClick. When you now click "b1", OnClick runs lines 29 to 33. First the method creates an arbitrary color and then assigns it to the sender's "TextColor" attribute. The sender is of course the control upon which the action (click) takes place. In effect, every time you click the button "don't click me", it changes its text color. If you simply change b1 to b2 on line 23, this will happen to button "whatever..."


Different controls will have different events. Refer to the API doc for events you are looking for. Some of the most common events include mouse clicks, moving mouse over, mouse leaving a control area, key press, text change etc. No matter the event, it's good practice to use try/except statements in the "On-" methods. It becomes essential because once the Eto.Forms.Form is launched, the Grasshopper script will finish itself and is no longer in the same scope. Error handling of GH is no longer accessible and if something in the interface goes awry, it will crash the Rhino program. It's yet another concept I cannot quite explain clearly but have an idea to deal with it.

This brings me to the modal issue. When you look at McNeel's official introduction on Eto.Forms, they primarily used Eto.Forms.Dialog as base window/container. This class only has .ShowModal() methods. That means when you launch your window the native environment is locked. There is an asynchronous ShowModal method but that only frees up either GH or Rhino interface, not both. Eto.Forms.Form on the other hand is completely modeless. A big advantage is that you can interact with Rhino model if you need to pan or zoom around. A drawback is that your window will kind of lose connection with the Grasshopper script.

Grasshopper scripts are run linearly from left to right and once it's done it would not recompute unless told to. Once your window is fired up, the script on GH canvas has finished computing. The window itself, however, is a live running program. If you try to return any value out of the GH component from which the window is launched, nothing will happen because the canvas does not refresh anymore. The twist here is to somehow pass references of some of the component instances on the canvas into the window object. That way we can manually expire them. I'm of course referring to calling the ExpireSolution() method documented here.

By the way I recommend this website. It's put together by an architecturally trained programmer and it has essential API doc references to Rhino, GH, Revit and Naviswork in one place.

To find and retain the "pointers" to objects on GH canvas, the straightforward way is through GH API. There is got to be some sort of FindByName() or Table<GH_Component> kind of thing to use. My knowledge of the GH API is actually pretty shallow. Yet I found my way around. In my opinion it turns out to be the easier way to set up the pointer. You can connect the host component downstream to another GhPython. By accessing Component.Params.Output[i].Recipients[i], you can have a reference to that GhPython. Using in tandem with scriptcontext.sticky library, you can change values in sticky and call the ExpireSolution(True) on the downstream GhPy. In this case that connection between host component and downstream doesn't even have to carry data. The sticky library can act as vehicle. Remember to NOT call ExpireSolution() on the host component. It would flush out your current running window.

Understanding scopes in Rhino and Grasshopper isn't easy. It took me a while to get to a working knowledge for my purposes but there are still many mechanisms that I don't fully comprehend. Good news is that McNeel is working on better API docs on upcoming releases of Rhino and particularly Grasshopper. Hopefully that can help clarify how the creators structure this piece of amazing software.

I'm glossing over many details at the moment but I've laid out the fundamental concepts in these posts on Eto.Forms. If you ever need to make an interface, not only for internal use in a design office, but also for coordination with clients/consultant (very likely considering how people are increasingly adopting information modeling), send me a note and we can discuss. Ultimately, making toolsets with Eto.Forms can begin to facilitate a shift in the practice, one where design product is much more streamlined in its conception, translation and realization.

Recent Posts

See All

How to pass all ARE exams

Now that I’ve finished all my ARE exams and registered as an architect, I can summarize a few pointers for anyone still in pursuit. These are things that I found to be noticeably helpful along my way

ARE 5 and prep materials

Here is an update on my progress on ARE 5 exams and my experiences with the preparation materials on the market. Ever since I passed my three managerial exams (PjM, PcM, CE), I've taken a long hiatus

GhPy can multi-thread too! But wait...

GhPy component is known to be a bit slow in its performance because of the JIT compiler it utilizes. However, GhPy is a powerful widget to test out scripting logic without the baggage associated with

bottom of page