Legacy Mesh Source Plugin Tutorial

From K-3D

Jump to: navigation, search
Note: This article contains information specific to K-3D prior to version 0.7. This information is obsolete and is retained for historical reference only.

Overview

In this tutorial we will create a very simple plugin for making a poly face like the one you see below. Once you complete this tutorial maybe you should read the Legacy Plugin Tutorial for a more general knowledge.

Image:Face Plugin Finished.png

That face will have properties: width and height. The other properties that you see on the image above come somehow by "default". (inherited)

Notice 1: This tutorial works on linux, if you work with windows you'll have to research how to do the same. If you want to make a Tutorial for windows you are very welcome to upload it to this wiki. (or you can leave in the discussion about this article your wish to have one. If there is enough people, probably we will come up with one tutorial)
Notice 2: If you have a question, opinion, suggestion or improvement about this tutorial or about developing on k3d, please do not hesitate to contact us on the k3d-development mailing list. Also feel free to leave your contributions (orthographic and grammatical corrections too) on the discussion (tab above also) of this page. Joaquin

What you need

  • A GNU\Linux installed
  • K3d installed and the k3dsdk.
    • See Getting Started and Generic Build.
    • In my tests I used the k3dsdk from the .tar source code, because it failed with the installed k3dsdk.
    • Warning: This tutorial is based on the k3d 0.6 API. If you are using k3d 0.7 some of the interfaces have been moved to the k3d::legacy interface. (as you can see in the doxygen files in the docs links)
  • gcc and g++ installed. (version 4 would be ok)
  • A text editor.
    • With color highlighting and text folding would be better, like Kate or Scite.
  • Know something about the linux command line. (when you see a $ that means the command line)
  • A basic knowledge of C++ and C.

Compiling and loading

Showtime!
Here is the whole code, below are the steps for compiling and loading it into k3d.

 #include <k3dsdk/imaterial.h> 
 #include <k3dsdk/node.h>
 #include <k3dsdk/persistent.h> 
 #include <k3dsdk/material.h>
 #include <k3dsdk/material_client.h>
 #include <k3dsdk/measurement.h>
 #include <k3dsdk/mesh_source.h>
 #include <k3dsdk/module.h>
 
 #include <iterator>
 
 namespace libk3dmymodule
 {
   
   /////////////////////////////////////////////////////////////////////////////
   // poly_face_implementation
   
   class my_poly_face_implementation :
     public k3d::material_client<k3d::mesh_source<k3d::persistent<k3d::node> > >
   {
     typedef k3d::material_client<k3d::mesh_source<k3d::persistent<k3d::node> > > base;
   
   public:
     my_poly_face_implementation(k3d::idocument& Document) :
       k3d::material_client<k3d::mesh_source<k3d::persistent<k3d::node> > >(Document),
       m_width(init_owner(*this) + init_name("width") + init_label(_("Width")) + init_description(_("Cube width")) + init_value(5.0) + init_step_increment(0.1) + init_units(typeid(k3d::measurement::distance))),   
       m_height(init_owner(*this) + init_name("height") + init_label(_("Height")) + init_description(_("Cube height")) + init_value(5.0) + init_step_increment(0.1) + init_units(typeid(k3d::measurement::distance)))  
     {
       m_material.changed_signal().connect(make_reset_mesh_slot());    
       m_width.changed_signal().connect(make_reset_mesh_slot());   
       m_height.changed_signal().connect(make_reset_mesh_slot());
     }
   
     void on_create_mesh(k3d::mesh& Mesh)
     {
       Mesh.polyhedra.push_back(new k3d::polyhedron());
       k3d::polyhedron& polyhedron = *Mesh.polyhedra.back();
 
       const double width = m_width.value();  
       const double height = m_height.value();   
       k3d::imaterial* const material = m_material.value();
         
       #if 1 //point block
       k3d::point* a_point = new k3d::point(0, 0, 0); 
         Mesh.points.push_back(a_point);        
       k3d::point* b_point = new k3d::point(width, 0, 0);
         Mesh.points.push_back(b_point);
       k3d::point* c_point = new k3d::point(width, height, 0);
         Mesh.points.push_back(c_point);
       k3d::point* d_point = new k3d::point(0, height, 0);      
         Mesh.points.push_back(d_point);        
       #endif
       
       #if 1 //edges block
       boost::multi_array<k3d::split_edge*, 1> edges(boost::extents[5]);
       edges[0] = new k3d::split_edge(a_point);
       edges[1] = new k3d::split_edge(b_point);
       edges[2] = new k3d::split_edge(c_point);
       edges[3] = new k3d::split_edge(d_point);
       
       for(unsigned long i = 0; i < 4; ++i)
         edges[i]->face_clockwise = edges[(i+1)%4];
       #endif
       
       #if 1 //face block
       k3d::face* const new_face = new k3d::face(edges[0], material);
       polyhedron.faces.push_back(new_face);
       #endif
   
       assert_warning(is_valid(polyhedron));
     }
   
     void on_update_mesh(k3d::mesh& Mesh)
     {
     }
   
     k3d::iplugin_factory& factory()
     {
       return get_factory();
     }
   
     static k3d::iplugin_factory& get_factory()
     {
       static k3d::plugin_factory<k3d::document_plugin<my_poly_face_implementation>, k3d::interface_list<k3d::imesh_source > > factory(
         k3d::uuid(0x51ecc5ce, 0xf8bf43ed, 0xabba2ed7, 0x3e1ed1d3),
         "MyPlugin",
         "Generates a face",
         "Tutorials",
         k3d::iplugin_factory::EXPERIMENTAL);
   
       return factory;
     }
   
   private:
     k3d_data(double, immutable_name, change_signal, with_undo, local_storage, no_constraint, measurement_property, with_serialization) m_width;
     k3d_data(double, immutable_name, change_signal, with_undo, local_storage, no_constraint, measurement_property, with_serialization) m_height;
   };
   
   /////////////////////////////////////////////////////////////////////////////
   // poly_face_factory
   
   k3d::iplugin_factory& poly_face_factory()
   {
     return my_poly_face_implementation::get_factory();
   }
 
 } // namespace libk3dmymodule
 
 
 K3D_MODULE_START(libk3dmymodule, k3d::uuid(0xb1aa0867, 0x37404b32, 0xb80add63, 0xfb02fed0), Registry)
        Registry.register_factory(libk3dmymodule::my_poly_face_implementation::get_factory());
 K3D_MODULE_END 

  • Create somewhere a dir for your new plugin. A plugin goes inside a module that's why the name.
$ mkdir your_name_module
  • So, copy-paste the code to a text file. For example with the name my_poly_face.cpp (must be .cpp) and save the file on the created directory.
  • Let's compile! On the created directory run :
$ gcc -shared -o my_poly_face.so my_poly_face.cpp `k3d-config --cflags --libs` -lstdc++
  • Probably that will fail. Could be because it doesn't find the headers (.h), it doesn't find the k3d-config or both.
For the k3d commands: If you installed k3d from the tar.gz and you have another version which is from the distribution, you will probably want to use the newer version installed from the tar.gz. So for the following commands replace: (where /usr/local/k3d is the directory where k3d is installed)
  • k3d -> /usr/local/k3d/bin/k3d
  • k3d-uuidgen -> /usr/local/k3d/bin/k3d-uuidgen
  • k3d-config -> /usr/local/k3d/bin/k3d-config
Replace where needed. The command should look like:
      $ gcc -shared -o my_poly_face.so my_poly_face.cpp `/usr/local/k3d/bin/k3d-config --cflags --libs` -lstdc++
For the headers: If you have problems with the headers (i.e. you dont have them on /usr/include/ or you have two versions of k3d) you can tell the compiler where your headers are with the -I option.
For example, if you extracted the .tar on /home/user/. The command should look like:
      $ gcc -shared -o my_poly_face.so my_poly_face.cpp `k3d-config --cflags --libs` -lstdc++ -I/home/user/k3d-0.6.3.1/
Where /home/user/k3d-0.6.3.1/ has the k3dsdk directory (also could be /usr/local/k3d/include/k3d/ if installed)
  • If everything went well you compiled your new module. It is time to try it.
  • First check it alone.
$ k3d --plugins `pwd` --log-level debug --ui none --exit
  • That should print
INFO: Loading plugin module my_poly_face.so
  • Which indicates that your new module was successfully loaded!
  • Now with all the others.
    • As you can see the --plugins option tells to load from the k3d directory (&) and from the current directory (pwd).
$ k3d --plugins "&:`pwd`" --log-level debug
  • That should give you a screen like this:
Image:Face Plugin K3d.png
  • On the menu Create->Tutorials->MyPlugin:
Image:Face Plugin Finished.png
  • Play with the width and height properties.

Analyzing the code

By analyzing the code we will see which parts of the code you have to change in order to create new plugins. (that creates meshes)

The mesh generation

At this point we analyze the core of our plugin. This is where you will probably want to work to make new objects.

  • Let's analyze the code step by step in the on_create_mesh() function:
  • First the function receives as an argument the mesh where it can work (a "pointer" in fact).
   void on_create_mesh(k3d::mesh& Mesh)
   {
  • It creates a new k3d polyhedron and puts it on the mesh with the push_back function:
    • Then it asks for that polyhedron an points to it with the polyhedron variable.
     Mesh.polyhedra.push_back(new k3d::polyhedron());
     k3d::polyhedron& polyhedron = *Mesh.polyhedra.back();
  • Those lines are the same as writing:
     k3d::polyhedron& polyhedron = *(k3d::polyhedron*)new k3d::polyhedron();  
     Mesh.polyhedra.push_back(&polyhedron);      
  • It evaluates the values of the properties:
     const double width = m_width.value();  
     const double height = m_height.value();   
     k3d::imaterial* const material = m_material.value();
  • Now it will use those properties and the polyhedron mesh to create the object.
    • First it creates the points.
    • Then the edges.
    • Then the faces.
The points
  • This block creates the points you see in the figure. (where w=width and h=height)
Image:Face Plugin Points Names.png
     #if 1 //point block
     k3d::point* a_point = new k3d::point(0, 0, 0); 
  • The k3d::point is the class for representing points on the k3d meshes. (k3d::point doc)
       Mesh.points.push_back(a_point);        
  • It puts the point into the mesh.
     k3d::point* b_point = new k3d::point(width, 0, 0);
       Mesh.points.push_back(b_point);
     k3d::point* c_point = new k3d::point(width, height, 0);
       Mesh.points.push_back(c_point);
     k3d::point* d_point = new k3d::point(0, height, 0);      
       Mesh.points.push_back(d_point);        
     #endif
  • If you comment the other two blocks of code setting the #if 1 to #if 0. You can compile and run k3d and you'll see how the vertices are created independent from the faces. As you can see in the image:
Image:Face Plugin Points.png
The edges
  • If you commented the edges block, uncomment it.
     #if 1 //edges block
     boost::multi_array<k3d::split_edge*, 1> edges(boost::extents[5]);
  • Here it creates a multidimensional array of the boost library, called edges.
    • boost::multi_array<k3d::split_edge*, 1> creates a multidimensional array of pointer to k3d::split_edge of dimension 1.
    • (boost::extents[5]) tells how many elements the array has.
     edges[0] = new k3d::split_edge(a_point);
  • Here it creates an edge (k3d::split_edge doc). It only needs one vertex for its creation, later we set the other vertices that define the edge.
     edges[1] = new k3d::split_edge(b_point);
     edges[2] = new k3d::split_edge(c_point);
     edges[3] = new k3d::split_edge(d_point);
     
     for(unsigned long i = 0; i < 4; ++i)
       edges[i]->face_clockwise = edges[(i+1)%4];
  • Here we set the other vertices that define the edge. As you can see is a loop that makes the face we want to construct as in the image:
Image:Face Plugin Edges Loop.png
     #endif
  • If you compile this code with the face block commented it will run, but you won't see any difference, because the edges are drawn when there is a face. Which is the next step.
The face
     #if 1 //face block
     k3d::face* const new_face = new k3d::face(edges[0], material);
  • The k3d::face is the class for representing faces on the k3d meshes. (kd3::face doc)
    • It needs the first edge of the loops of edges that creates the face.
     polyhedron.faces.push_back(new_face);
  • Finally it inserts it on the mesh.
Final Checks

For debugging purposes we check that what we made is a valid mesh. That's all

     assert_warning(is_valid(polyhedron));  
In case you make a solid object you can also use the assert_warning(is_solid(polyhedron)); check.

The properties

  • Here we are going to see how to add a new property.
  • See the lines:
     m_width(init_owner(*this) + init_name("width") + init_label(_("Width")) + init_description(_("Cube width")) + init_value(5.0) + init_step_increment(0.1) + init_units(typeid(k3d::measurement::distance))),   
     m_height(init_owner(*this) + init_name("height") + init_label(_("Height")) + init_description(_("Cube height")) + init_value(5.0) + init_step_increment(0.1) + init_units(typeid(k3d::measurement::distance)))  
     ...
     m_width.changed_signal().connect(make_reset_mesh_slot());   
     m_height.changed_signal().connect(make_reset_mesh_slot());
     ...
 private:
   k3d_data(double, immutable_name, change_signal, with_undo, local_storage, no_constraint, measurement_property, with_serialization) m_width;
   k3d_data(double, immutable_name, change_signal, with_undo, local_storage, no_constraint, measurement_property, with_serialization) m_height;
  • As you can see those lines tell k3d that we have two properties that are float numbers.
  • If you want to add a new property look on the modules/ directory of the k3d source code for a module that has a property similar to the one you want.
  • For example we add an int property, that would be the number of rows. (taken from the grid module)
     m_rows(init_owner(*this) + init_name("rows") + init_label(_("Rows")) + init_description(_("Row number (Y axis)")) + init_value(1) + init_constraint(constraint::minimum(1L)) + init_step_increment(1) + init_units(typeid(k3d::measurement::scalar))),    
     m_width(init_owner(*this) + init_name("width") + init_label(_("Width")) + init_description(_("Cube width")) + init_value(5.0) + init_step_increment(0.1) + init_units(typeid(k3d::measurement::distance))),   
     m_height(init_owner(*this) + init_name("height") + init_label(_("Height")) + init_description(_("Cube height")) + init_value(5.0) + init_step_increment(0.1) + init_units(typeid(k3d::measurement::distance)))      
     ...
     m_rows.changed_signal().connect(make_reset_mesh_slot());
     m_width.changed_signal().connect(make_reset_mesh_slot());   
     m_height.changed_signal().connect(make_reset_mesh_slot());      
     ...
 private:
   k3d_data(long, immutable_name, change_signal, with_undo, local_storage, with_constraint, measurement_property, with_serialization) m_rows;
   k3d_data(double, immutable_name, change_signal, with_undo, local_storage, no_constraint, measurement_property, with_serialization) m_width;
   k3d_data(double, immutable_name, change_signal, with_undo, local_storage, no_constraint, measurement_property, with_serialization) m_height;
  • That would look like.
Image:Face Tutorial Row Parameter.png
  • So looking up in the k3d source code you can add several properties, as a first approach.
  • Notice that the m_rows, m_width, m_height are variables so you can change that name at will as long as they match in the code.

The names and Ids

You should change the following names in the code so that you make your own plugin with its own meaning on their names.

  • libk3dmymodule
  • my_poly_face_implementation
  • m_width, m_height
  • poly_face_factory
Remember that if you change the name of a variable, class, etc, do it in the whole code.

Now check the get_factory code:

 static k3d::iplugin_factory& get_factory()
 {
   static k3d::plugin_factory<k3d::document_plugin<my_poly_face_implementation>, k3d::interface_list<k3d::imesh_source > > factory(
     k3d::uuid(0x51ecc5ce, 0xf8bf43ed, 0xabba2ed7, 0x3e1ed1d3),
     "MyPlugin",
     "Generates a face",
     "Tutorials",
     k3d::iplugin_factory::EXPERIMENTAL);
   
   return factory;
 }
  • You must change the 0x51ecc5ce, 0xf8bf43ed, 0xabba2ed7, 0x3e1ed1d3 sequence. (this identifies every plugin)
  • It is not recommended to change the "MyPlugin" string right away, because if you do you will have to create a new .svg icon with that name an put it on the k3d dir. (do it when you finish your plugin)
  • You can change "Generates a face" line with a good description of your plugin.
  • You can change "Tutorials" line, this tells in which submenu from the menu Create goes your plugin.

Let's check the end of the file:

 K3D_MODULE_START(libk3dmymodule, k3d::uuid(0xb1aa0867, 0x37404b32, 0xb80add63, 0xfb02fed0), Registry)
        Registry.register_factory(libk3dmymodule::my_poly_face_implementation::get_factory());
 K3D_MODULE_END
  • Again you have to change the 0xb1aa0867, 0x37404b32, 0xb80add63, 0xfb02fed0 sequence. It also must be different from the one we mention above.
Personal tools