An inside view of the ADG design


Design overview

The project grew up arond the GObject library: the ADG canvas is developed in plain C using the object-oriented approach supplied by GObject. This does not mean an ADG based application must be developed in C: the basic idea, when the API will be stable enough, is to have a set of language bindings for higher level languages (above all the garbage-collected ones). Then the application could be developed using these languages much in the same way as GTK+ applications are conceived. The first language supported will probably be LUA.

The rendering is based on the cairo engine, so the ADG project fully shares strong and weak points of the underlying cairo library.

For the optional database connectivity my suggestion is to look in the libgda project, partly because it is an actively maintained project and partly because it uses the same technology (object-oriented C code using GObject and language bindings), so it can be easily integrated in a single application.

Using the library

Basically, an application based on ADG must fulfill the following tasks:

  1. define the model
    instantiate and settle an AdgModel derived class, that is the virtual object representing the mechanical part or whatever you need to show;
  2. populate the canvas
    add some entities to an AdgCanvas instance, such as a stroked representation of a model, and complete the drawing with decoration entities (title block, dimensions, labels, axis and whatever else needed);
  3. customize the rendering
    by default the ADG library provides and uses some predefined style but you still have the opportunity to change the way the drawing will be rendered, such as changing line thickness, colors, fonts, spacing between dimension, hatching mode and so on;
  4. render to a cairo surface
    once you have the canvas ready, just render it to a cairo surface: the ADG only provide the AdgWidget facility for rendering on a GtkWidget but checkout the list of availables cairo backends to know the available options and how to use them.

Define the model

The model is the abstract representation of the drawing subject (or part of it). The most common one is probably the AdgPath class, a model built around the cairo_path_t struct.

The units used in the model definition are not relevant, as they will became signicative only while used by an entity. It is common practice to use obvious units: for instance, in Italy you will use meters for building plants and millimeters for mechanical parts. When adding to the canvas an entity binded to this model, you should set a proper local map to scale the result as needed and get the proper ending scale.

You can have as many models you need, depending on the application complexity. For instance, if the part you want to manage changes a lot only on some specific detail, you can consider to move this everchanging piece on its own model leaving the rest in another one, and join the models just before populating the canvas.

Also, a single model can be referred by as many different entities as you need: for example, the same model can be used by more AdgStroke entities, thus allowing rendering with different maps.

Populate the canvas

This operation transposes the model to the view. Just instantiate an AdgCanvas and add entities inside it in the same way you construct a user interface in GTK+. Together with some representation of the model you will probably would like to add other decoration entities such as title block, dimensions, hatches and whatever needed to get the final drawing.

Drawing by hand or with a CAD gives you full control: you can choose where positioning quotes to avoid overlapping. An automatic drawing system doesn't have the cognition of proper position and a collision detection system is beyond the scope of this project. The ADG way to solve this issue is to provide everything needed to escape from (hopefully) any situation, allowing to customize almost everything. It is up to the application to put the quotes in a better place or to choose a proper scale.

Global and local matrices

Original sample (local matrix scaled by 2)One key concept of this approach is the use of two matrices that can be set transformed by every AdgEntity and derived objects: the global matrix and the local matrix.

Original sample (local matrix is identity)To grasp this concept, here are two shots of the adg-demo test program (found in the demo directory). The image on the right shows the same canvas with a local matrix scaled up while the global matrix is still the same.

As you can see, the model shape is scaled and the endpoints of the quotes followed this change but some other stuff kept the original scale, such as line thicknesses, text and arrow sizes, the distance from the extension lines to the shape and the spacing between the baselines.

You can think to the global matrix as a magnify glass: modifying it affects everything on the canvas. It works about the same way the traditional zoom works (allowing also translations, rotations and everything you can do using a matrix). As such, the global matrix is always applied, and it is applied as last transformation. The local matrix instead is quite unusual: it affects only something, mainly related to the model. It can also be "normalized" before being applied.

These two matrices can't be set directly. Instead the AdgEntity class provides APIs to manipulate the so called maps (short for affine maps). Every matrix is the result of combining by multiplication all the maps on the entity hierarchy.

In a typical example of an entity inside a container, the local matrix of the entity will be computed as follow (the same will apply for the global matrix):

AdgMatrix matrix, map;
adg_entity_get_local_map(canvas, &matrix);
adg_entity_get_local_map(container, &map);
cairo_matrix_multiply(&matrix, &map, &matrix);
adg_entity_get_local_map(entity, &map);
cairo_matrix_multiply(&matrix, &map, &matrix);

This allows some funny thing, such as scaling only a group of entities inside the same container or placing the same model more times in a canvas applying different matrices. Consequentially, changing the AdgCanvas maps will affect every entity.

By default global and local maps are initialized to the identity matrix, that is a matrix that doesn't change the source.

Customize the rendering

The rendering customization is provided throught the interaction of the following ADG components:

Styles

The style is the brick over which all the customization is done. Being a huge task that could grow beyond any guess, the ADG approach is to implement the styles in an opened way, without knowing or guessing anything about them.

This means adding a new style is a matter of subclassing AdgStyle: the only requirement is to implement the apply() virtual method. You could put all the stuff you consider style stuff inside this new class: it is handled as an opaque object by the ADG library itsself. A new style will be likely used by a new entity, so you'd probably access the style instance only from the render() method of this new entity.

Dresses

When an entity needs to access a style instance (usually inside its render() method), the AdgDress value should be resolved to its AdgStyle equivalent. The main getter function is adg_entity_style(), which executes the following operations up to when a style is found:
  1. adg_entity_get_style(entity, dress);
    check if the style is defined directly by this entity type, that is if it was explicitely set with adg_entity_set_style();
  2. adg_entity_style(parent, dress);
    if the subject entity has a parent returns its style by recursively call adg_entity_style() on the parent entity;
  3. adg_dress_get_style(dress);
return the default style for the given AdgDress value.

In the default implementation all the styles will be resolved by adg_dress_get_style(), but this implementation gives a good level of freedom, allowing the cascade overriding of a dress style with a single adg_entity_set_style() call. When required, new AdgDress values can be easily added with adg_dress_new(). Every dress always requires a default style, just to be sure to resolve any request as stated above.

Render to a cairo surface

To be useful, the canvas needs to be rendered somewhere. For convenience, the ADG library provides AdgWidget if the library has been built with GTK+ support enabled. This is a GtkWidget that can be embedded inside a GTK+ user interface but it is meant only as a demo widget.

In general you should use plain cairo to create the surface. The following example shows a quite basic way to render a canvas to a pdf file:

void
render_to_pdf(AdgCanvas *canvas)
{
    cairo_surface_t *surface;
    cairo_t *cr;

    surface = cairo_pdf_surface_create("test.pdf", 841, 595);
    cr = cairo_create(surface);
    cairo_surface_destroy(surface);

    adg_entity_render(ADG_ENTITY(canvas), cr);

    cairo_show_page(cr);
    cairo_destroy(cr);
}

You can check the demo/adg-demo.c source file for further examples.

Technical details was last modified by Nicola on Mon 19 Oct 2009 11:19:49 PM CEST
Hosted by Get Automatic Drawing Generation at SourceForge.net. Fast, secure and Free Open Source software downloads
Search on this domain