Friday, December 19, 2008

BUI - LayoutManager and much more

Last weekend I progressed on restructuring the code. I managed to get rid of some cruft. Of course there is some new cruft to fix. :)

Here are the major changes made summarized:
  • Implemented LayoutManager to handle calculating all layout related things for all user interface containers/elements. This made it possible to clean up the mess caused by different coordinate systems used by the backend and OpenGL based frontends. In BUI origin of a window is imagined to be at the top left corner. Also the y axis is inverted (value of y is inverse of y in Cartesian coordinate system).
  • Cleaned up the way render is defined in AbstractObject. This leads cleaner frontend code as coordinate doesn't have to be passed on anymore. Also super is no longer needed.
  • Implemented Separator element. It just draws a line based on the container inside which it has been placed. Note that name attribute works only in case it has been placed inside VerticalContainer (vertical flow leads to horizontal separator and vice versa). In this case the name is rendered in the middle of the separator. Also line is not drawn on that specific location. To implement the missing case I probably have to look into Pango and use Cairo to render Pango text into an OpenGL surface or just a raw image which isn't as nice.
  • I also began to separate drawing code into it's own modules. In case of OpenGL the idea is to make the code generic so that the actual import can be handled on the frontend side. This means the same functions can be reused on multiple frontends.
  • Converted Fill to element. It did not make sense to define Fill as a container as it logically cannot contain other elements. Therefore it is an element.
  • Cleaned up the way Blender WindowManager calculates mouse coordinates. It should work alright now. There was a tiny glitch before.
There are still plenty of things to implement before the first official release but at least it's progressing. :) In the next weeks I hope to implement features more visible to the user (CustomContainer with free layout (element coordinates as relative?), event mapper, scrollbars, auto width/height, StyleManager?, ???).

Friday, December 12, 2008

BUI - Image Filter tweaks, AllMethodsStatic, background color for containers/elements

I did not get much done since the last post. The week was pretty busy. After next weeks I will have holiday of three weeks so I expect to be more productive then. Anyway I got something done:
  • I added some minor tweaks to the Image Filter example. For instance it shows the image names as labels now.
  • A new base class for container and event classes, AllMethodsStatic, is available. I used some metaprogramming to define this class to mimic namespaces. Essentially inheriting from this class makes all methods of the class static. This is nice as you don't have to write @staticmethod decorators in front of each constraint/event. The solution is not particularly pythonic but I think it works in this case, at least for now.
  • I made it possible to determine background color for a container or an element. This should be expanded later to make it possible to define gradient/image/whatever as a background. As a side effect implementing this clarified the code a bit. The rendering parts still need solid tests though.

Friday, December 5, 2008

BUI - state events and minor tweaks

Since the last blog post I have implemented following features for BUI:
  • Initial, very crude Pyglet frontend. Note that it does not do anything smart yet.
  • State events. This means that it is possible to check if mouse cursor is over some given element or container and then trigger an event handler. There is a new event hook known as on_mouse_over to activate this feature. See image_state_event.py example file for more details.
  • YAML definitions embedded in Python files work with tabs now. I added this feature as some Python coders prefer to use tabs instead of spaces favored by PEP-8. Now it should not cause as much confusion for the coder. Note that mixing spaces and tabs may cause issues still as expected.
  • I also did some miscellaneous clean up here and there. See commit logs for more details if you are interested.
  • Implemented simple image browser (with wildcards) for Blender. See http://code.google.com/p/bui/source/browse/trunk/examples/blender/imagefilter.py .
I also began to sketch my first totally custom user interface element for Blender. This element, vertical slider, should be usable for scrollbars as well. I implemented initial drawing code (very crude!) and came to realize my event mapping system needs a refresh. Basically the issue I encountered was that sometimes it is beneficial to be able to catch multiple events at once and then trigger some handler(s) based on that. For instance in this case the mouse cursor has to be over the thing which you use to control the value of the slider (does anyone know what this thing should be called?) and to adjust the value the user has to click mouse button (usually left one) and then move it.

After this specific case I noticed that this is something that should be doable generally. It is actually a part of design that should be separated altogether. Designwise this means that I have to implement an event to event handler mapper that handles the relations I need. In other words it should be possible to map multiple events (state or regular) to multiple handlers.

I think I end up implementing something like this in Python:

event_mapping = '''
- events: s_press, k_press # triggers at once when both s and k are pressed
event_handlers: walk_the_dog, feed_the_monkey
- events: left_mouse_pressed, cursor_over_element(some_element)
event_handlers: paint_on_cursor_location
- events: s_release, p_released, shift_press
event_handlers: write_to_blog
'''

...

# remember to pass the mapping to Application


Note that in the first case it should trigger also when the user releases the key for a while and then presses it again!

In the second part I used cursor_over_element instead of mouse_over_element because I think cursor is more generic than mouse. It is possible to use other input devices than mouse after all. Also note the pressed notation. In this case press is actually converted into a state using -ed. Hence the handlers get triggered all the time both given states are true.

In the third case state and regular events are actually mixed. The event triggers only if p has been released (ie. it is not pressed at the moment) and then s is released and shift pressed down simultaneously. This is an example of a case that just won't work in the system at it is nonsensible this way. If either s or shift case was removed or converted to a state, it would be just fine. It might be nice to be able to warn the developer about situations like this.

BEGIN EDIT:
I realized that the third case works. All the user has to do to trigger the handlers is to make sure p has not been pressed, shift is pressed (activates "press shift" and s is released (activates "s release"). When all those cases are true, the handlers are executed.
END EDIT:

It may be that I will retain at least parts of the old way of assigning handlers as the current way is quite adequate for some quick hacking but we shall see...

I also had a nice chat with the author of bpyGUI. To understand what you can do with bpyGUI, check out http://www.youtube.com/watch?v=QhDQRFLv3K0 . His approach is quite different from mine. Probably the neatest thing about his system is the way he implemented drag and drop. There are probably a couple of things to pick up there. :)

I realized that I might be able to get rid of those pesky @staticmethod decorators simply by creating a metaclass containing the behavior. This is something that must be done before the official release of course as it simplified the coding process quite a bit and makes code more readable.

As probably most Python people noticed, Python 3.0 was released! This was a really big thing for the Python community and a long awaited one. It will probably take a while till I begin to port bui to Python 3.0 however. Before migration would make any sense, all my dependencies current dependencies (Blender, pyglet, pyyaml) would have to be available in Python 3.0 code.

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. :)

Wednesday, August 13, 2008

Cassopi testing

Today I wrote a tiny script that helps me to handle testing easier. Originally each file to be tested had to contain following block at the end (Does someone know if blogspot has code block btw?):

# enable contract checking
import contract
contract.checkmod(__name__)

def _test():
import doctest
return doctest.testmod()

if __name__ == '__main__':
_test()

Of course it's not much code but nevertheless on longer term it would just get annoying to add that to the end of almost each file. Also extending it would be somewhat nasty as you would have to edit all files containing it.

Therefore I ended up writing a simple script that can be executed at the root of Cassopi source (/cassopi/). You just type "./tester.py modulename" and it runs contract tests (if contract module is available) and doctests. I might add coverage testing there later but at the moment I am happy with those two. You can find the script at http://code.google.com/p/cassopi/source/browse/trunk/cassopi/tester.py .

I also found a great page containing more documentation for the contract module. Check out http://people.cs.uchicago.edu/~knorby/pycontract-doc/ .

Tuesday, August 12, 2008

Cassopi development site open

After a break (half a year!) I have resumed the development of my sketching application previously known as Jade. I decided to begin hosting the application on code.google.com . When registering the application I found out that a project named Jade already exists. That's why I renamed the project Cassopi. The new project site can be found at http://code.google.com/p/cassopi/ .

I will spend this month developing it. In the first stage I aim to get basic user interface working. This is quite a bit of work as I started from scratch using pyglet, pyopengl and OpenGL. I am not an expert at OpenGL so this works as a learning experience as well. I already have simple implementation of multiple viewports working already. :)

Here's a screenshot. Note that it's not perfect due to double buffering I think. This is something pyglet provides and I am not sure if there is anything I can do about it yet. It's not a priority issue anyway.