Properties — _self and common idioms

Properties in Field are very important (as you might be able to see from the developer documentation and the advanced customization documentation). They are the glue that holds Field's plugin architecture together, but they might also be the glue that holds your work in Field together. There are one of the core concepts that Field adds to the text-editor + interpreter paradigm it builds upon.

All 'visual elements' (all boxes that you create in the canvas that contain code that you edit in The text editor) can have properties that are stored locally to that 'box'. Some properties persist when you save the sheet (they are actually stored in the sheet itself), some properties are stored and version tracked so you can roll back to previous versions. Even the code that you type into the box is a property (a version tracked one at that).

Using Properties, some patterns

As you might expect, their access from Python code has been made very convenient. They essentially introduce another scope into Python accessible through the special variable _self.

_self.someProperty =5

just sets someProperty to 5. By default, this is stored inside this iVisualElement and it's persisted to disk. Each of the following code snippets set a property, and demonstrate some naming conventions concerning them.

_self.someOtherProperty_ = "10"

sets someProperty_ to "10", and this too is stored inside iVisualElement but, because of the trailing _ it isn't saved to disk. This is important because many things you might want to transitorily store in a property can't be persisted to disk.

_self.someVersionedProperty_v = Vector4(1,1,1,2)

sets someVersionedProperty_v to this Vector4, it's stored locally, persisted to disk, and version controlled (hence the _v). (Specifically, it's broken out of the sheet save file and exposed separately to the Mercurial-based version control system). This means that you can enquire and perhaps even visualize its value over long periods of time.

_self.someProperty_i = "inspect me"

sets someInspectedProperty_i to "inspect me" which, because of the trailing _i appears prominently in the Inspector window (thanks to the InspectorPlugin) where it can be edited:

Also menu items:

def printed():
   print "menu selected"

_self.someMenuItem_m_ = printed

makes a new menu item "someMenuItem" in the right-click menu for this element that, when clicked, runs the function printed, all thanks to the trailing _m (and the PythonPlugin):

Delegation and Defaults

Boxes are actually connected in a graph structure. Elements can have parents and children. If a property is missing at one level, the search continues 'upwards' until a property is found. This is how plugins (which are just elements near the top of the pile) extend the functionality of many elements. This structure is revealed when we select a box with non-default connections:

We can edit this structure using a special mouse tool:

We can either select it with the mouse or just press and hold 'D'. Then drag with the mouse a connection between the two boxes:

When asked to get properties (rather than set them) _self does the full graph-delegation lookup:

print _self.python_plugin_

Ultimately gets hold of the PythonPlugin near the top of the graph. This is useful to the plugin writer — it provides a uniform way for elements' code to find your plugin.

In the image above, _self.something executed 'in' element 'a1' might pick up a 'default' provided by element 'A' if there's no something stored locally.

Finally, some example property idioms:

_self.subelements["banana1"].python_source_v ='print"hello world!"'

sets the code "contained" in the sub element called "banana1" to be print "hello world!". There's also _self.superelements[...], and _self.all for a map, by name, of everything in the sheet and _self.find for a "map", by regular expression over name, of everything in the sheet.

camera = (_self.defaultCamera_ = _self.defaultCamera_ or BaseCamera())

sets a (transitory) property "defaultCamera_" to a new BaseCamera, unless it's already been set (either here, previously, or by an element further up the graph)

_self.root.globalDefaultColor = Vector4(1,1,1,1)

sets a property "globalDefaultColor" to white at the root of the sheet, so that all other elements that write _self.globalDefaultColor obtain Vector4(1,1,1,1) (unless it has been overwritten locally by them).

These kinds of pseudo-hierarchical delegation allow control over processes (or colors, or code, or ...) represented in the canvas to take place at various granularities — remember, the dispatch "hierarchy" is in actual fact a directed-graph (cycles in the graph are handled as well as you'd expect, which is to say that they are hard to predict, but they don't lead to an infinite loop).

And for the finest control over properties, consider rewriting the rules directly in Python, inside Field itself — ExtendingFieldInField.

Clearly, Properties are the vital "route in" for much of the functionality that the Field environment offers programers to program on and in the Field environment itself. They are also key to Field's vision of a programing environment that mixes code, instantiation and data. A list of the important properties defined and used by the core plugins is available here.

makeBoxLocalEverywhere

One last note. Sometimes while building something up you fall into the trap of using global variables (because it's quicker to type) and then regret it — you wish that you'd made all of those foos _self.foo_ instead. Things that were previously "global" to you need to be encapsulated somehow. Enter makeBoxLocalEverywhere :

makeBoxLocalEverywhere("foo")

This is a very powerful command. After this code has been executed global access to foo is just like writing _self.foo_. Now you can build up delegation heirarchies around boxes &mdash refactoring the semantics of your boxes without actually editing any code at all. All those global variables become "box local".