Friday, November 28, 2008

BUI - week's development

Since last blog post I made following major changes to BUI:

  • EmptyContainer and EmptyElement are gone. Fill container has replaced them.
  • I got rid of most of globals() in the source. There is still one place left to repair though (I have some kind of idea how to fix this).
  • Events and Constraints are stored in classes (or in separate modules. I have not tested this option but it should work) and passed on to Application. See examples folder in the source repository to see how this mechanism works.
  • Added Image element for Blender. Note that you can use vector (SVG) images with Image element. It scales the image automatically based on given width and height. If no dimensions are given, it will use dimensions defined in the image itself.
  • Added Icon element for Blender. Icon makes it possible to render icons (default set) provided with Blender.
  • Added ColorPicker element for Blender. ColorPicker is just the basic color picker provided with Blender. I tried to get Normal element to work too but for some reason the callback function required for value updates of the element did not appear to work. This might be a bug in Blender and I will investigate this issue further.
  • Extended bones.py example file. Now it is possible to modify Z location of an object named the same as the slider created based on bones of the armature. Check out the example .blend provided to see and understand how this really work. Essentially it is just a handy replacement for Blender's driver system.

Friday, November 21, 2008

BUI - week's development + Introduction to Python doc

As mentioned in the last Friday's post the week indeed was pretty busy. I did manage to develop BUI a bit though. Nathan "jesterKing" Letwory tested BUI in a project of his. Feedback has been positive so far. He also contributed the first patch for the project. The patch added Number and IntNumber elements for Blender.

I also made it possible to assign events to key presses. Example:

structure_keys = '''
a: add_monkey # assigns event add_monkey to press of a
d: delete_all # same idea here
s:
press: do_something # assigns an event to press of s
release: do_something_else # assigns an event to release of s
'''


So by default events are mapped to press. Should you want to attach an event to key release, use the syntax described above. Note that it should work even without press portion.

I also fixed a bug related to hiding individual elements (it was possible to hide only containers before). I also added Fill element that will eventually replace both EmptyElement and EmptyContainer. Furthermore I restructured the code a bit and got rid of some nasty parts. There is still some cleaning up to do though.

As an interesting sidenote I found something quite similar to BUI, at least if you look at the way the user interface is defined. So check out IUP (http://www.tecgraf.puc-rio.br/iup/). The nice thing about this discovery is that I might be able to find use for some of their ideas in BUI (Z-container, more elements?). :)

Also during the week I finished a brief documentation about Python. It is available at http://bebraw.googlepages.com/introductiontopython.pdf . Basically Introduction to Python is a crash course to Python in an object oriented way. It helps if you happen to know something about the paradigm already. Even if you don't it might give you some clue about it. It would probably make sense to "bloggify" the whole thing at some point and make it more beginner friendly but we'll see about that.

Friday, November 14, 2008

BUI - development site

I modularized BUI and opened a development site for it. The site can be found at http://bui.googlecode.com/ . At the moment I am in process of implementing World (parent of containers). The plan is to have World to contain general rules (world size, element height of children without height of their own). Furthermore a World should be able to contain many children. This would make it possible to mimic panels of Blender. I have some other ideas as well but I will discuss them in more detail after I have the basic concept done.

If you look at the root of the source code repository, you can find three example files (bones.py, filter_layers.py and simple.py) that demonstrate the usage of BUI in context of Blender. To make those examples to work, set Python path of Blender so that it can find the place in which you checked out the repository. Or just put the repository in your scripts directory should you have one.

After I have finished implementing World, I still need to add basic way to map keys and mouse events (move, press buttons) to events. This is something that is actually pretty easy to do.

The next two weeks are going to be a bit busy so the development work will be probably a bit slower than usual. After that it should return back to normal.

Friday, November 7, 2008

BUI - Basic UI framework

Last weekend I began to develop my own user interface abstraction using Blender's Python API. The API offers basic access to Blender's drawing (Blender widgets) and event system. Based on this I built my own little system.

To drive development forward I used my "filter layers" concept. I find Blender's layer system a bit restricting. Filter layers offer the user a way to define what objects belong to a given layer by defining a filter. In other words you can have all lamps on one layer, all cameras on one and so on.

My approach is quite simple. I abstracted the structure of the user interface using YAML. It should be possible to change this to support other formats, such as XML, without too much effort. Here's a snippet of what user interface definition looks like at the moment:


ui_structure = '''
VerticalContainer:
width: 400
children:
- HorizontalContainer:
children:
- Label:
name: Filter layers v0.9
- PushButton:
name: X
tooltip: Quit script
event_handler: quit_script
width: 20
- EmptyContainer:
height: 10
- VerticalContainer:
name: layers
children:
- UIStructure:
name: layer_structure
- HorizontalContainer:
children:
- PushButton:
name: Add layer
tooltip: Add new layer
width: 100
'''


The treelike structure consists of containers and elements. Containers handle the order in which the elements inside it are rendered. So if you use HorizontalContainer, elements are laid out horizontally. In case of VerticalContainer, as you might expect, the elements are laid vertically. EmptyContainers can be used to add empty space to the layout. I originally used specific padding property put decided to remove it as EmptyContainers proved to be more simple and nicer to handle.

If you look closely, you can find a special element known as UIStructure. This actually represents a link to another tree. The reason why I implemented it this way is because I needed to duplicate certain parts of tree in the user interface code for instance when I am adding a layer or a filter.

The whole structure is converted in object format when the application is run. I wrote simple traversing functions that can be used to find elements in it. Also the user interface tree can be modified on runtime.

It is possible to attach an event handler to each element. The names of the event handlers are defined implicitly by default following Convention over Configuration principle. So if you have a PushButton named "Add layer", you can expect that it uses an event handler known as add_layer. Of course this is not desirable always so I made it possible to define event handlers explicitly.

At one point during the development I noticed that I need something more than just user interface definition and events. I realised that there are certain constraints that apply to the user interface all the time. For instance layers have to be numbered starting from one and increasing by one till nineteen. Or "Show filter" must be renamed to "Show filters" should a layer contain more than just one filter and vice versa.

To solve this issue I implemented a constraint system. Constraints are named as some_descriptive_name_here_constraint. They are evaluated each time the script window of Blender is redrawn. I suppose there are some optimizations that could be done but I am not too worried about the performance at this point as the constraints defined are pretty simple anyway.

At the moment I am in progress of generalizing and restructuring this whole system on proper modules. The idea is that there is a specific abstract part. Based on this common part drawing and event handling implementations can be made for wanted platform such as Blender or Pyglet. In other words should you want to use the basic system, all you need to do is to subclass event manager, write elements and hope it works. :)

Saturday, November 1, 2008

py.test - change in testing platform

Python's unittest module is decent for some quick testing. It's a bit cumbersome to use though. Especially when compared to something extremely simple yet powerful like py.test.

After dabbling around with py.test a while I came to realize that's the way I want to write tests. Let's take a look at small excerpt from parser_test.py and see how it works:


...

minimal_structure = '''
VerticalContainer:
'''

def test_StructureParser_parse_minimal():
structure_parser = StructureParser(minimal_structure)

assert isinstance(structure_parser, StructureParser)
root = structure_parser.parse()
assert isinstance(root, VerticalContainer)

...


The point of this test is to guarantee that first of all we can create StructureParser. Next we parse, get the root node of the parsing as result and check that the type of class gotten is right.

The main things here are the way the file was named (parser_test.py), the way the function was named (test_StructureParser_parse_minimal) and the way assert statements are used. If I want to execute the test(s), all I need to do is to type "py.test" in the terminal.

Based on where I executed py.test, it finds the tests and executes them. This is done recursively beginning from the directory in which it was executed. If the name of the file is in form test_foobar.py or foobar_test.py (we stick to this convention in Cassopi), it checks if the file contains any tests (test_StructureParser_parse_minimal for instance!) and executes them.

Here is another more comprehensive test found directly after the test presented above from the same file:


...

structure_with_padding_in_all_directions = '''
VerticalContainer:
padding:
top: 5
left: 10
right: 15
bottom: 20
'''

def test_StructureParse_parse_vertical_container_with_padding():
structure_parser = StructureParser(structure_with_padding_in_all_directions)

root = structure_parser.parse()
assert isinstance(root, VerticalContainer)
assert root.padding.top == 5
assert root.padding.left == 10
assert root.padding.right == 15
assert.root.padding.bottom == 20

...


This case is pretty much the same as the previous one except that we have added more data. Note that we still assert that we have the right instance type. The reason I did this because each test should be independent (see Kent Beck's rules of testing). In other words no test should depend on other.

I structure my tests so that each covers different aspect. I start by focusing on basic aspects and then iteratively move on to more focused concerns. As you can infer based on given examples, the concepts we are dealing with are fairly high level. We hide the crude implementation details and test parts having different abstraction level separately.

The main point about testing is that they help us to define the way the code should behave. The underlying implementation just implements these set of rules. This is one of the strong points of test driven approach. It makes refactoring code really easy and I claim that you end up with more simple and powerful design in the end.

One of the reasons for this is that when you want to add new functionality to the system, you write a test first. First you write piece of code that makes the test pass. After this you can see how it fits the design. If the fit is not that good, you can easily refactor the design. That's why the tests were written for.

When you are implementing a piece of functionality this way, you may see the design evolve radically. It is probably quite clunky at first but at the end up you might have something actually quite elegant and perhaps even reusable.

So far my usage of py.test has been fairly basic but it has proved already to be nice and fast to use. There are still a couple of things I need to figure out how to handle with py.test. First of all it would be nice to know if there was some easy way to check test coverage. Secondly it might be interesting to look into using mocks as this allows nice separation of modules. In other words it allows to test modules without having them to affect each other.

In case you want to find more Python testing tools, http://pycheesecake.org/wiki/PythonTestingToolsTaxonomy is well worth checking out. Due to amount of available tools it may actually be quite a bit of challenge to find the right tools for the job. :)