Notes on coding for TIM or developing small pieces of plugins.


Build Qt resource file

If file /timgui/ui_resources.qrc is edited, it is necessary to rebuild the .py resource file actually used by TIM.

pyrcc4 -o ui_resources.py ui_resources.qrc

On Windows, your pyrcc4 (PyQt resource Compiler) may be hidden in the PyQt4 site package directory, for example: C:\Python27\Lib\site-packages\PyQt4\pyrcc4.exe

Mkdocs for documentation

  • mkdocs serve - Start the live-reloading docs server.
  • mkdocs build - Build the documentation site.

Project layout for Mkdocs:

mkdocs.yml    # The configuration file.
docs/
    index.md  # The documentation homepage.
    ...       # Other markdown pages, images and other files.

It is necessary to use three strings for each page item, so that the sub-headings will show up in site navigation. I don't want it to add another heading, so I left the second string empty. e.g. (in file mkdocs.yml):

pages:
- ['index.md', '', 'About']
- ['install.md', '', 'Install']
- ['getting_started.md', '', 'Getting Started']
...

The following material is very old and may be dated, they are waiting to be tidied up.

Convention

I have attempted to stick with a convention. Many parts of the code is very old, which may not comply with the convention very well, these will be changed over time. The pros and cons of each style aren't always clear, I am still learning about them.

Class names are mostly connected captalised words, eg. MyScene, SceneManager, Ui_SelectionBoard, Ui_PointsBoard. Note that Ui_ prefixs for classes that are UI components.

Public Methods of classes use similar convention to Qt library's: captalised words but not the first letter, eg. clear(), addPoint(pt), loadPolyline(polyline), createSlice(). Private methods are prefixed with underscore eg. _connectScene(s).

Properties of an object (includes instances of classes) are not captalised, and words are connected by underscores, eg. self.name, self._polyline_items, self._live_line_item. Note underscores also prefix those properties that should be protected.

SIGNALS are similar to propertys/instance names, all small letters and words connected by underscore, this is intended to differentiate from normal Qt's SIGNAL names. wg. SIGNAL("model_geo_reloaded"), SIGNAL("model_lst_step_changed").

Constants/enums are all capital words connected by underscores, eg. MyScene.MULTI_SELECTION_MODE, MyScene.SINGLE_SELECTION_MODE, Model.ADDED, Model.UPDATED, Model.REMOVED, note most of them should belong to a class, so the name space is tidier.

General Structure

The application has several key types of components:

Small, self contained object that does a single meaningful thing, eg. colorbar (provides ability to convert values into colors), customised graphics items (mostly subclass of QGraphicsItem).

Slightly larger kind of objects that have ability to communicate with other parts while keeping certain amount of data, or a complex set of flyweight objects, eg. scene (keeps a set of graphic items for viewing), model (keeps PyTOUGH objects).

'Managers', a container type of object that keeps a simple collection (usually a list or dictionary) of smaller objects and provides additional SIGNALS/SLOTS to communicate with other UI or data components. Often there is only ONE manager in the whole application. eg. colorbar manager, scene manager.

'Controls' are generally UI components that control things. eg. scene control, view control, colorbar control... etc. This can include complex interactions between several major managers, controls, and views.

The main window directly contains several different controls, several managers, and a model data object. This is all major components are kept and connected together.

Scene

"Scenes are pure data, and they can be visualised only by associating them with one or more QGraphicsView objects." In this application it represents a logical group of model objects that are meant to be shown together, eg a layer or a slice, with lots of items such as blocks, connections, and wells. This is actually a group of classes:

  1. Class MyScene inherited QGraphicsScene, and is basically customised to handle modes of selection and line creation etc. This includes controlling how the mouse behaves.

  2. Class SceneData inherited MyScene, and is where the all the individual QGraphicsItems are stored. It controls the color, length and visibility of the 'Blocks', 'Connections', and 'Wells'.

  3. Classes SliceScene, TopSurfaceScene and LayerScene inherited SceneData, and are actually only responsible of populating the SceneData objects. To the rest of the application, all scenes are considered an instance of SceneData.

Class MyScene

There are several modes of operation: MULTI_SELECTION_MODE, SINGLE_SELECTION_MODE, MARQUEE_SELECTION_MODE, LINE_CREATION_MODE, WALL_CREATION_MODE. Most of the methods of the class probably needs to be aware of the current mode.

    MyScene.__init__(parent=None):
    MyScene.printSelectedItems():
    MyScene.currentMode():
    MyScene.setMode(mode):
    MyScene.unsetMode():
    MyScene._liveLineStart(head_scene_pos):
    MyScene._liveLineStop():
    MyScene._liveLineUpdate(scene_pos):
    MyScene._polylineAddPoint(scene_pos):
    MyScene.clearPolyline():
    MyScene.loadPolyline(polyline=[]):
    MyScene.getPolylineItems():
    MyScene.getPolylineQ():
    MyScene.getPolyline():
    MyScene.mousePressEvent( e):
    MyScene.mouseMoveEvent( e):
    MyScene.mouseDoubleClickEvent( e):

I am guessing the strict/consistent structure of Qt's parent/child is important for communication, eg. when a item being added into a scene, the scene knows item as child, and item knows scene as parent. This allows the child to inform parent when it is changes, and parent to inform child when the child needs to refresh.

My current relationship of Scene and SceneManager does not have this. Should I make Scenes and SceneManager becoming children and parent?

Selection

I need to make it so the selection made inside each scene be propagated to other scenes that holds the same item. How should I achieve this? Several options at the moment:

  1. Reimplement Scene and SceneManager to have strict parent/child relationship, and make default message/signal goes from Scene to SceneManager and back down to other Scenes. Connection stays in the same module and nearby classes. This may actually solve some other issues too.

  2. Let selection-board to be the central location for selection. All scenes connected to board inform the board selection/deselection made, then the board informs other scenes to select/de-select. It requires connection to be made when new scenes are added.

At the moment, Class MyScene emits two signals when selection/de-selection is made:

  • SIGNAL("scene_multi_selected"),i.name,i.objType,i.isSelected()
  • SIGNAL("scene_single_selected"),i.name,i.objType

And this assumes all items inserted in the class SceneData has property of .name and .objType. Possible values of .objType are: SceneData.BLOCK_ITEM, SceneData.CONNE_ITEM, SceneData.WELL_ITEM.

class SceneData also implements three methods that allows controlling of what's being selected.

  • setSingleSelect(self,i_name,i_type)
  • setMultiSelectUpdate(self,i_name,i_type,sel)
  • setMultiSelectComplete(self,i_names,i_type)

Model Data

tim_model_data.py

This module handles most of the access to a model (geometry, data file, and listing results) through PyTOUGH interfaces. Three main PyTOUGH objects: geo, dat, and lst are included. Signals are emitted when PyTOUGH objects loaded/refreshed/changed.

Signals emitted

  • SIGNAL("model_geo_reloaded")
  • SIGNAL("model_dat_reloaded")
  • SIGNAL("model_lst_reloaded")

  • SIGNAL("model_lst_step_changed"),new_step_idx

  • SIGNAL("model_block_raw_data_list_updated"), self.vars[self.BLOCK]

  • SIGNAL("model_flow_raw_data_list_updated"), self.vars[self.FLOW]

  • SIGNAL("model_raw_data_changed"), (mdtype,valName,self.UPDATED)

  • SIGNAL("model_raw_data_changed"), (mdtype,valName,self.ADDED)
  • SIGNAL("model_raw_data_changed"), (mdtype,valName,self.REMOVED)

Color Bar

Design

There are two kinds of non-UI related structures used here, myColorBar and ColorBarManager:

  • myColorBar is a class of objects, each one keeps a set of settings. It is used to get a color given a scalar value, same as a Colormap object in matplotlib.

  • ColorBarManager is a container of multiple named colorbars. It pretty much acts like a dictionary, with added ability to emit signals to inform others when a colorbar is updated. Generally other components of the application connects to the manager instead of keeping the individual colorbars.

Important

The notification ability can only be done using ColorBarManager. And it is essential to use __setitem__ to trigger the signal emission:

cb = window.color_bars['Temperature']

cb.valueScale = [0., 50., 100., 150., 200., 250., 300.]
cb.colorScale = ['blue','skyblue','lightgreen','yellow','orange','orangered','red']
cb.discreteColorMode = False

# NOTE, it is necessary to do this to trigger the manager's notification
window.color_bars['Temperature'] = cb

Information Board

Specification

A widget that displays 'live' information of mouse's current position, either x-y coordinate or an item/PyTOUGH object. Possible things to show:

  • X,Y,Z

  • Block name (or connection, well)

  • (t2data) Rock Type, permeability, etc.

  • (t2data) Generators in block

  • (t2listing) Variables, maybe just the first few?

Design questions

  • How much to show? Maybe show few, and user expandable by clicking or customisation?

  • Should 2-D line plots (history etc) be triggered from here?

Implementation

Now this is implemented by using QThread and worker. The boards UI is running in the main thread but the worker is running separately in a different thread. The worker does most of the heavy lifting on extracting information out of model objects. The communication between UI and worker is done by signals, it is asynchronous in this case i.e. the slow extraction will not stop UI from responding. The extracted information gets displayed when it's available and sent back to UI.

Testing

  • Testing should be done mainly by using the standard Python unittest, with a little help from QTest that comes in PyQt. What QTest does is to simulate keystrokes, mouse clicks, and mouse movement. A good example can be found here: http://www.voom.net/pyqt-qtest-example

Misc

  • partial() created inside a Qt connection will not be garbage collected after PyQt 4.3. In the past I may have kept references to these partials in order to avoid garbage collection.

  • Tracking down extra references kept to objects can sometimes be quite tricky (to say the least). Some common "hiding places" in PyQt are signals connected using lambda (or functools.partial), or monkey-patched protected methods. But there are no hard-and-fast rules for finding them. The problems usually lie in how the objects are created, rather than in how they are deleted.

  • Memento Pattern, potentially useful

  • Proxy Pattern, used at some places, eg. model data

  • Facade Pattern, is scene manager one of this? Might need to check it vs. Mediator Pattern.

  • Mediator Pattern, may be useful, as it defines an object that encapsulates how a set of objects interact.