Tuesday, December 18, 2012

Dynamo - Manual Transactions

Hey there, kids! I hope you're all getting ready for the holiday festivities, but maybe some of you are using some of your downtime to tinker with Dynamo on the side. Today, I'll be talking about the Transaction node in Dynamo, and what it can do for your holiday experimentation.

How Dynamo evaluates nodes

I mentioned in my previous blog post that one of Dynamo's internal modes of interacting with the Revit API via Transactions is Manual Transaction mode, and Dynamo only evaluates in this mode when there is a Transaction node in the execution workflow. To summarize again: Dynamo starts evaluating the right-most nodes (nodes with nothing connected to the output). If a node has any inputs, then it first evaluates its inputs, and then passes the evaluated inputs to the node, which is then used to evaluate the node.

Take a look at the following image, in which I've labeled the order nodes are evaluated:


First, Dynamo tries to evaluate the Reference Point node. It sees that the node has an input, so it then tries to evaluate the input, which is the GraphFunction node. That also has inputs, so it start evaluating those, starting with the first (top-most). The multiplication node also has inputs, so it evaluates those. This brings us to the first Number node. It has no inputs, so it can be evaluated as-is. The number -1 is evaluated. Now the other input to the multiplication node needs to be evaluated. The pi node has no inputs, so it just returns 3.4159... Now, the inputs to the multiplication node are satisfied, and we can evaluate the node, which multiplies them. Dynamo will now start evaluating the second input to the GraphFunction node, and the same process will occur.

What the Transaction node does

The Transaction node hooks into this evaluation tree-traversal in order to wrap some of the evaluation in a Revit API transaction. Let's say we had a Transaction node in between the division node and the GraphFunction node. When it would normally come time to attempt to evaluate the division node, it would first run into the new Transaction node. The Transaction node inserts some extra code in the process that initializes a new Revit API transaction, and the continues with the evaluation process. This means that the division node and its inputs will be evaluated with a transaction active. Once the division node has finished being evaluated, it's time for the Transaction node to be evaluated. For its evaluation, it ends the Transaction it created, and then passes its only input along as the result of the evaluation.

Caveats of the Transaction node

When a Transaction node is in the evaluation tree, Dynamo can no longer automatically manage the transactions of its nodes, since the user has explicitly determined when a transaction begins and--more importantly--ends. This means that any node which requires a transaction must be manually connected to a Transaction node by the user. This will prove to be rather inconvenient if you're using a third-party node that uses a transaction internally. However, it mirrors how writing any code for the Revit API involving transactions works, so if you're used to working with it in code, code with it in Dynamo should be familiar.

So when should I use a Transaction node?

Any time you need discreet control of a transaction in a Dynamo workflow. Without a Transaction node in the workflow, Dynamo will automatically detect if any of the nodes require a transaction, and if they do, it will run the entire workflow inside of one. If this functionality cannot accomplish what you need, that's when you use a Transaction node.

As mentioned in my previous blog post, a great example is the Solar Radiation Optimizer sample. It first adjust the value of a family instance parameter, then needs to recalculate the amount of incident solar radiation with the new value applied. The Solar Radiation plugin cannot recalculate until the parameter change has been applied, which requires the transaction the parameter change occurred in to have ended. When I wrote the Solar Radiation Optimizer sample, I used a Transaction node in order to set the new family instance parameter value. After the value was set, the transaction would end, the solar radiation plugin would update and spit out its results to a file, Dynamo would pick up on the change to the file and read in the new data, then compare the data with what it has stored, and then repeat the process until the end of the loop.

Tuesday, November 27, 2012

Dynamo - Revit Transaction Modes

I'm sitting at an airport bar waiting for my flight back to Boston to start boarding, so I figure now is as good a time as any to discuss how Dynamo interacts with the Revit API via transactions.

All Dynamo nodes know whether or not they require a transaction in order to evaluate. This is necessary in order to make sure that they are run within a transaction, but also so that they aren't run in a transaction if they don't need to be.

Dynamo has three internal "transaction modes" which are set based on various factors.

Automatic

The default mode. If any node in the workflow requires a transaction to evaluate then the whole Dynamo expression is run in a transaction in the Revit idle thread. Otherwise, it is run in its own thread.

Manual

This mode is active if the workflow contains a Transaction node. In this mode, it is up to the user to make sure that all nodes which require a transaction are connected to a Transaction node (either directly or through one or more "parent" nodes). This is desired if you would like to see intermediate results in Revit or if you need a complete Transaction before continuing to evaluate. A good example is the Solar Radiation Optimizer sample, which has to end a Transaction in order for Solar Radiation to update and feed data back into Dynamo.

Debug

Activated when the Debug box is checked in the UI, every node which requires a transaction is evaluated in its own transaction. This allows you to see every node's effect on the document, but is also really slow. Fun fact: this was the first (and for a while, the only) transaction mode.

Monday, November 26, 2012

Dynamo - Sliders

The End Result


Slider nodes now have a text box that displays the current value underneath the slider as you move it.

Thoughts

I feel like any technology substantially cool also makes me want to pull my hair out.

Sometimes I'm amazed at how well WPF can get things done. I can usually link together how I'd like a UI to work using abstract notions that don't translate well to paper, let alone code, but WPF somehow has the ability to do this translation as simply as possible.

That doesn't mean that there isn't a lot of trial and error, however!

One long requested feature for Dynamo was the ability to see the exact value of a slider. The reason why this is useful is obvious, but because I didn't really know how I'd like the UX to work, I put the feature off for a while. Besides, I'm more of an engine guy and not so much into UI (ironic, considering I work on a programming language who's defining feature is a UI).

This weekend, in a alcohol turkey-induced haze, I came up with a decent idea:  have a TextBox appear when the user's mouse is over the slider, and have it disappear when the mouse is removed. "But wait Stephen," you exclaim excitedly, "Why don't you just use a tool tip? It does almost exactly that!" You see, Dynamo has tool tips for nodes reserved for node descriptions and error messages, and there is no clean way to override that functionality. Also, that's not really what tool tips are for, in my (humble, UX-lacking) experience.

OK. So how was it implemented?

Early in Dynamo's life, Ian Keough added a Canvas to the UI for Dynamo nodes. This Canvas was used to draw the chrome on the nodes, as well as the title text. I was able to use the Canvas in order to position a TextBox at an arbitrary coordinate relative to the node. Then, it was simply a matter of hooking the appropriate events to some logic that controls when the TextBox is displayed and hidden, and setting up a Binding that binds the TextBox's Text property to the current value of the slider.

Unfortunately for fans of XAML (but fortunately for myself, who isn't one), this has to be done programmatically in C#, due to how Dynamo nodes are initialized. This is what the code looks like:
1:  Slider tb_slider;  
2:  dynTextBox mintb;  
3:  dynTextBox maxtb;  
4:  TextBox displayBox;  
5:    
6:  public dynDoubleSliderInput()  
7:  {  
8:    tb_slider.ValueChanged += delegate  
9:    {  
10:      var pos = Mouse.GetPosition(elementCanvas);  
11:      Canvas.SetLeft(displayBox, pos.X);  
12:    };  
13:      
14:    tb_slider.PreviewMouseDown += delegate  
15:    {  
16:      if (this.IsEnabled && !elementCanvas.Children.Contains(displayBox))  
17:      {  
18:        elementCanvas.Children.Add(displayBox);  
19:    
20:        var pos = Mouse.GetPosition(elementCanvas);  
21:        Canvas.SetLeft(displayBox, pos.X);  
22:      }  
23:    };  
24:      
25:    tb_slider.PreviewMouseUp += delegate  
26:    {  
27:      if (elementCanvas.Children.Contains(displayBox))  
28:        elementCanvas.Children.Remove(displayBox);  
29:    };  
30:    
31:    displayBox = new TextBox()  
32:    {  
33:      IsReadOnly = true,  
34:      Background = Brushes.White,  
35:      Foreground = Brushes.Black  
36:    };  
37:    Canvas.SetTop(displayBox, this.Height);  
38:    Canvas.SetZIndex(displayBox, int.MaxValue);  
39:    
40:    var binding = new System.Windows.Data.Binding("Value")  
41:    {  
42:      Source = tb_slider,  
43:      Mode = System.Windows.Data.BindingMode.OneWay,  
44:      Converter = new DoubleDisplay()  
45:    };  
46:    displayBox.SetBinding(TextBox.TextProperty, binding);  
47:  }  
48:    
49:  [ValueConversion(typeof(double), typeof(String))]  
50:  private class DoubleDisplay : IValueConverter  
51:  {  
52:    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)  
53:    {  
54:      return ((double)value).ToString("F4");  
55:    }  
56:    
57:    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)  
58:    {  
59:      return null;  
60:    }  
61:  }  

(Note that I've removed code that's irrelevant to this explanation, which I'll summarize quickly: the initialization and configuration of the fields were removed, as well as the initialization code for the inherited Dynamo node UI.)

On line 8: When the slider's ValueChanged event occurs, I get the current mouse position relative to the Canvas, which the TextBox is a child of, and then use the X-coordinate to place the TextBox in line with the mouse.

On line 14: When the user clicks on the slider, first I check to see if the TextBox should be displayed. If it actually should, then I add the TextBox to the Canvas, and set it's X position in the Canvas just like I did in the previous event handler.

On line 25: When the user releases the mouse button over the slider, then I check if the TextBox is currently being displayed, and if it is, I stop displaying it.

On line 31: I initialize and configure the TextBox. Note that on line 37, I set the Y coordinate to the height of the node, thus making sure that it is always displayed right below the node.

On line 40: I create a Binding that ties what text that the TextBox displays to the value of the slider. Note that I set an explicit Converter. I wan't familiar with programmatically making WPF Bindings, but fortunately MSDN has a great tutorial on the subject.

On line 52: I create a new implementation of an IValueConverter, that converts double to String. This is used to truncate the number of decimals to 4, as opposed to the default, which was a lot more.

And that's all there is to it!