Devel Tutorial

Disclaimer: this page is currently a work in progress, and information could be incorrect or missing.

For a more complete reference on Devel, see the Devel Manual

Creating an plugin with Devel : develstart

In this tutorial, we will create a Graphite plugin equivalent to the quickstart plugin talked about in Programmer 101, using Devel.

We will see how to create the plugin core, then add commands, shaders and tools.

The code is available on our svn repository, and can be checkout anonymously on Inria Gforge, in Graphite/plugins/OGF/develstart.

First things first

We consider here that you already have read Programmer 101.

To perform this tutorial, you need to have Devel plugin loaded. You know it is loaded when you have the Devel menu in the menu bar at startup. See Devel Manual if you don't.

Then, open the GEL Terminal (GEL > Show terminal). This will help you to follow what Graphite is doing in the background and to understand what is going on in case you made a mistake.

Creating a plugin

The fisrt thing to do is to create the basic structure of the plugin. For this we will use the Devel > Create plugin command.

This one is easy, you just need to now the name of the plugin you want to create! Enter develstart in the name text-box then press OK.

The basic skeleton of the plugin is created in <Graphite_dir>/plugins/OGF/develstart.

Add commands

Now, we want to be able to interact with the plugin. First thing we will do is to create a menu to launch some commands on Surface objects.

One important thing to get a grasp on is that here you will create a set of commands grouped together. All the commands of this set will target the same type of Grob, so you would need to create at least one for each Grob type you want your plugin to work with.

At first it will only contain one demo command (my_function()), but we will gradually add more interesting things.

Clic on Devel > Create commands.

In the first text field of the newly opened dialog box, enter the plugin name, "develstart". This will be the case on any other "Create ..." command of Devel as well: you have to specify each time the name of the plugin you are working on.

In the "type" field, you have to choose the Grob type you want the commands to work on. Here, select "Surface".

The "name" field will determine the top menu item for these commands, just like devel's "Devel". So in the "name" text-box, enter "DevelStart" and press OK.

The files surface_devel_start_commands.h and surface_devel_start_commands.cpp are created in <Graphite_dir>/plugins/OGF/develstart/commands/.

Open the header file.

 gom_attribute(menu, "my_subcategory") ;
 void my_function(int my_integer = 0, const std::string& my_string = "hello") ;

We can see here the declaration of the my_function() command, with its default parameters. The gom_attribute statement preceding the method declaration is here to indicate to the GUI creation system that this command goes in a sub-menu called "my subcategory".

Now, we are going to replace it with a command that scales Surface objects.

In the header file, replace the declaration of my_function() by:

 gom_attribute(menu, "geometric alteration") ;
 void my_scale_command(double ratio_x = 1.0, double ratio_y = 1.0, double ratio_z = 1.0) ;

And in the .cpp file, replace the implementation of my_function() by:

 void SurfaceDevelStartCommands::my_scale_command(double ratio_x, double ratio_y, double ratio_z) {
	FOR_EACH_VERTEX(Map, surface(), it) {
		double x = ratio_x * it->point().x() ;
		double y = ratio_y * it->point().y() ;
		double z = ratio_z * it->point().z() ;
		it->set_point(Point3d(x,y,z)) ;
	}
	surface()->update() ;
 }

Now, you can compile your plugin and load it in Graphite (See Devel Manual for details). As we will continue to improve on it along this tutorial, you should add it to the modules loaded at startup (File > Preferences > Modules, enter "develstart", press "Add" and "save configuration".)

Load a model or create a Surface object with

  • Scene > create object, with type = "Surface"
  • Mesh > shapes > create icosahedron

You should see the "DevelStart" item in the menu-bar. Execute the "my scale command" function, set the ratio parameters to something like "1" , "1.8" and "1", then press "apply". Your mesh should show a deformation similar to this one:

If you ant to add new commands for the same Grob type, just add new functions to the class, and put their declaration just after the gom_slots keyword in the header file.

To learn more on commands, have a look a the source code of Graphite, particularly in <Graphite_dir>/src/packages/OGF/<package>/commands for the surface, quick_start, volume, parametrizer and scene_graph packages).

Create a new shader

We will now create a new way to display Graphite objects. Here, we want a shader that can shrink the facets of surfaces, like the one in quickstart.

Clic on Devel > Create shader.

Like before, set plugin name to develstart.

For the base class, we have to specify the Shader class we want to inherit the behavior from. We have to use the class name of the base shader. Here, it's about displaying surfaces in 3d, and we want to start with the most basic representation of surfaces ("Plain"), so given the naming convention of shader classes (see Devel manual), we use PlainSurfaceShader3d.

The name of the shader you are creating must be the shader name, which differs from the class name used for the base class. For example, the shader name of PlainSurfaceShader3d is "Plain". Enter "Shrink" in the "name" text-box, then press OK.

The files shrink_surface_shader3d.cpp and shrink_surface_shader3d.h are created in <Graphite_dir>/plugins/OGF/develstart/shaders/.

Currently, the function that does all the shader's work looks like that:

 void ShrinkSurfaceShader3d::draw(RenderingContext* out) {
    PlainSurfaceShader3d::draw(out) ;
 }

As you can see, it just calls the base class' implementation. Now we will modify that to make it show surfaces with shrunk facets.

First, add this line after the other includes:

 #include <OGF/cells/map/geometry.h>

Then replace the body of the draw() function by this code:

	double s = 0.9 ;

	RenderingPipeline* pipeline = out->pipeline() ;
	enable_lighting(out) ;
	pipeline->color(surface_style_.color) ;
	MapTexVertexNormal normal(surface()) ;
	FOR_EACH_FACET(Map,surface(), it) {
		Point3d c = Geom::facet_barycenter(it) ;
		pipeline->begin(RenderingPipeline::POLYGON) ;
		Map::Halfedge* jt = it->halfedge() ;
		do {
			pipeline->normal(normal[jt->tex_vertex()]) ;
			pipeline->vertex(c + s * (jt->vertex()->point() - c)) ;
			jt = jt->next() ;
		} while(jt != it->halfedge()) ;
		pipeline->end(RenderingPipeline::POLYGON) ;
	}

s is the ratio of shrinking, so here facets will be displayed at 90% of their original size.

Close Graphite, configure and compile the plugin, restart Graphite and load the plugin. Then open a model or create a Surface object.

Select "Shrink" in the Shader list. Normally, your mesh should look a bit like this one:

So, now, what if we want to be able to change the shrink ratio at will ? To do that, we are going to add a gom_property to the shader.

To create a property, you need to provide the matching getter and setter functions, named get_<property> and set_<property>.

Add the following lines to the header, directly after the gom_properties keyword.

 void set_surface_shrink(int x) { shrink_ratio_ = x ; update() ; }
 int get_surface_shrink() const { return shrink_ratio_ ;         }

Add the variable declaration after private:

 int shrink_ratio_;

And finally replace the ratio value by this calculation:

 double s = double(10 - shrink_ratio_) / 10.0 ;

Recompile develstart and restart Graphite, load an object and apply the "Shrink" shader. You should now see the "surface shrink" numeric box that allows you to modify the shrinking ratio on the fly.

To go further, feel free to look at the implementations of the shaders of Graphite (in <Graphite_dir>/src/packages/OGF/<package>/shaders for the surface, quick_start, volume, parametrizer and scene_graph packages).

Creating a new tool

It seems that version 2-alpha2 of Graphite has some problems with tools in plugins when CGAL is used. If you experience problems, you could try to disable the use of CGAL (comment USES_CGAL in <Graphite_dir>/CMakeOptions.txt and recompile), use the SVN sources of Graphite or wait for the next release if you need plugins with tools and CGAL at the same time.

Now, we will see how to create a way to interact with the objects using the mouse cursor. In Graphite, such interactions are possible with "tools", and Devel can help you create one.

We are going to make a tool that just recognize if you cliqued on a vertex or not.

Clic on Devel > Create tool.

Like for commands and shaders, enter the plugin name, "develstart".

The Grob type we want to work with is still Surface, so select it once again in the type list.

As base class, you usually will use the tool classes defined for the corresponding Grob, which are named <Grob_name>Tool. These classes implements enough for you to get access to the object you cliqued on (see surface_tool.[h|cpp] or line_tool.[h|cpp] in <Graphite_dir>/src/packages/OGF/surface/tools for example). You could go directly with Tool if you want to change the mechanism of picking, or on the contrary use more specific classes like SurfacePaintAttributeTool. Here we will be interested in accessing elements of surfaces, so we will use SurfaceTool as the base class.

Enter "VertexPicker" as name.

The files develstart/tools/vertex_picker_surface_tool.[h|cpp] are created, and the class of the tool is VertexPickerSurfaceTool. All version following 2.0-a2 have a corrected naming convention, so files would be surface_vertex_picker_tool.[h|cpp] and the class SurfaceVertexPickerTool .

For now, the class only have a constructor, a (virtual) destructor and a few gom_attributes.

    // specifies in which box this tool will be added 
    gom_attribute(category,"develstart") ;
    //an icon can be specified for this tool
    //(this example corresponds to GRAPHITE_ROOT/lib/icons/my_icon.xpm)
    //gom_attribute(icon,"my_icon") ;
    // specifies the help bubble associated with this tool 
    gom_attribute(help,"develstart tool") ;
    // the message is displayed in the status bar when this
    // tool is selected 
    gom_attribute(message,"insert your message here") ;

    gom_class DEVELSTART_API VertexPickerSurfaceTool : public SurfaceTool {
    public:
              VertexPickerSurfaceTool( ToolsManager* parent ) ;
               virtual ~VertexPickerSurfaceTool() ;
    } ;

To add interesting behavior when cliquing, dragging, ... to this tool, we are going to override some functions of the Tool class.

If you look at tool.h (in <Graphite_dir>/src/packages/OGF/scene_graph/tools), you can see that the following hooks are available:

  gom_slots:
        virtual void grab(const RayPick& value) ;
        virtual void drag(const RayPick& value) ;
        virtual void release(const RayPick& value) ;
        virtual void reset() ;
        virtual void configure() ;

At first, we will focus on just saying if what we have cliqued on is a vertex or not.

Add to vertex_picker_surface_tool.h the following declarations:

 virtual void grab(const RayPick& p_ndc) ;

and change the gom_attribute "help" to "vertex picker".

In the cpp file, add after the includes:

 #include <OGF/surface/tools/surface_picker.h>

and also add the implementation:

 void VertexPickerSurfaceTool::grab(const RayPick& p_ndc) {
        Map::Vertex* v = picker()->pick_vertex(p_ndc) ;
        if(v == nil) {
	   status("No vertex here...") ;
        } else {
	   status("This is a vertex !") ;
        }
 }

This time too, quit Graphite, reconfigure and rebuild the plugin, restart Graphite and load a mesh. You should see a new box named "develstart" at the bottom of the "Tools" panel, with the "vertex picker" in it. Select this tool and try cliquing on various places of your mesh. You should get something like this (check the checkbox in the "vertices style" box, in the "Shaders" panel to show the vertices):

Right now, as you can see, the VertexPicker you made does exactly the same thing for every buttons of the mouse. It is also possible create a "meta-tool" that binds different other tools to each button. The resulting tool will be the one displayed in the GUI, giving the impression of a single tool doing multiple different things. Let's try to make one.

We are going to make a tool which does the same thing as the first one, but with vertices on the right mouse button, and with facets on the right one.

Do the same process as before for the FacetPicker

  • Devel > create tool
    • plugin name : develstart
    • type : Surface
    • base class name : SurfaceTool
    • name : FacetPicker
  • configure the plugin project
  • Add to the header:
 virtual void grab(const RayPick& p_ndc) ;
  • change "help" gom_attribute to "facet picker"
  • Add to the .cpp:
 #include <OGF/surface/tools/surface_picker.h>

and

 void FacetPickerSurfaceTool::grab(const RayPick& p_ndc) {
     Map::Facet* v = picker()->pick_facet(p_ndc) ;
     if(v == nil) {
         status("Not a facet...") ;
     } else {
	 status("This is a facet !") ;
     }
 }

And now create the tool that will bind them together, which will inherit from MultiTool:

  • Devel > create tool
    • plugin name : develstart
    • type : Surface
    • base class name : MultiTool
    • name : MultiPicker
  • configure the plugin project
  • change "help" gom_attribute to "multi picker"
  • in the .cpp file, add after the includes:
 #include <OGF/develstart/tools/vertex_picker_surface_tool.h>
 #include <OGF/develstart/tools/facet_picker_surface_tool.h>

and replace the contructor by:

 MultiPickerSurfaceTool::MultiPickerSurfaceTool(ToolsManager* parent) : MultiTool(parent) {
     set_tool(1, new VertexPickerSurfaceTool(parent)) ;
     set_tool(3, new FacetPickerSurfaceTool(parent)) ;
 }

Ok, now you can compile the plugin and test it in Graphite. You should see three tools in the "develstart" toolbox now. As you can see when you try them all, the "Multi picker" allows to do both actions with the same tool, like we wanted. But there is still a problem that remains : you now have two redundant tools! So we now will make them disappear from the GUI, to let only the evolved version available.

As is explained in Programmer 101, the GUI is generated automatically from the analysis of the sources, thanks to gomgen. This process relies on keywords like gom_class and gom_attribute. So to remove parts of the generated GUI, all we have to do is to remove these keywords from the corresponding classes.

First, replace the gom_class keyword of the VertexPickerSurfaceTool and FacetPickerSurfaceTool classes by class, then remove all their gom_attributes. You sould get something like this for those two classes:

 namespace OGF {
    class DEVELSTART_API VertexPickerSurfaceTool : public SurfaceTool {
    public:
        VertexPickerSurfaceTool( ToolsManager* parent ) ;
        virtual ~VertexPickerSurfaceTool() ;
        virtual void grab(const RayPick& p_ndc) ;
    } ;
 }

The very last thing to do is to remove the declaration of the tools from common/develstart_common.cpp. Remove the lines referring to these classes in initialize(), so ogf_register_grob_tool<Surface,MultiPickerSurfaceTool>(); is the only tool registration remaining.

Recompile, ... etc. Tadaaaa! You now have only your most powerful tool left!

The bottom line here is that you are not obliged to do it his way: once you are familiar with the system, you can progressively shift from using Devel for every class to using it only when it is quicker to do so. For example here, a quicker way to do the same MultiPicker tool would be to create the MultiPicker files with Devel, and to write directly in the same file the two other classes.

You can experiment with the other hooks (drag, release, etc) to add more interesting behavior to your tools. Also, feel free to have a look at the at the implementations of the tools of Graphite (in <Graphite_dir>/src/packages/OGF/<package>/tools for the surface, quick_start, volume, parametrizer and scene_graph packages).

And now, create a new type of Grob

One of the cool things of Graphite is that you are not limited to the types of objects provided , you can create your own types of Grob. Here, we are going to add to our plugin a new Grob inheriting from Surface.

Clic on Devel > Create grob.

As always, insert the plugin name.

Up to release 2.0-a2, Devel needs the base class to be the Grob class, but you could use other Grob classes like Surface or PointSet by editing manually the created code. Following versions can handle any existing Grob class as base class out of the box.

For the class name, enter "MyGrob".

An optional file extension can be associated with the Grob. Here, insert "mygrob" to associate the *.mygrob files with your Grob, then press OK.

When you create a Grob, Devel automatically creates base classes for the corresponding commands, shaders and tools. So the files created are :

  • develstart/grob/my_grob.[h|cpp]
  • develstart/commands/my_grob_commands.[h|cpp]
  • develstart/shaders/my_grob_shader.[h|cpp]
  • develstart/tools/my_grob_tool.[h|cpp]