diff --git a/demos/api/explore.cpp b/demos/api/explore.cpp
index 18c8fb5509bcfa1c2fb30ada10c43dc3f40f8ceb..bc60643838395f147d3abda343f95d1d74740957 100644
--- a/demos/api/explore.cpp
+++ b/demos/api/explore.cpp
@@ -30,8 +30,8 @@ int main(int argc, char **argv)
 
     // report some statistics
     int numElem = 0;
-    for(unsigned int i = 0; i < elemTags.size(); i++)
-      numElem += elemTags[i].size();
+    for(unsigned int j = 0; j < elemTags.size(); j++)
+      numElem += elemTags[j].size();
     std::string type;
     gmsh::model::getType(dim, tag, type);
     std::cout << nodeTags.size() << " mesh nodes and "
@@ -41,22 +41,22 @@ int main(int argc, char **argv)
     gmsh::model::getPartitions(dim, tag, partitions);
     if(partitions.size()){
       std::cout << " - Partition tag(s):";
-      for(unsigned int i = 0; i < partitions.size(); i++)
-        std::cout << " " << partitions[i];
+      for(unsigned int j = 0; j < partitions.size(); j++)
+        std::cout << " " << partitions[j];
       int parentDim, parentTag;
       gmsh::model::getParent(dim, tag, parentDim, parentTag);
       std::cout << " - parent entity (" << parentDim << "," << parentTag << ")\n";
     }
-    for(unsigned int i = 0; i < elemTypes.size(); i++){
+    for(unsigned int j = 0; j < elemTypes.size(); j++){
       std::string name;
       int d, order, numv, numpv;
       std::vector<double> param;
-      gmsh::model::mesh::getElementProperties(elemTypes[i], name, d, order,
+      gmsh::model::mesh::getElementProperties(elemTypes[j], name, d, order,
                                               numv, param, numpv);
       std::cout << " - Element type: " << name << ", order " << order << "\n";
       std::cout << "   with " << numv << " nodes in param coord: (";
-      for(unsigned int j = 0; j < param.size(); j++)
-        std::cout << param[j] << " ";
+      for(unsigned int k = 0; k < param.size(); k++)
+        std::cout << param[k] << " ";
       std::cout << ")\n";
     }
   }
diff --git a/tutorial/c++/t1.cpp b/tutorial/c++/t1.cpp
index 2fbdc9c22de6ab8561953a8116fea24d797934d5..899ea60b017082ac32c966690fb026c8e6745447 100644
--- a/tutorial/c++/t1.cpp
+++ b/tutorial/c++/t1.cpp
@@ -133,6 +133,7 @@ int main(int argc, char **argv)
   // gmsh::option::setNumber("Mesh.SaveAll", 1);
 
   // To visualize the model we could run the graphical user interface with:
+  //
   // gmsh::fltk::run();
 
   // Note that starting with Gmsh 3.0, models can be built using other geometry
diff --git a/tutorial/c++/t10.cpp b/tutorial/c++/t10.cpp
index 5a0829bda4f9c98d3e44a4fbb40aaa5af9fd0f71..436cecabd5acbb17cb4acec5fe202e48ea91fa8b 100644
--- a/tutorial/c++/t10.cpp
+++ b/tutorial/c++/t10.cpp
@@ -7,8 +7,8 @@
 // -----------------------------------------------------------------------------
 
 // In addition to specifying target mesh sizes at the points of the geometry
-// (see `t1.cpp') or using a background mesh (see `t7.cppq'), you can use
-// general mesh size "Fields".
+// (see `t1.cpp') or using a background mesh (see `t7.cpp'), you can use general
+// mesh size "Fields".
 
 #include <gmsh.h>
 #include <sstream>
@@ -23,7 +23,7 @@ int main(int argc, char **argv)
 
   model::add("t10");
 
-  // Let's create a simple rectangular geometry
+  // Let's create a simple rectangular geometry:
   double lc = .15;
   factory::addPoint(0.0,0.0,0,lc, 1);
   factory::addPoint(1,0.0,0,lc, 2);
@@ -103,7 +103,7 @@ int main(int argc, char **argv)
   // interface by selecting `Define->Fields' in the `Mesh' module.
 
   // Finally, let's use the minimum of all the fields as the background mesh
-  // field
+  // field:
   model::mesh::field::add("Min", 7);
   model::mesh::field::setNumbers(7, "FieldsList", {2, 3, 5, 6});
 
@@ -139,6 +139,9 @@ int main(int argc, char **argv)
 
   model::mesh::generate(2);
   gmsh::write("t10.msh");
+
+  // gmsh::fltk::run();
+
   gmsh::finalize();
   return 0;
 }
diff --git a/tutorial/c++/t11.cpp b/tutorial/c++/t11.cpp
index 3a723f35c9e9fac84b1a7211423376ddaf125d31..ffc0f29690225c6b48295049ee0c9ac87761bc56 100644
--- a/tutorial/c++/t11.cpp
+++ b/tutorial/c++/t11.cpp
@@ -16,9 +16,9 @@ int main(int argc, char **argv)
   gmsh::model::add("t11");
 
   // We have seen in tutorials `t3.cpp' and `t6.cpp' that extruded and
-  // transfinite meshes can be "recombined" into quads/prisms/hexahedra by using
-  // the "Recombine" keyword. Unstructured meshes can be recombined in the same
-  // way. Let's define a simple geometry with an analytical mesh size field:
+  // transfinite meshes can be "recombined" into quads, prisms or
+  // hexahedra. Unstructured meshes can be recombined in the same way. Let's
+  // define a simple geometry with an analytical mesh size field:
 
   int p1 = gmsh::model::geo::addPoint(-1.25, -.5, 0);
   int p2 = gmsh::model::geo::addPoint(1.25, -.5, 0);
@@ -68,12 +68,12 @@ int main(int argc, char **argv)
   // gmsh::option::setNumber("Mesh.Algorithm", 8);
 
   // The default recombination algorithm might leave some triangles in the mesh,
-  // if recombining all the triangles leads to badly shaped quads. In such cases,
-  // to generate full-quad meshes, you can either subdivide the resulting hybrid
-  // mesh (with Mesh.SubdivisionAlgorithm = 1), or use the full-quad recombination
-  // algorithm, which will automatically perform a coarser mesh followed by
-  // recombination, smoothing and subdivision. Uncomment the following line to try
-  // the full-quad algorithm:
+  // if recombining all the triangles leads to badly shaped quads. In such
+  // cases, to generate full-quad meshes, you can either subdivide the resulting
+  // hybrid mesh (with `Mesh.SubdivisionAlgorithm' set to 1), or use the
+  // full-quad recombination algorithm, which will automatically perform a
+  // coarser mesh followed by recombination, smoothing and
+  // subdivision. Uncomment the following line to try the full-quad algorithm:
   //
   // gmsh::option::setNumber("Mesh.RecombinationAlgorithm", 2); // or 3
 
@@ -91,6 +91,8 @@ int main(int argc, char **argv)
   // gmsh::option::setNumber("Mesh.SubdivisionAlgorithm", 1);
   // gmsh::model::mesh::refine();
 
+  // gmsh::fltk::run();
+
   gmsh::finalize();
   return 0;
 }
diff --git a/tutorial/c++/t12.cpp b/tutorial/c++/t12.cpp
index 6a50a3378a7fc5bce758fecde0e483f39b822fcb..79d98f38a1da98307064d368fa2fc8ca0dcb89f5 100644
--- a/tutorial/c++/t12.cpp
+++ b/tutorial/c++/t12.cpp
@@ -6,6 +6,10 @@
 //
 // -----------------------------------------------------------------------------
 
+#include <gmsh.h>
+
+namespace factory = gmsh::model::geo;
+
 // "Compound" meshing constraints allow to generate meshes across surface
 // boundaries, which can be useful e.g. for imported CAD models (e.g. STEP) with
 // undesired small features.
@@ -35,10 +39,6 @@
 // The optional reclassification on the underlying elementary entities in Step
 // 5. is governed by the `Mesh.CompoundClassify' option.
 
-#include <gmsh.h>
-
-namespace factory = gmsh::model::geo;
-
 int main(int argc, char **argv)
 {
   gmsh::initialize();
diff --git a/tutorial/c++/t13.cpp b/tutorial/c++/t13.cpp
index 859d5faa2be5c0086f8e22add992aa7d695ddb7d..eefbddfa8d9f0c77c8a478a4b79bfb6d0590f594 100644
--- a/tutorial/c++/t13.cpp
+++ b/tutorial/c++/t13.cpp
@@ -48,7 +48,7 @@ int main(int argc, char **argv)
   gmsh::model::mesh::classifySurfaces(angle * M_PI / 180.,
                                       includeBoundary,
                                       forceParametrizablePatches,
-                                      curveAngle);
+                                      curveAngle * M_PI / 180.);
 
   // Create a geometry for all the discrete curves and surfaces in the mesh, by
   // computing a parametrization for each one
@@ -75,6 +75,8 @@ int main(int argc, char **argv)
 
   gmsh::model::mesh::generate(3);
 
+  // gmsh::fltk::run();
+
   gmsh::finalize();
   return 0;
 }
diff --git a/tutorial/c++/t14.cpp b/tutorial/c++/t14.cpp
index 2bbad4f70dcc52c060f6dca07c7b0fb8c319f5de..2118e52ee5133851384116c8a7937f15c788f273 100644
--- a/tutorial/c++/t14.cpp
+++ b/tutorial/c++/t14.cpp
@@ -67,7 +67,7 @@ int main(int argc, char **argv)
   gmsh::model::geo::extrude({{2,15}}, 0, 0, h, ext_tags);
 
   // Create physical groups, which are used to define the domain of the
-  // (co);homology computation and the subdomain of the relative (co);homology
+  // (co)homology computation and the subdomain of the relative (co)homology
   // computation.
 
   // Whole domain
diff --git a/tutorial/c++/t15.cpp b/tutorial/c++/t15.cpp
index e753519f92d39b6d0a7e2d7c35d75fef3a26a254..9b40ea9039f4aade3787a4d4bf9c543ffa28abeb 100644
--- a/tutorial/c++/t15.cpp
+++ b/tutorial/c++/t15.cpp
@@ -38,10 +38,6 @@ int main(int argc, char **argv)
   factory::addLine(4, 1, 4);
   factory::addCurveLoop({4, 1, -2, 3}, 1);
   factory::addPlaneSurface({1}, 1);
-  model::addPhysicalGroup(0, {1, 2}, 1);
-  model::addPhysicalGroup(1, {1, 2}, 2);
-  model::addPhysicalGroup(2, {1}, 6);
-  model::setPhysicalName(2, 6, "My surface");
 
   // We change the mesh size to generate a coarser mesh
   lc *=  4;
diff --git a/tutorial/c++/t16.cpp b/tutorial/c++/t16.cpp
index 176108759b309e4799ca07eaa6b17490b49cc633..0c2e50b6017e0ca944da9c4c8aca4b5e355314c8 100644
--- a/tutorial/c++/t16.cpp
+++ b/tutorial/c++/t16.cpp
@@ -45,8 +45,8 @@ int main(int argc, char **argv)
   factory::cut({{3,1}}, {{3,2}}, ov, ovv, 3);
 
   // Boolean operations with OpenCASCADE always create new entities. By default
-  // the extra arguments `removeObject' and `removeTool' in `occ::cut()' are set
-  // to `true', which will delete the original entities.
+  // the extra arguments `removeObject' and `removeTool' in `cut()' are set to
+  // `true', which will delete the original entities.
 
   // We then create the five spheres:
   double x = 0, y = 0.75, z = 0, r = 0.09 ;
@@ -64,6 +64,27 @@ int main(int argc, char **argv)
   // a conformal manner (without creating duplicate interfaces):
   factory::fragment({{3,3}}, holes, ov, ovv);
 
+  // ov contains all the generated entities of the same dimension as the input
+  // entities:
+  gmsh::logger::write("fragment produced volumes:");
+  for(std::size_t i = 0; i < ov.size(); i++)
+    gmsh::logger::write("(" + std::to_string(ov[i].first) + "," +
+                        std::to_string(ov[i].second) + ")");
+
+  // ovv contains the parent-child relationships for all the input entities:
+  gmsh::logger::write("before/after volume relations:");
+  std::vector<std::pair<int, int> > in(1, std::pair<int, int>(3, 3));
+  in.insert(in.end(), holes.begin(), holes.end());
+  for(std::size_t i = 0; i < in.size(); i++) {
+    std::string s = "parent (" + std::to_string(in[i].first) + "," +
+      std::to_string(in[i].second) + ") -> child";
+    for(std::size_t j = 0; j < ovv[i].size(); j++) {
+      s += " (" + std::to_string(ovv[i][j].first) + "," +
+        std::to_string(ovv[i][j].second) + ")";
+    }
+    gmsh::logger::write(s);
+  }
+
   factory::synchronize();
 
   // When the boolean operation leads to simple modifications of entities, and
@@ -115,11 +136,15 @@ int main(int argc, char **argv)
   // Additional examples created with the OpenCASCADE geometry kernel are
   // available in `t18.cpp', `t19.cpp' as well as in the `demos/api' directory.
 
+  // Inspect the log:
   std::vector<std::string> log;
   gmsh::logger::get(log);
   std::cout << "Logger has recorded " << log.size() << " lines" << std::endl;
   gmsh::logger::stop();
 
+  // Show the GUI:
+  // gmsh::fltk::run();
+
   gmsh::finalize();
   return 0;
 }
diff --git a/tutorial/c++/t18.cpp b/tutorial/c++/t18.cpp
index 5792725675224c0e136e5bc303cec7357c36fbce..cedff457ae12856fe9f53062cff29ebd1e9d41a1 100644
--- a/tutorial/c++/t18.cpp
+++ b/tutorial/c++/t18.cpp
@@ -78,7 +78,6 @@ int main(int argc, char **argv)
   std::vector<std::pair<int, int> > in;
   gmsh::model::getEntitiesInBoundingBox(2-eps,-eps,-eps, 2+1+eps,1+eps,1+eps,
                                         in, 3);
-  std::vector<int> boundary_tags, complement_tags;
   for(auto it = in.begin(); it != in.end(); ++it) {
     auto it2 = std::find(out.begin(), out.end(), *it);
     if(it2 != out.end()) out.erase(it2);
diff --git a/tutorial/c++/t2.cpp b/tutorial/c++/t2.cpp
index 07e9256c6fda73af7fb211891664def103489b49..ab2e281f8131d02d76e69aa2d27c31ded65aa1b9 100644
--- a/tutorial/c++/t2.cpp
+++ b/tutorial/c++/t2.cpp
@@ -15,8 +15,8 @@ namespace factory = gmsh::model::geo;
 
 int main(int argc, char **argv)
 {
-  // If argc/argv are passed to gmsh::initialize(), Gmsh will parse the
-  // commandline in the same way as the standalone Gmsh app:
+  // If argc/argv are passed to gmsh::initialize(), Gmsh will parse the command
+  // line in the same way as the standalone Gmsh app:
   gmsh::initialize(argc, argv);
 
   gmsh::option::setNumber("General.Terminal", 1);
@@ -70,7 +70,7 @@ int main(int argc, char **argv)
   factory::addCurveLoop({5,-8,-7,3}, 10);
   factory::addPlaneSurface({10}, 11);
 
-  // In the same way, we can translate copies of the two surfaces 6 and 11 to
+  // In the same way, we can translate copies of the two surfaces 1 and 11 to
   // the right with the following command:
   factory::copy({{2, 1}, {2, 11}}, ov);
   factory::translate(ov, 0.12, 0, 0);
diff --git a/tutorial/c++/t3.cpp b/tutorial/c++/t3.cpp
index 97aace0c7d6fff38707f5a2392cd6cf374ecb75f..c0e5dc5fd65af5183b172964c4b4187be0215e16 100644
--- a/tutorial/c++/t3.cpp
+++ b/tutorial/c++/t3.cpp
@@ -31,10 +31,9 @@ int main(int argc, char **argv)
   factory::addLine(4, 1, 4);
   factory::addCurveLoop({4, 1, -2, 3}, 1);
   factory::addPlaneSurface({1}, 1);
-  model::addPhysicalGroup(0, {1, 2}, 1);
-  model::addPhysicalGroup(1, {1, 2}, 2);
-  model::addPhysicalGroup(2, {1}, 6);
-  model::setPhysicalName(2, 6, "My surface");
+  model::addPhysicalGroup(1, {1, 2, 4}, 5);
+  int ps = model::addPhysicalGroup(2, {1});
+  model::setPhysicalName(2, ps, "My surface");
 
   // As in `t2.cpp', we plan to perform an extrusion along the z axis.  But
   // here, instead of only extruding the geometry, we also want to extrude the
@@ -59,7 +58,7 @@ int main(int argc, char **argv)
   // supported. To do a full turn, you will thus need to apply at least 3
   // rotations. The OpenCASCADE geometry kernel does not have this limitation.
 
-  // A translation (-2*h, 0, 0) and a rotation ((0,0.15,0.25}, (1,0,0), Pi/2)
+  // A translation (-2*h, 0, 0) and a rotation ((0,0.15,0.25), (1,0,0), Pi/2)
   // can also be combined to form a "twist".  The last (optional) argument for
   // the extrude() and twist() functions specifies whether the extruded mesh
   // should be recombined or not.
diff --git a/tutorial/c++/t6.cpp b/tutorial/c++/t6.cpp
index 3e8d27392bf85c2c07cbb1445453ea4494022628..bca2582659c354f0023cec678dfe36aba1817660 100644
--- a/tutorial/c++/t6.cpp
+++ b/tutorial/c++/t6.cpp
@@ -48,7 +48,7 @@ int main(int argc, char **argv)
   // location of the nodes on the curve. For example, the following command
   // forces 20 uniformly placed nodes on curve 2 (including the nodes on the two
   // end points):
-  factory::mesh::setTransfiniteCurve(2, 20, "Bump", 0.05);
+  factory::mesh::setTransfiniteCurve(2, 20);
 
   // Let's put 20 points total on combination of curves `l1', `l2' and `l3'
   // (beware that the points `p1' and `p2' are shared by the curves, so we do
diff --git a/tutorial/c++/t7.cpp b/tutorial/c++/t7.cpp
index ff5abf7b4d55be1803d39a26ab7e4fc5059d7b7b..244504fc5d2ec11b5cee85ca4482c02bc8cf24a8 100644
--- a/tutorial/c++/t7.cpp
+++ b/tutorial/c++/t7.cpp
@@ -52,6 +52,9 @@ int main(int argc, char **argv)
   gmsh::model::mesh::generate(2);
   gmsh::write("t7.msh");
 
+  // Show the mesh
+  // gmsh::fltk::run();
+
   gmsh::finalize();
   return 0;
 }
diff --git a/tutorial/c++/t8.cpp b/tutorial/c++/t8.cpp
index 2d7fe5efe28304a575a8196ff3f049e0e503a049..ccf6c599967cb96f4048c081e92c2d3a3acbd139 100644
--- a/tutorial/c++/t8.cpp
+++ b/tutorial/c++/t8.cpp
@@ -36,8 +36,8 @@ int main(int argc, char **argv)
   factory::addPlaneSurface({1}, 1);
   factory::synchronize();
 
+  // We merge some post-processing views to work on
   try {
-    // We merge some post-processing views to work on
     gmsh::merge("../view1.pos");
     gmsh::merge("../view1.pos");
     gmsh::merge("../view4.pos"); // contains 2 views inside
@@ -65,7 +65,7 @@ int main(int argc, char **argv)
   int black[3] = {0, 0, 0};
   option::setColor("General.Background", white[0], white[1], white[2]);
 
-  // We could make our own shorter versions of repetitive methods
+  // We can make our own shorter versions of repetitive methods
   auto set_color = [] (std::string name, int c[3]) -> void
                    { option::setColor(name, c[0], c[1], c[2]); };
   set_color("General.Foreground", black);
diff --git a/tutorial/c++/t9.cpp b/tutorial/c++/t9.cpp
index 52514a17344a3cfb685d6a41a0b774a2e8f77be5..e1957e95aa909251843f54c1517737141ce30160 100644
--- a/tutorial/c++/t9.cpp
+++ b/tutorial/c++/t9.cpp
@@ -75,6 +75,9 @@ int main(int argc, char **argv)
   gmsh::option::setNumber("View[1].IntervalsType", 2);
   gmsh::option::setNumber("View[2].IntervalsType", 2);
 
+  // show the GUI at the end
+  // gmsh::fltk::run();
+
   gmsh::finalize();
   return 0;
 }
diff --git a/tutorial/c++/x1.cpp b/tutorial/c++/x1.cpp
index b6a5a0f790f4fec4e20666e331a6f1959ef509cb..aa51dccd3e7dceeb3fc15852f1fbaf34da475c67 100644
--- a/tutorial/c++/x1.cpp
+++ b/tutorial/c++/x1.cpp
@@ -10,7 +10,7 @@
 // additional features are introduced gradually in the extended tutorials,
 // starting with `x1.cpp'.
 
-// In this first extended tutorial, we start by using the API to explore basic
+// In this first extended tutorial, we start by using the API to access basic
 // geometrical and mesh data.
 
 #include <iostream>
@@ -34,76 +34,100 @@ int main(int argc, char **argv)
   // identified by their dimension and by a `tag': a strictly positive
   // identification number. Model entities can be either CAD entities (from the
   // built-in `geo' kernel or from the OpenCASCADE `occ' kernel) or `discrete'
-  // entities (defined by a mesh).
+  // entities (defined by a mesh). `Physical groups' are collections of model
+  // entities and are also identified by their dimension and by a tag.
 
-  // Get all the entities in the model, as a vector of (dimension, tag) pairs:
+  // Get all the elementary entities in the model, as a vector of (dimension,
+  // tag) pairs:
   std::vector<std::pair<int, int> > entities;
   gmsh::model::getEntities(entities);
 
-  for(unsigned int i = 0; i < entities.size(); i++){
+  for(std::size_t i = 0; i < entities.size(); i++){
     // Mesh data is made of `elements' (points, lines, triangles, ...), defined
     // by an ordered list of their `nodes'. Elements and nodes are identified by
     // `tags' as well (strictly positive identification numbers), and are stored
-    // ("classified") in the model entity they discretize.
+    // ("classified") in the model entity they discretize. Tags for elements and
+    // nodes are globally unique (and not only per dimension, like entities).
 
     // A model entity of dimension 0 (a geometrical point) will contain a mesh
     // element of type point, as well as a mesh node. A model curve will contain
     // line elements as well as its interior nodes, while its boundary nodes
     // will be stored in the bounding model points. A model surface will contain
     // triangular and/or quadrangular elements and all the nodes not classified
-    // on its boundary or on its embedded entities (curves and points). A model
-    // volume will contain tetrahedra, hexahedra, etc. and all the nodes not
-    // classified on its boundary or on its embedded entities (surfaces, curves
-    // and points).
+    // on its boundary or on its embedded entities. A model volume will contain
+    // tetrahedra, hexahedra, etc. and all the nodes not classified on its
+    // boundary or on its embedded entities.
 
-    // Get the mesh nodes for each entity:
+    // Dimension and tag of the entity:
+    int dim = entities[i].first, tag = entities[i].second;
+
+    // Get the mesh nodes for the entity (dim, tag):
     std::vector<std::size_t> nodeTags;
     std::vector<double> nodeCoords, nodeParams;
-    int dim = entities[i].first, tag = entities[i].second;
     gmsh::model::mesh::getNodes(nodeTags, nodeCoords, nodeParams, dim, tag);
 
-    // Get the mesh elements for each entity:
+    // Get the mesh elements for the entity (dim, tag):
     std::vector<int> elemTypes;
     std::vector<std::vector<std::size_t> > elemTags, elemNodeTags;
     gmsh::model::mesh::getElements(elemTypes, elemTags, elemNodeTags, dim, tag);
 
-    // Print a summary of information available on the entity and its mesh:
-    int numElem = 0;
-    for(unsigned int i = 0; i < elemTags.size(); i++)
-      numElem += elemTags[i].size();
+    // Let's print a summary of the information available on the entity and its
+    // mesh.
+
+    // * Type of the entity:
     std::string type;
     gmsh::model::getType(dim, tag, type);
-    std::cout << nodeTags.size() << " mesh nodes and "
-              << numElem << " mesh elements on entity ("
-              << dim << "," << tag << ") of type " << type << "\n";
+    std::cout << "Entity (" << dim << "," << tag << ") of type " << type << "\n";
+
+    // * Number of mesh nodes and elements:
+    int numElem = 0;
+    for(std::size_t j = 0; j < elemTags.size(); j++) numElem += elemTags[j].size();
+    std::cout << " - Mesh has " << nodeTags.size() << " nodes and " << numElem
+              << " elements\n";
+
+    // * Entities on its boundary:
+    std::vector<std::pair<int, int> > boundary;
+    gmsh::model::getBoundary({{dim,tag}}, boundary);
+    if(boundary.size()) {
+      std::cout << " - Boundary entities: ";
+      for(std::size_t j = 0; j < boundary.size(); j++)
+        std::cout << " (" << boundary[j].first << "," << boundary[j].second << ")";
+      std::cout << "\n";
+    }
+
+    // * Does the entity belong to physical groups?
     std::vector<int> physicalTags;
     gmsh::model::getPhysicalGroupsForEntity(dim, tag, physicalTags);
     if(physicalTags.size()) {
-      std::cout << " - Physical group tag(s):";
-      for(unsigned int i = 0; i < physicalTags.size(); i++)
-        std::cout << " " << physicalTags[i];
+      std::cout << " - Physical group tags:";
+      for(std::size_t j = 0; j < physicalTags.size(); j++)
+        std::cout << " " << physicalTags[j];
       std::cout << "\n";
     }
+
+    // * Is the entity a partition entity? If so, what is its parent entity?
     std::vector<int> partitions;
     gmsh::model::getPartitions(dim, tag, partitions);
     if(partitions.size()){
-      std::cout << " - Partition tag(s):";
-      for(unsigned int i = 0; i < partitions.size(); i++)
-        std::cout << " " << partitions[i];
+      std::cout << " - Partition tags:";
+      for(std::size_t j = 0; j < partitions.size(); j++)
+        std::cout << " " << partitions[j];
       int parentDim, parentTag;
       gmsh::model::getParent(dim, tag, parentDim, parentTag);
       std::cout << " - parent entity (" << parentDim << "," << parentTag << ")\n";
     }
-    for(unsigned int i = 0; i < elemTypes.size(); i++){
+
+    // * List all types of elements making up the mesh of the entity:
+    for(std::size_t j = 0; j < elemTypes.size(); j++){
       std::string name;
       int d, order, numv, numpv;
       std::vector<double> param;
-      gmsh::model::mesh::getElementProperties(elemTypes[i], name, d, order,
+      gmsh::model::mesh::getElementProperties(elemTypes[j], name, d, order,
                                               numv, param, numpv);
       std::cout << " - Element type: " << name << ", order " << order << "\n";
       std::cout << "   with " << numv << " nodes in param coord: (";
-      for(unsigned int j = 0; j < param.size(); j++)
-        std::cout << param[j] << " ";
+      for(std::size_t k = 0; k < param.size(); k++)
+        std::cout << param[k] << " ";
       std::cout << ")\n";
     }
   }
diff --git a/tutorial/c/t1.c b/tutorial/c/t1.c
index 8435c419fffc58189e07ec93ba7a6e12c2602c01..62e7faa823dba2e224ef9e30c053ecc33e6ba101 100644
--- a/tutorial/c/t1.c
+++ b/tutorial/c/t1.c
@@ -93,7 +93,7 @@ int main(int argc, char **argv)
      representing the external contour, since there are no holes): */
   gmshModelGeoAddPlaneSurface(s1, sizeof(s1)/sizeof(s1[0]), 1, &ierr);
 
-  /* At this level, Gmsh knows everything to display the rectangular surface 6
+  /* At this level, Gmsh knows everything to display the rectangular surface 1
      and to mesh it. An optional step is needed if we want to group elementary
      geometrical entities into more meaningful groups, e.g. to define some
      mathematical ("domain", "boundary"), functional ("left wing", "fuselage")
@@ -137,7 +137,8 @@ int main(int argc, char **argv)
      gmshOptionSetNumber("Mesh.SaveAll", 1);
   */
 
-  /* We could run the graphical user interface with:
+  /* To visualize the model we could run the graphical user interface with:
+
      gmsh::fltk::run();
   */
 
diff --git a/tutorial/python/README.txt b/tutorial/python/README.txt
index 6dae7c1e81c89ccf79c084a43640919f17bda4a9..d4f2fdeff34b96b3d6aabd683a564c8545f43c9e 100644
--- a/tutorial/python/README.txt
+++ b/tutorial/python/README.txt
@@ -1,4 +1,4 @@
-This directory contains Python versions of the tutorials, written using the Gmsh
+This directory contains the Gmsh Python tutorials, written using the Gmsh Python
 API.
 
 To run the Python tutorials, you need the Gmsh dynamic library and the Python
diff --git a/tutorial/python/t1.py b/tutorial/python/t1.py
index 619478b58a6e2fd91ca08e83c92d87cbabacf009..1d32448156cd6d5ad50cda0153a419514798ffc6 100644
--- a/tutorial/python/t1.py
+++ b/tutorial/python/t1.py
@@ -1,72 +1,118 @@
-# This file reimplements gmsh/tutorial/t1.geo in Python.
-
-# For all the elementary explanations about the general philosphy of entities in
-# Gmsh, see the comments in the .geo file. Comments here focus on the specifics
-# of the Python API.
+# ------------------------------------------------------------------------------
+#
+#  Gmsh Python tutorial 1
+#
+#  Elementary entities (points, curves, surfaces), physical groups
+#  (points, curves, surfaces)
+#
+# ------------------------------------------------------------------------------
 
-# The API is entirely defined in the gmsh module
+# The Python API is entirely defined in the `gmsh.py' module (which contains the
+# full documentation of all the functions in the API):
 import gmsh
 
-# Before using any functions in the Python API, Gmsh must be initialized.
+# Before using any functions in the Python API, Gmsh must be initialized:
 gmsh.initialize()
 
 # By default Gmsh will not print out any messages: in order to output messages
-# on the terminal, just set the standard Gmsh option "General.Terminal" (same
-# format and meaning as in .geo files):
+# on the terminal, just set the "General.Terminal" option to 1:
 gmsh.option.setNumber("General.Terminal", 1)
 
 # Next we add a new model named "t1" (if gmsh.model.add() is not called a new
 # unnamed model will be created on the fly, if necessary):
 gmsh.model.add("t1")
 
-# The Python API provides direct access to the internal CAD kernels. The
-# built-in CAD kernel was used in t1.geo: the corresponding API functions have
-# the "gmsh.model.geo" prefix. To create geometrical points with the built-in
-# CAD kernel, one thus uses gmsh.model.geo.addPoint():
-#
+# The Python API provides direct access to each supported geometry kernel. The
+# built-in kernel is used in this first tutorial: the corresponding API
+# functions have the `gmsh.model.geo' prefix.
+
+# The first type of `elementary entity' in Gmsh is a `Point'. To create a point
+# with the built-in geometry kernel, the Python API function is
+# gmsh.model.geo.addPoint():
 # - the first 3 arguments are the point coordinates (x, y, z)
-#
-# - the next (optional) argument is the target mesh size close to the point
-#
-# - the last (optional) argument is the point tag
+# - the next (optional) argument is the target mesh size (the "characteristic
+#   length") close to the point
+# - the last (optional) argument is the point tag (a stricly positive integer
+#   that uniquely identifies the point)
 lc = 1e-2
 gmsh.model.geo.addPoint(0, 0, 0, lc, 1)
+
+# The distribution of the mesh element sizes will be obtained by interpolation
+# of these characteristic lengths throughout the geometry. Another method to
+# specify characteristic lengths is to use general mesh size Fields (see
+# `t10.py'). A particular case is the use of a background mesh (see `t7.py').
+#
+# If no target mesh size of provided, a default uniform coarse size will be used
+# for the model, based on the overall model size.
+#
+# We can then define some additional points. All points should have different
+# tags:
 gmsh.model.geo.addPoint(.1, 0,  0, lc, 2)
 gmsh.model.geo.addPoint(.1, .3, 0, lc, 3)
-gmsh.model.geo.addPoint(0, .3, 0, lc, 4)
 
-# The API to create lines with the built-in kernel follows the same
-# conventions: the first 2 arguments are point tags, the last (optional one)
+# If the tag is not provided explicitly, a new tag is automatically created, and
+# returned by the function:
+p4 = gmsh.model.geo.addPoint(0, .3, 0, lc)
+
+# Curves are Gmsh's second type of elementery entities, and, amongst curves,
+# straight lines are the simplest. The API to create straight line segments with
+# the built-in kernel follows the same conventions: the first 2 arguments are
+# point tags (the start and end points of the line), and the last (optional one)
 # is the line tag.
+#
+# In the commands below, for example, the line 1 starts at point 1 and ends at
+# point 2.
+#
+# Note that curve tags are separate from point tags - hence we can reuse tag `1'
+# for our first curve. And as a general rule, elementary entity tags in Gmsh
+# have to be unique per geometrical dimension.
 gmsh.model.geo.addLine(1, 2, 1)
 gmsh.model.geo.addLine(3, 2, 2)
-gmsh.model.geo.addLine(3, 4, 3)
-gmsh.model.geo.addLine(4, 1, 4)
-
-# The philosophy to construct curve loops and surfaces is similar: the first
-# argument is now a vector of integers.
+gmsh.model.geo.addLine(3, p4, 3)
+gmsh.model.geo.addLine(4, 1, p4)
+
+# The third elementary entity is the surface. In order to define a simple
+# rectangular surface from the four curves defined above, a curve loop has first
+# to be defined. A curve loop is defined by an ordered list of connected curves,
+# a sign being associated with each curve (depending on the orientation of the
+# curve to form a loop). The API function to create curve loops takes a list
+# of integers as first argument, and the curve loop tag (which must ne unique
+# amongst curve loops) as the second (optional) argument:
 gmsh.model.geo.addCurveLoop([4, 1, -2, 3], 1)
-gmsh.model.geo.addPlaneSurface([1], 1)
 
-# Physical groups are defined by providing the dimension of the group (0 for
-# physical points, 1 for physical curves, 2 for physical surfaces and 3 for
-# phsyical volumes) followed by a vector of entity tags. The last (optional)
-# argument is the tag of the new group to create.
-gmsh.model.addPhysicalGroup(0, [1, 2], 1)
-gmsh.model.addPhysicalGroup(1, [1, 2], 2)
-gmsh.model.addPhysicalGroup(2, [1], 6)
+# We can then define the surface as a list of curve loops (only one here,
+# representing the external contour, since there are no holes--see `t4.py' for
+# an example of a surface with a hole):
+gmsh.model.geo.addPlaneSurface([1], 1)
 
-# Physical names are also defined by providing the dimension and tag of the
-# entity.
-gmsh.model.setPhysicalName(2, 6, "My surface")
+# At this level, Gmsh knows everything to display the rectangular surface 1 and
+# to mesh it. An optional step is needed if we want to group elementary
+# geometrical entities into more meaningful groups, e.g. to define some
+# mathematical ("domain", "boundary"), functional ("left wing", "fuselage") or
+# material ("steel", "carbon") properties.
+#
+# Such groups are called "Physical Groups" in Gmsh. By default, if physical
+# groups are defined, Gmsh will export in output files only mesh elements that
+# belong to at least one physical group. (To force Gmsh to save all elements,
+# whether they belong to physical groups or not, set the `Mesh.SaveAll' option
+# to 1.) Physical groups are also identified by tags, i.e. stricly positive
+# integers, that should be unique per dimension (0D, 1D, 2D or 3D). Physical
+# groups can also be given names.
+#
+# Here we define a physical curve that groups the left, bottom and right curves
+# in a single group (with prescribed tag 5); and a physical surface with name
+# "My surface" (with an automatic tag) containing the geometrical surface 1:
+gmsh.model.addPhysicalGroup(1, [1, 2, 4], 5)
+ps = gmsh.model.addPhysicalGroup(2, [1])
+gmsh.model.setPhysicalName(2, ps, "My surface")
 
 # Before it can be meshed, the internal CAD representation must be synchronized
 # with the Gmsh model, which will create the relevant Gmsh data structures. This
-# is achieved by the gmsh.model.geo.synchronize() API call for the built-in CAD
-# kernel. Synchronizations can be called at any time, but they involve a non
-# trivial amount of processing; so while you could synchronize the internal CAD
-# data after every CAD command, it is usually better to minimize the number of
-# synchronization points.
+# is achieved by the gmsh.model.geo.synchronize() API call for the built-in
+# geometry kernel. Synchronizations can be called at any time, but they involve
+# a non trivial amount of processing; so while you could synchronize the
+# internal CAD data after every CAD command, it is usually better to minimize
+# the number of synchronization points.
 gmsh.model.geo.synchronize()
 
 # We can then generate a 2D mesh...
@@ -81,8 +127,30 @@ gmsh.write("t1.msh")
 #
 # gmsh.option.setNumber("Mesh.SaveAll", 1)
 
-# We could run the graphical user interface with
+# To visualize the model we could run the graphical user interface with:
+#
 # gmsh.fltk.run()
 
-# This should be called at the end:
+# Note that starting with Gmsh 3.0, models can be built using other geometry
+# kernels than the default "built-in" kernel. To use the OpenCASCADE geometry
+# kernel instead of the built-in kernel, you should use the functions with the
+# `gmsh.model.occ' prefix.
+#
+# Different geometry kernels have different features. With OpenCASCADE, instead
+# of defining the surface by successively defining 4 points, 4 curves and 1
+# curve loop, one can define the rectangular surface directly with
+#
+# gmsh.model.occ.addRectangle(.2, 0, 0, .1, .3)
+#
+# Afther synchronization with the Gmsh model with
+#
+# gmsh.model.occ.synchronize()
+#
+# the underlying curves and points could be accessed with
+# gmsh.model.getBoundary().
+#
+# See e.g. `t16.py', `t18.py' or `t19.py' for complete examples based on
+# OpenCASCADE, and `demos/api' for more.
+
+# This should be called when you are done using the Gmsh Python API:
 gmsh.finalize()
diff --git a/tutorial/python/t10.py b/tutorial/python/t10.py
index a3b3623307620e0cefcd61640b3dda87d4fcca81..058be0c250b7a881fb33c50ff0783b7a0c33ffe4 100644
--- a/tutorial/python/t10.py
+++ b/tutorial/python/t10.py
@@ -1,4 +1,14 @@
-# This file reimplements gmsh/tutorial/t10.geo in Python.
+# ------------------------------------------------------------------------------
+#
+#  Gmsh Python tutorial 10
+#
+#  General mesh size fields
+#
+# ------------------------------------------------------------------------------
+
+# In addition to specifying target mesh sizes at the points of the geometry (see
+# `t1.py') or using a background mesh (see `t7.py'), you can use general mesh
+# size "Fields".
 
 import gmsh
 import math
@@ -11,6 +21,7 @@ gmsh.option.setNumber("General.Terminal", 1)
 
 model.add("t10")
 
+# Let's create a simple rectangular geometry:
 lc = .15
 factory.addPoint(0.0,0.0,0, lc, 1)
 factory.addPoint(1,0.0,0, lc, 2)
@@ -26,11 +37,29 @@ factory.addLine(4,1, 4);
 factory.addCurveLoop([1,2,3,4], 5)
 factory.addPlaneSurface([5], 6)
 
+factory.synchronize()
+
+# Say we would like to obtain mesh elements with size lc/30 near curve 2 and
+# point 5, and size lc elsewhere. To achieve this, we can use two fields:
+# "Distance", and "Threshold". We first define a Distance field (`Field[1]') on
+# points 5 and on curve 2. This field returns the distance to point 5 and to
+# (100 equidistant points on) curve 2.
 model.mesh.field.add("Distance", 1)
 model.mesh.field.setNumbers(1, "NodesList", [5])
 model.mesh.field.setNumber(1, "NNodesByEdge", 100)
 model.mesh.field.setNumbers(1, "EdgesList", [2])
 
+# We then define a `Threshold' field, which uses the return value of the
+# `Distance' field 1 in order to define a simple change in element size
+# depending on the computed distances
+#
+# LcMax -                         /------------------
+#                               /
+#                             /
+#                           /
+# LcMin -o----------------/
+#        |                |       |
+#      Point           DistMin DistMax
 model.mesh.field.add("Threshold", 2);
 model.mesh.field.setNumber(2, "IField", 1);
 model.mesh.field.setNumber(2, "LcMin", lc / 30)
@@ -38,15 +67,24 @@ model.mesh.field.setNumber(2, "LcMax", lc)
 model.mesh.field.setNumber(2, "DistMin", 0.15)
 model.mesh.field.setNumber(2, "DistMax", 0.5)
 
+# Say we want to modulate the mesh element sizes using a mathematical function
+# of the spatial coordinates. We can do this with the MathEval field:
 model.mesh.field.add("MathEval", 3)
 model.mesh.field.setString(3, "F", "Cos(4*3.14*x) * Sin(4*3.14*y) / 10 + 0.101")
 
+# We could also combine MathEval with values coming from other fields. For
+# example, let's define a `Distance' field around point 1
 model.mesh.field.add("Distance", 4)
 model.mesh.field.setNumbers(4, "NodesList", [1])
 
+# We can then create a `MathEval' field with a function that depends on the
+# return value of the `Distance' field 4, i.e., depending on the distance to
+# point 1 (here using a cubic law, with minimum element size = lc / 100)
 model.mesh.field.add("MathEval", 5);
 model.mesh.field.setString(5, "F", "F4^3 + " + str(lc / 100))
 
+# We could also use a `Box' field to impose a step change in element sizes
+# inside a box
 model.mesh.field.add("Box", 6)
 model.mesh.field.setNumber(6, "VIn", lc / 15)
 model.mesh.field.setNumber(6, "VOut", lc)
@@ -55,12 +93,46 @@ model.mesh.field.setNumber(6, "XMax", 0.6)
 model.mesh.field.setNumber(6, "YMin", 0.3)
 model.mesh.field.setNumber(6, "YMax", 0.6)
 
+# Many other types of fields are available: see the reference manual for a
+# complete list. You can also create fields directly in the graphical user
+# interface by selecting `Define->Fields' in the `Mesh' module.
+
+# Finally, let's use the minimum of all the fields as the background mesh field:
 model.mesh.field.add("Min", 7)
 model.mesh.field.setNumbers(7, "FieldsList", [2, 3, 5, 6])
 
 model.mesh.field.setAsBackgroundMesh(7)
 
-factory.synchronize()
+# To determine the size of mesh elements, Gmsh locally computes the minimum of
+#
+# 1) the size of the model bounding box;
+# 2) if `Mesh.CharacteristicLengthFromPoints' is set, the mesh size specified
+#    at geometrical points;
+# 3) if `Mesh.CharacteristicLengthFromCurvature' is set, the mesh size based
+#    on the curvature and `Mesh.MinimumCirclePoints';
+# 4) the background mesh field;
+# 5) any per-entity mesh size constraint.
+#
+# This value is then constrained in the interval
+# [`Mesh.CharacteristicLengthMin', `MeshCharacteristicLengthMax'] and
+# multiplied by `Mesh.CharacteristicLengthFactor'.  In addition, boundary
+# mesh sizes (on curves or surfaces) are interpolated inside the enclosed
+# entity (surface or volume, respectively) if the option
+# `Mesh.CharacteristicLengthExtendFromBoundary' is set (which is the case by
+# default).
+#
+# When the element size is fully specified by a background mesh (as it is in
+# this example), it is thus often desirable to set
+
+gmsh.option.setNumber("Mesh.CharacteristicLengthExtendFromBoundary", 0)
+gmsh.option.setNumber("Mesh.CharacteristicLengthFromPoints", 0)
+gmsh.option.setNumber("Mesh.CharacteristicLengthFromCurvature", 0)
+
+# This will prevent over-refinement due to small mesh sizes on the boundary.
+
 model.mesh.generate(2)
 gmsh.write("t10.msh")
+
+# gmsh.fltk.run()
+
 gmsh.finalize()
diff --git a/tutorial/python/t11.py b/tutorial/python/t11.py
index cfdf6ad584cc33028e2ae959ef1fe6d9dbae4f22..f179e4d968a6a5845969f6725fb1fa8f0432f153 100644
--- a/tutorial/python/t11.py
+++ b/tutorial/python/t11.py
@@ -1,6 +1,10 @@
-# This file reimplements gmsh/tutorial/t11.geo in Python.
+# ------------------------------------------------------------------------------
 #
-# Unstructured quadrangular meshes
+#  Gmsh Python tutorial 11
+#
+#  Unstructured quadrangular meshes
+#
+# ------------------------------------------------------------------------------
 
 import gmsh
 
@@ -10,6 +14,13 @@ factory = model.geo
 gmsh.initialize()
 gmsh.option.setNumber("General.Terminal", 1)
 
+gmsh.model.add("t11")
+
+# We have seen in tutorials `t3.py' and `t6.py' that extruded and transfinite
+# meshes can be "recombined" into quads, prisms or hexahedra. Unstructured
+# meshes can be recombined in the same way. Let's define a simple geometry with
+# an analytical mesh size field:
+
 p1 = factory.addPoint(-1.25, -.5, 0)
 p2 = factory.addPoint(1.25, -.5, 0)
 p3 = factory.addPoint(1.25, 1.25, 0)
@@ -25,44 +36,62 @@ pl = factory.addPlaneSurface([cl])
 
 factory.synchronize()
 
-# field options
 field = model.mesh.field
-
-# add an analytical size field with tag 1
 field.add("MathEval", 1)
 field.setString(1, "F", "0.01*(1.0+30.*(y-x*x)*(y-x*x) + (1-x)*(1-x))")
 field.setAsBackgroundMesh(1)
 
-# to generate quadrangles instead of triangles for the plane surface, add the
-# recombine constraint before meshing
+# To generate quadrangles instead of triangles, we can simply add
 model.mesh.setRecombine(2, pl)
-model.mesh.generate(2)
 
-# we can handle several surfaces with a loop
-# for s in surfaces:
-#     model.mesh.setRecombine(2, s)
-# model.mesh.generate(2)
+# If we'd had several surfaces, we could have used the global option
+# "Mesh.RecombineAll":
+#
+# gmsh.option.setNumber("Mesh.RecombineAll", 1)
 
-# Or, you could force recombination after meshing for the entire mesh:
-# model.mesh.generate(2)
-# model.mesh.recombine()
+# The default recombination algorithm is called "Blossom": it uses a minimum
+# cost perfect matching algorithm to generate fully quadrilateral meshes from
+# triangulations. More details about the algorithm can be found in the
+# following paper: J.-F. Remacle, J. Lambrechts, B. Seny, E. Marchandise,
+# A. Johnen and C. Geuzaine, "Blossom-Quad: a non-uniform quadrilateral mesh
+# generator using a minimum cost perfect matching algorithm", International
+# Journal for Numerical Methods in Engineering 89, pp. 1102-1119, 2012.
+
+# For even better 2D (planar) quadrilateral meshes, you can try the
+# experimental "Frontal-Delaunay for quads" meshing algorithm, which is a
+# triangulation algorithm that enables to create right triangles almost
+# everywhere: J.-F. Remacle, F. Henrotte, T. Carrier-Baudouin, E. Bechet,
+# E. Marchandise, C. Geuzaine and T. Mouton. A frontal Delaunay quad mesh
+# generator using the L^inf norm. International Journal for Numerical Methods
+# in Engineering, 94, pp. 494-512, 2013. Uncomment the following line to try
+# the Frontal-Delaunay algorithms for quads:
+#
+# gmsh.option.setNumber("Mesh.Algorithm", 8)
 
-# Or, you could set the option "Mesh.RecombineAll"
-# gmsh.option.setNumber("Mesh.RecombineAll", 1)
-# model.mesh.generate(2)
+# The default recombination algorithm might leave some triangles in the mesh, if
+# recombining all the triangles leads to badly shaped quads. In such cases, to
+# generate full-quad meshes, you can either subdivide the resulting hybrid mesh
+# (with `Mesh.SubdivisionAlgorithm' set to 1), or use the full-quad
+# recombination algorithm, which will automatically perform a coarser mesh
+# followed by recombination, smoothing and subdivision. Uncomment the following
+# line to try the full-quad algorithm:
+#
+# gmsh.option.setNumber("Mesh.RecombinationAlgorithm", 2) # or 3
 
-# You can set other meshing options depending on your quadrilateral requirements
+# You can also set the subdivision step alone, with
+#
+# gmsh.option.setNumber("Mesh.SubdivisionAlgorithm", 1)
 
-# Better 2D planar quadrilateral meshes with the Frontal-Delaunay for quads
-# algorithm
-# gmsh.option.setNumber("Mesh.Algorithm", 8)
-# model.mesh.generate(2)
+model.mesh.generate(2)
 
-# Force a full-quad mesh with either option
+# Note that you could also apply the recombination algorithm and/or the
+# subdivision step explicitly after meshing, as follows:
+#
+# gmsh.model.mesh.generate(2)
+# gmsh.model.mesh.recombine()
 # gmsh.option.setNumber("Mesh.SubdivisionAlgorithm", 1)
-# gmsh.option.setNumber("Mesh.RecombinationAlgorithm", 2) # or 3
-# model.mesh.generate(2)
+# gmsh.model.mesh.refine()
 
-gmsh.fltk.run()
+# gmsh.fltk.run()
 
 gmsh.finalize()
diff --git a/tutorial/python/t12.py b/tutorial/python/t12.py
index 3f39135dbdca101181ada6dbfbca937746a7bcab..f928cebdfe94cb1a96b9712cf5160059b564ffd9 100644
--- a/tutorial/python/t12.py
+++ b/tutorial/python/t12.py
@@ -1,12 +1,45 @@
-# This file reimplements gmsh/tutorial/t12.geo in Python.
+# ------------------------------------------------------------------------------
 #
-# Cross-patch meshing with compounds
+#  Gmsh Python tutorial 12
+#
+#  Cross-patch meshing with compounds
+#
+# ------------------------------------------------------------------------------
 
 import gmsh
 
 model = gmsh.model
 factory = model.geo
 
+# "Compound" meshing constraints allow to generate meshes across surface
+# boundaries, which can be useful e.g. for imported CAD models (e.g. STEP) with
+# undesired small features.
+
+# When a `setCompound()' meshing constraint is given, at mesh generation time
+# Gmsh
+#  1. meshes the underlying elementary geometrical entities, individually
+#  2. creates a discrete entity that combines all the individual meshes
+#  3. computes a discrete parametrization (i.e. a piece-wise linear mapping)
+#     on this discrete entity
+#  4. meshes the discrete entity using this discrete parametrization instead
+#     of the underlying geometrical description of the underlying elementary
+#     entities making up the compound
+#  5. optionally, reclassifies the mesh elements and nodes on the original
+#     entities
+
+# Step 3. above can only be performed if the mesh resulting from the
+# combination of the individual meshes can be reparametrized, i.e. if the shape
+# is "simple enough". If the shape is not amenable to reparametrization, you
+# should create a full mesh of the geometry and first re-classify it to
+# generate patches amenable to reparametrization (see `t13.py').
+
+# The mesh of the individual entities performed in Step 1. should usually be
+# finer than the desired final mesh; this can be controlled with the
+# `Mesh.CompoundCharacteristicLengthFactor' option.
+
+# The optional reclassification on the underlying elementary entities in Step
+# 5. is governed by the `Mesh.CompoundClassify' option.
+
 gmsh.initialize()
 gmsh.option.setNumber("General.Terminal", 1)
 
@@ -44,12 +77,15 @@ factory.addSurfaceFilling([15], 10)
 
 factory.synchronize()
 
-# set compound curve (dim 1) of curves 2, 3, 4
+# Treat curves 2, 3 and 4 as a single curve when meshing (i.e. mesh across
+# points 6 and 7)
 model.mesh.setCompound(1, [2, 3, 4])
-# set compound curve of curves 6, 7, 8
+
+# Idem with curves 6, 7 and 8
 model.mesh.setCompound(1, [6, 7, 8])
 
-# set compound surface from surfaces 1, 5, 10
+# Treat surfaces 1, 5 and 10 as a single surface when meshing (i.e. mesh across
+# curves 9 and 10)
 model.mesh.setCompound(2, [1, 5, 10])
 
 gmsh.fltk.run()
diff --git a/tutorial/python/t13.py b/tutorial/python/t13.py
index 999e7b0bf5cc7d930b0d13cb6d73612f2826ef47..9b8c6e4fd31806c1ed43fabfcd325ebb9c442a02 100644
--- a/tutorial/python/t13.py
+++ b/tutorial/python/t13.py
@@ -1,6 +1,10 @@
-# This file reimplements gmsh/tutorial/t13.geo in Python.
-
-# Remeshing without an underlying CAD model
+# ------------------------------------------------------------------------------
+#
+#  Gmsh Python tutorial 13
+#
+#  Remeshing without an underlying CAD model
+#
+# ------------------------------------------------------------------------------
 
 import gmsh
 import math
@@ -18,29 +22,37 @@ gmsh.merge(os.path.join(path, '..', 't13_data.stl'))
 # We first classify ("color") the surfaces by splitting the original surface
 # along sharp geometrical features. This will create new discrete surfaces,
 # curves and points.
-
 angle = 40 # Angle for surface detection
-forceParametrizablePatches = False # Create surfaces guaranteed to be parametrizable?
+
+# For complex geometries, patches can be too complex, too elongated or too large
+# to be parametrized; setting the following option will force the creation of
+# patches that are amenable to reparametrization:
+forceParametrizablePatches = False
+
+# For open surfaces include the boundary edges in the classification process.
 includeBoundary = True
 
+# Force curves to be split on given angle:
+curveAngle = 180;
+
 gmsh.model.mesh.classifySurfaces(angle * math.pi/180.,
                                  includeBoundary,
-                                 forceParametrizablePatches)
+                                 forceParametrizablePatches,
+                                 curveAngle * math.pi/180.)
 
 # Create a geometry for all the discrete curves and surfaces in the mesh, by
 # computing a parametrization for each one
 gmsh.model.mesh.createGeometry()
 
-# Create a volume as usual
+# Create a volume from all the surfaces
 s = gmsh.model.getEntities(2)
 l = gmsh.model.geo.addSurfaceLoop([s[i][1] for i in range(len(s))])
 gmsh.model.geo.addVolume([l])
 
 gmsh.model.geo.synchronize()
 
-# element size imposed by a size field, just because we can :-)
+# We specify element sizes imposed by a size field, just because we can :-)
 funny = False
-
 f = gmsh.model.mesh.field.add("MathEval");
 if funny:
   gmsh.model.mesh.field.setString(f, "F", "2*Sin((x+y)/5) + 3")
@@ -50,6 +62,6 @@ gmsh.model.mesh.field.setAsBackgroundMesh(f)
 
 gmsh.model.mesh.generate(3)
 
-gmsh.fltk.run()
+# gmsh.fltk.run()
 
 gmsh.finalize()
diff --git a/tutorial/python/t14.py b/tutorial/python/t14.py
index 9d3a67debb0b53a9e77269744d07dfa6885012b8..632df29c5df390afb4836f25f1a07f1c8b9d9f85 100644
--- a/tutorial/python/t14.py
+++ b/tutorial/python/t14.py
@@ -1,4 +1,10 @@
-# This file reimplements gmsh/tutorial/t14.geo in Python.
+# ------------------------------------------------------------------------------
+#
+#  Gmsh Python tutorial 14
+#
+#  Homology and cohomology computation
+#
+# ------------------------------------------------------------------------------
 
 # Homology computation in Gmsh finds representative chains of (relative)
 # (co)homology space bases using a mesh of a model.  The representative basis
@@ -124,12 +130,9 @@ gmsh.model.mesh.computeCohomology(domainTags=[domain_physical_tag],
 # Generate the mesh and perform the requested homology computations
 gmsh.model.mesh.generate(3)
 
-# Find physical tags of a certain homology or cohomology space chains
-physicals = gmsh.model.getPhysicalGroups()
-for pi in physicals:
-    name = gmsh.model.getPhysicalName(pi[0], pi[1])
-    if(name.find("H^1") == 0): # find tags of all cohomology chains of dimension 1
-       print("H^1 tag: " + str(pi[1]) + ", name: " + name)
+# For more information, see M. Pellikka, S. Suuriniemi, L. Kettunen and
+# C. Geuzaine. Homology and cohomology computation in finite element
+# modeling. SIAM Journal on Scientific Computing 35(5), pp. 1195-1214, 2013.
 
 gmsh.write("t14.msh")
 gmsh.finalize()
diff --git a/tutorial/python/t15.py b/tutorial/python/t15.py
index a0e18172c4d0cc15046204decbe41cce51b248d4..6174aadb452d5d22ef1c86aa6e87f93e040859ba 100644
--- a/tutorial/python/t15.py
+++ b/tutorial/python/t15.py
@@ -1,6 +1,18 @@
-# This file reimplements gmsh/tutorial/t15.geo in Python.
+# ------------------------------------------------------------------------------
 #
-# Embedded points, lines and surfaces
+#  Gmsh Python tutorial 15
+#
+#  Embedded points, lines and surfaces
+#
+# ------------------------------------------------------------------------------
+
+# By default, across geometrical dimensions meshes generated by Gmsh are only
+# conformal if lower dimensional entities are on the boundary of higher
+# dimensional ones (i.e. if points, curves or surfaces are part of the boundary
+# of volumes).
+
+# Embedding constraints allow to force a mesh to be conformal to other lower
+# dimensional entities.
 
 import gmsh
 
@@ -22,27 +34,23 @@ factory.addLine(3, 4, 3)
 factory.addLine(4, 1, 4)
 factory.addCurveLoop([4, 1, -2, 3], 1)
 factory.addPlaneSurface([1], 1)
-model.addPhysicalGroup(0, [1, 2], 1)
-model.addPhysicalGroup(1, [1, 2], 2)
-model.addPhysicalGroup(2, [1], 6)
-model.setPhysicalName(2, 6, "My surface")
-# ...end of copy
 
 # We change the mesh size to generate a coarser mesh
 lc = lc * 4
 factory.mesh.setSize([(0, 1), (0, 2), (0, 3), (0, 4)], lc)
 
-# Define a new point and embed it in a surface
+# We define a new point
 factory.addPoint(0.02, 0.02, 0., lc, 5)
 
-# We have to synchronize before embedding entites.
-# Otherwise, we get an error like "Point 5 does not exist"
+# We have to synchronize before embedding entites:
 factory.synchronize()
 
-# embed the point (dim 0) in the surface (dim 2)
+# One can force this point to be included ("embedded") in the 2D mesh, using the
+# `embed()' function:
 model.mesh.embed(0, [5], 2, 1)
 
-# We can also use embed to embed a curve in the 2D mesh
+# In the same way, one use `embed()' to force a curve to be embedded in the 2D
+# mesh:
 factory.addPoint(0.02, 0.12, 0., lc, 6)
 factory.addPoint(0.04, 0.18, 0., lc, 7)
 factory.addLine(6, 7, 5)
@@ -64,7 +72,7 @@ l = factory.addLine(7, p+1)
 factory.synchronize()
 model.mesh.embed(1, [l], 3, 1)
 
-# finally, we can embed a surface in a volume
+# Finally, we can also embed a surface in a volume:
 factory.addPoint(0.02, 0.12, 0.05, lc, p+2)
 factory.addPoint(0.04, 0.12, 0.05, lc, p+3)
 factory.addPoint(0.04, 0.18, 0.05, lc, p+4)
@@ -81,9 +89,8 @@ s = factory.addPlaneSurface([ll])
 factory.synchronize()
 model.mesh.embed(2, [s], 3, 1)
 
-# create and show the mesh
 model.mesh.generate(3)
+
 gmsh.write("t15.msh")
-gmsh.fltk.run()
 
 gmsh.finalize()
diff --git a/tutorial/python/t16.py b/tutorial/python/t16.py
index 50335e2089ce00259fe7fa17d5a2b0e8cf12d600..5f4e8ce36fe5ff291f5f1b5496c43171e4ca8043 100644
--- a/tutorial/python/t16.py
+++ b/tutorial/python/t16.py
@@ -1,4 +1,14 @@
-# This file reimplements gmsh/tutorial/t16.geo in Python.
+# ------------------------------------------------------------------------------
+#
+#  Gmsh Python tutorial 16
+#
+#  Constructive Solid Geometry, OpenCASCADE geometry kernel
+#
+# ------------------------------------------------------------------------------
+
+# Instead of constructing a model in a bottom-up fashion with Gmsh's built-in
+# geometry kernel, starting with version 3 Gmsh allows you to directly use
+# alternative geometry kernels. Here we will use the OpenCASCADE kernel.
 
 import gmsh
 import math
@@ -11,12 +21,25 @@ gmsh.option.setNumber("General.Terminal", 1)
 
 model.add("t16")
 
+# Let's build the same model as in `t5.geo', but using constructive solid
+# geometry.
+
+# We can log all messages for further processing with:
+gmsh.logger.start()
+
+# We first create two cubes:
 factory.addBox(0,0,0, 1,1,1, 1)
 factory.addBox(0,0,0, 0.5,0.5,0.5, 2)
+
+# We apply a boolean difference to create the "cube minus one eigth" shape:
 factory.cut([(3,1)], [(3,2)], 3)
 
-x = 0; y = 0.75; z = 0; r = 0.09
+# Boolean operations with OpenCASCADE always create new entities. By default the
+# extra arguments `removeObject' and `removeTool' in `cut()' are set to `True',
+# which will delete the original entities.
 
+# We then create the five spheres:
+x = 0; y = 0.75; z = 0; r = 0.09
 holes = []
 for t in range(1, 6):
     x += 0.166
@@ -24,6 +47,10 @@ for t in range(1, 6):
     factory.addSphere(x,y,z,r, 3 + t)
     holes.append((3, 3 + t))
 
+# If we had wanted five empty holes we would have used `cut()' again. Here we
+# want five spherical inclusions, whose mesh should be conformal with the mesh
+# of the cube: we thus use `fragment()', which intersects all volumes in a
+# conformal manner (without creating duplicate interfaces):
 ov, ovv = factory.fragment([(3,3)], holes)
 
 # ov contains all the generated entities of the same dimension as the input
@@ -32,23 +59,47 @@ print("fragment produced volumes:")
 for e in ov:
     print(e)
 
-# ovv contains the father-child relationships for all the input entities:
-print("before/after volume relations:")
+# ovv contains the parent-child relationships for all the input entities:
+print("before/after fragment relations:")
 for e in zip([(3,3)] + holes, ovv):
     print("parent " + str(e[0]) + " -> child " + str(e[1]))
 
 factory.synchronize()
 
+# When the boolean operation leads to simple modifications of entities, and if
+# one deletes the original entities, Gmsh tries to assign the same tag to the
+# new entities. (This behavior is governed by the
+# `Geometry.OCCBooleanPreserveNumbering' option.)
+
+# Here the `Physical Volume' definitions can thus be made for the 5 spheres
+# directly, as the five spheres (volumes 4, 5, 6, 7 and 8), which will be
+# deleted by the fragment operations, will be recreated identically (albeit with
+# new surfaces) with the same tags.:
+for i in range(1, 6):
+    gmsh.model.addPhysicalGroup(3, [3 + i], i)
+
+# The tag of the cube will change though, so we need to access it
+# programmatically:
+gmsh.model.addPhysicalGroup(3, [ov[-1][1]], 10)
+
+# Creating entities using constructive solid geometry is very powerful, but can
+# lead to small pratical issues for e.g. setting mesh sizes at points, or
+# identifying boundaries.
+
+# To identify point or other bounding entities you can take advantage of the
+# `getEntities()', `getBoundary()' and `getEntitiesInBoundingBox()' functions:
+
 lcar1 = .1
 lcar2 = .0005
 lcar3 = .055
 
-ov = model.getEntities(0);
-model.mesh.setSize(ov, lcar1);
+# Assign a mesh size to all the points:
+model.mesh.setSize(model.getEntities(0), lcar1)
 
-ov = model.getBoundary(holes, False, False, True);
-model.mesh.setSize(ov, lcar3);
+# Override this constraint on the points of the five spheres:
+model.mesh.setSize(model.getBoundary(holes, False, False, True), lcar3)
 
+# Select the corner point by searching for it geometrically:
 eps = 1e-3
 ov = model.getEntitiesInBoundingBox(0.5-eps, 0.5-eps, 0.5-eps,
                                     0.5+eps, 0.5+eps, 0.5+eps, 0)
@@ -58,4 +109,15 @@ model.mesh.generate(3)
 
 gmsh.write("t16.msh")
 
+# Additional examples created with the OpenCASCADE geometry kernel are available
+# in `t18.py', `t19.py' as well as in the `demos/api' directory.
+
+# Inspect the log:
+log = gmsh.logger.get()
+print("Logger has recorded " + str(len(log)) + " lines")
+gmsh.logger.stop()
+
+# Show the GUI:
+# gmsh.fltk.run()
+
 gmsh.finalize()
diff --git a/tutorial/python/t17.py b/tutorial/python/t17.py
index 9a5ba009b1c51fac01a30497edc3757150053cff..870922040e979a4f36091a17d1857b0a786797e9 100644
--- a/tutorial/python/t17.py
+++ b/tutorial/python/t17.py
@@ -1,6 +1,18 @@
-# This file reimplements gmsh/tutorial/t17.geo in Python.
+# ------------------------------------------------------------------------------
 #
-# Anisotropic background mesh
+#  Gmsh Python tutorial 17
+#
+#  Anisotropic background mesh
+#
+# ------------------------------------------------------------------------------
+
+# Characteristic lengths can be specified very accurately by providing a
+# background mesh, i.e., a post-processing view that contains the target mesh
+# sizes.
+
+# Here, the background mesh is represented as a metric tensor field defined on a
+# square. One should use bamg as 2d mesh generator to enable anisotropic meshes
+# in 2D.
 
 import gmsh
 import math
@@ -9,17 +21,21 @@ import os
 gmsh.initialize()
 gmsh.option.setNumber("General.Terminal", 1)
 
+gmsh.model.add("t17")
+
+# Create a square
 gmsh.model.occ.addRectangle(-1, -1, 0, 2, 2)
 gmsh.model.occ.synchronize()
 
-# add a post-processing view to use as a size field
+# Merge a post-processing view containing the target anisotropic mesh sizes
 path = os.path.dirname(os.path.abspath(__file__))
 gmsh.merge(os.path.join(path, '..', 't17_bgmesh.pos'))
 
+# Apply the view as the current background mesh
 bg_field = gmsh.model.mesh.field.add("PostView")
 gmsh.model.mesh.field.setAsBackgroundMesh(bg_field)
 
-# use bamg
+# Use bamg
 gmsh.option.setNumber("Mesh.SmoothRatio", 3)
 gmsh.option.setNumber("Mesh.AnisoMax", 1000)
 gmsh.option.setNumber("Mesh.Algorithm", 7)
diff --git a/tutorial/python/t18.py b/tutorial/python/t18.py
new file mode 100644
index 0000000000000000000000000000000000000000..1f5edbde1abe595bd8af1411884e9a394a28a4b6
--- /dev/null
+++ b/tutorial/python/t18.py
@@ -0,0 +1,117 @@
+# ------------------------------------------------------------------------------
+#
+#  Gmsh Python tutorial 18
+#
+#  Periodic meshes
+#
+# ------------------------------------------------------------------------------
+
+# Periodic meshing constraints can be imposed on surfaces and curves.
+
+import gmsh
+import math
+import os
+
+gmsh.initialize()
+gmsh.option.setNumber("General.Terminal", 1)
+
+gmsh.model.add("t18")
+
+# Let's use the OpenCASCADE geometry kernel to build two geometries.
+
+# The first geometry is very simple: a unit cube with a non-uniform mesh size
+# constraint (set on purpose to be able to verify visually that the periodicity
+# constraint works!):
+
+gmsh.model.occ.addBox(0, 0, 0, 1, 1, 1, 1)
+gmsh.model.occ.synchronize()
+
+gmsh.model.mesh.setSize(gmsh.model.getEntities(0), 0.1)
+gmsh.model.mesh.setSize([(0,1)], 0.02)
+
+# To impose that the mesh on surface 2 (the right side of the cube) should
+# match the mesh from surface 1 (the left side), the following periodicity
+# constraint is set:
+translation = [1,0,0,1, 0,1,0,0, 0,0,1,0, 0,0,0,0]
+gmsh.model.mesh.setPeriodic(2, [2], [1], translation)
+
+# The periodicity transform is provided as a 4x4 affine transformation matrix,
+# given by row.
+
+# During mesh generation, the mesh on surface 2 will be created by copying
+# the mesh from surface 1.
+
+# For more complicated cases, finding the corresponding surfaces by hand can
+# be tedious, especially when geometries are created through solid
+# modelling. Let's construct a slightly more complicated geometry.
+
+# We start with a cube and some spheres:
+gmsh.model.occ.addBox(2, 0, 0, 1, 1, 1, 10)
+x = 2-0.3; y = 0; z = 0
+gmsh.model.occ.addSphere(x, y, z, 0.35, 11)
+gmsh.model.occ.addSphere(x+1, y, z, 0.35, 12)
+gmsh.model.occ.addSphere(x, y+1, z, 0.35, 13)
+gmsh.model.occ.addSphere(x, y, z+1, 0.35, 14)
+gmsh.model.occ.addSphere(x+1, y+1, z, 0.35, 15)
+gmsh.model.occ.addSphere(x, y+1, z+1, 0.35, 16)
+gmsh.model.occ.addSphere(x+1, y, z+1, 0.35, 17)
+gmsh.model.occ.addSphere(x+1, y+1, z+1, 0.35, 18)
+
+# We first fragment all the volumes, which will leave parts of spheres
+# protruding outside the cube:
+out, _ = gmsh.model.occ.fragment([(3,10)], [(3,i) for i in range(11,19)])
+gmsh.model.occ.synchronize()
+
+# Ask OpenCASCADE to compute more accurate bounding boxes of entities using
+# the STL mesh:
+gmsh.option.setNumber("Geometry.OCCBoundsUseStl", 1)
+
+# We then retrieve all the volumes in the bounding box of the original cube,
+# and delete all the parts outside it:
+eps = 1e-3
+vin = gmsh.model.getEntitiesInBoundingBox(2-eps,-eps,-eps,
+                                          2+1+eps,1+eps,1+eps,
+                                          3)
+for v in vin: out.remove(v)
+gmsh.model.removeEntities(out, True) # Delete outside parts recursively
+
+# We now set some a non-uniform mesh size constraint (again to check results
+# visually):
+p = gmsh.model.getBoundary(vin, False, False, True) # Get all points
+gmsh.model.mesh.setSize(p, 0.1)
+p = gmsh.model.getEntitiesInBoundingBox(2-eps, -eps, -eps,
+                                        2+eps, eps, eps,
+                                        0)
+gmsh.model.mesh.setSize(p, 0.001)
+
+# We now identify corresponding surfaces on the left and right sides of the
+# geometry automatically.
+
+# First we get all surfaces on the left:
+sxmin = gmsh.model.getEntitiesInBoundingBox(2-eps, -eps, -eps,
+                                            2+eps, 1+eps, 1+eps,
+                                            2)
+
+for i in sxmin:
+    # Then we get the bounding box of each left surface
+    xmin, ymin, zmin, xmax, ymax, zmax = gmsh.model.getBoundingBox(i[0], i[1])
+    # We translate the bounding box to the right and look for surfaces inside
+    # it:
+    sxmax = gmsh.model.getEntitiesInBoundingBox(xmin-eps+1, ymin-eps, zmin-eps,
+                                                xmax+eps+1, ymax+eps, zmax+eps,
+                                                2)
+    # For all the matches, we compare the corresponding bounding boxes...
+    for j in sxmax:
+        xmin2, ymin2, zmin2, xmax2, ymax2, zmax2 = gmsh.model.getBoundingBox(
+            j[0], j[1])
+        xmin2 -= 1
+        xmax2 -= 1
+        # ...and if they match, we apply the periodicity constraint
+        if(abs(xmin2 - xmin) < eps and abs(xmax2 - xmax) < eps and
+           abs(ymin2 - ymin) < eps and abs(ymax2 - ymax) < eps and
+           abs(zmin2 - zmin) < eps and abs(zmax2 - zmax) < eps):
+            gmsh.model.mesh.setPeriodic(2, [j[1]], [i[1]], translation)
+
+# gmsh.fltk.run()
+
+gmsh.finalize()
diff --git a/tutorial/python/t19.py b/tutorial/python/t19.py
new file mode 100644
index 0000000000000000000000000000000000000000..1ed89f28e915fad71a6d862ea9d2ac8ae806a71a
--- /dev/null
+++ b/tutorial/python/t19.py
@@ -0,0 +1,92 @@
+# ------------------------------------------------------------------------------
+#
+#  Gmsh Python tutorial 19
+#
+#  Thrusections, fillets, pipes, mesh size from curvature
+#
+# ------------------------------------------------------------------------------
+
+# The OpenCASCADE geometry kernel supports several useful features for solid
+# modelling.
+
+import gmsh
+import math
+import os
+
+gmsh.initialize()
+gmsh.option.setNumber("General.Terminal", 1)
+
+gmsh.model.add("t19")
+
+# Volumes can be constructed from (closed) curve loops thanks to the
+# `addThruSections()' function
+gmsh.model.occ.addCircle(0,0,0, 0.5, 1)
+gmsh.model.occ.addCurveLoop([1], 1)
+gmsh.model.occ.addCircle(0.1,0.05,1, 0.1, 2)
+gmsh.model.occ.addCurveLoop([2], 2)
+gmsh.model.occ.addCircle(-0.1,-0.1,2, 0.3, 3)
+gmsh.model.occ.addCurveLoop([3], 3)
+gmsh.model.occ.addThruSections([1,2,3], 1)
+gmsh.model.occ.synchronize()
+
+# With `Ruled ThruSections' you can force the use of ruled surfaces:
+gmsh.model.occ.addCircle(2+0,0,0, 0.5, 11)
+gmsh.model.occ.addCurveLoop([11], 11)
+gmsh.model.occ.addCircle(2+0.1,0.05,1, 0.1, 12)
+gmsh.model.occ.addCurveLoop([12], 12)
+gmsh.model.occ.addCircle(2-0.1,-0.1,2, 0.3, 13)
+gmsh.model.occ.addCurveLoop([13], 13)
+gmsh.model.occ.addThruSections([11,12,13], 11, True, True)
+gmsh.model.occ.synchronize()
+
+# We copy the first volume, and fillet all its edges:
+out = gmsh.model.occ.copy([(3, 1)])
+gmsh.model.occ.translate(out, 4, 0, 0)
+gmsh.model.occ.synchronize()
+e = gmsh.model.getBoundary(gmsh.model.getBoundary(out), False)
+gmsh.model.occ.fillet([out[0][1]], [abs(i[1]) for i in e], [0.1])
+gmsh.model.occ.synchronize()
+
+# OpenCASCADE also allows general extrusions along a smooth path. Let's first
+# define a spline curve:
+nturns = 1.
+npts = 20
+r = 1.
+h = 1. * nturns
+p = []
+for i in range(0, npts):
+    theta = i * 2 * math.pi * nturns/npts
+    gmsh.model.occ.addPoint(r * math.cos(theta), r * math.sin(theta),
+                            i * h/npts, 1, 1000 + i)
+    p.append(1000 + i)
+gmsh.model.occ.addSpline(p, 1000)
+
+# A wire is like a curve loop, but open:
+gmsh.model.occ.addWire([1000], 1000)
+
+# We define the shape we would like to extrude along the spline (a disk):
+gmsh.model.occ.addDisk(1,0,0, 0.2, 0.2, 1000)
+gmsh.model.occ.rotate([(2,1000)], 0, 0, 0, 1, 0, 0, math.pi/2)
+
+# We extrude the disk along the spline to create a pipe:
+gmsh.model.occ.addPipe([(2,1000)], 1000)
+
+# We delete the source surface, and increase the number of sub-edges for a
+# nicer display of the geometry:
+gmsh.model.occ.remove([(2, 1000)])
+gmsh.option.setNumber("Geometry.NumSubEdges", 1000)
+
+gmsh.model.occ.synchronize()
+
+# We can activate the calculation of mesh element sizes based on curvature:
+gmsh.option.setNumber("Mesh.CharacteristicLengthFromCurvature", 1)
+
+# And we set the minimum number of elements per 2*Pi radians:
+gmsh.option.setNumber("Mesh.MinimumElementsPerTwoPi", 20)
+
+# We can constraints the min and max element sizes to stay within reasonnable
+# values (see `t10.geo' for more details):
+gmsh.option.setNumber("Mesh.CharacteristicLengthMin", 0.001)
+gmsh.option.setNumber("Mesh.CharacteristicLengthMax", 0.3)
+
+# gmsh.fltk.run()
diff --git a/tutorial/python/t2.py b/tutorial/python/t2.py
index 18eb496655ce390304471ea26de4876fcffbb107..048439cd807808d1378c02847868c738758abe25 100644
--- a/tutorial/python/t2.py
+++ b/tutorial/python/t2.py
@@ -1,15 +1,21 @@
-# This file reimplements gmsh/tutorial/t2.geo in Python. Comments focus on the new
-# API functions used, compared to the ones introduced in t1.py.
+# ------------------------------------------------------------------------------
+#
+#  Gmsh Python tutorial 2
+#
+#  Geometrical transformations, extruded geometries, elementary entities
+#  (volumes), physical groups (volumes)
+#
+# ------------------------------------------------------------------------------
 
 import gmsh
 import sys
 
-# nice shortcuts
+# We start by giving some nice shortcuts for some namespaces
 model = gmsh.model
 factory = model.geo
 
-# If sys.argv is passed, Gmsh will parse the commandline in the same way as the
-# standalone Gmsh app.
+# If sys.argv is passed to gmsh.initialize(), Gmsh will parse the command line
+# in the same way as the standalone Gmsh app:
 gmsh.initialize(sys.argv)
 
 gmsh.option.setNumber("General.Terminal", 1)
@@ -28,38 +34,51 @@ factory.addLine(3, 4, 3)
 factory.addLine(4, 1, 4)
 factory.addCurveLoop([4, 1, -2, 3], 1)
 factory.addPlaneSurface([1], 1)
-model.addPhysicalGroup(0, [1, 2], 1)
-model.addPhysicalGroup(1, [1, 2], 2)
-model.addPhysicalGroup(2, [1], 6)
-model.setPhysicalName(2, 6, "My surface")
-# ...end of copy
+model.addPhysicalGroup(0, [1, 2, 4], 5)
+ps = model.addPhysicalGroup(2, [1])
+model.setPhysicalName(2, ps, "My surface")
 
+# We can then add new points and curves in the same way as we did in `t1.py':
 factory.addPoint(0, .4, 0, lc, 5)
 factory.addLine(4, 5, 5)
 
-# Geometrical transformations take a vector of pairs of integers as first
-# argument, which contains the list of entities, represented by (dimension, tag)
-# pairs. Here we thus translate point 3 (dimension=0, tag=3), by dx=-0.05, dy=0,
-# dz=0.
+# But Gmsh also provides tools to transform (translate, rotate, etc.)
+# elementary entities or copies of elementary entities.  Geometrical
+# transformations take a vector of pairs of integers as first argument, which
+# contains the list of entities, represented by (dimension, tag) pairs.  For
+# example, the point 3 (dimension=0, tag=3) can be moved by 0.05 to the left
+# (dx=-0.05, dy=0, dz=0) with
 factory.translate([(0, 3)], -0.05, 0, 0)
 
-# The "Duplicata" functionality in .geo files is handled by
-# factory.Copy(), which takes a vector of (dim, tag) pairs as input, and
-# returns another vector of (dim, tag) pairs.
+# Note that there are no units in Gmsh: coordinates are just numbers - it's
+# up to the user to associate a meaning to them.
 
+# The resulting point can also be duplicated and translated by 0.1 along the y
+# axis. The "Duplicata" functionality in .geo files is handled by
+# model.geo.copy(), which takes a vector of (dim, tag) pairs as input, and
+# returns another vector of (dim, tag) pairs:
 ov = factory.copy([(0, 3)])
 factory.translate(ov, 0, 0.1, 0)
 
+# The new point tag is available in ov[0][1], and can be used to create new
+# lines:
 factory.addLine(3, ov[0][1], 7)
 factory.addLine(ov[0][1], 5, 8)
 factory.addCurveLoop([5,-8,-7,3], 10)
 factory.addPlaneSurface([10], 11)
 
+# In the same way, we can translate copies of the two surfaces 1 and 11 to the
+# right with the following command:
 ov = factory.copy([(2, 1), (2, 11)])
 factory.translate(ov, 0.12, 0, 0)
 
 print "New surfaces " + str(ov[0][1]) + " and " + str(ov[1][1])
 
+# Volumes are the fourth type of elementary entities in Gmsh. In the same way
+# one defines curve loops to build surfaces, one has to define surface loops
+# (i.e. `shells') to build volumes. The following volume does not have holes and
+# thus consists of a single surface loop:
+
 factory.addPoint(0., 0.3, 0.13, lc, 100)
 factory.addPoint(0.08, 0.3, 0.1, lc, 101)
 factory.addPoint(0.08, 0.4, 0.1, lc, 102)
@@ -85,28 +104,54 @@ factory.addPlaneSurface([124], 125)
 factory.addCurveLoop([115, 116, 117, 114], 126)
 factory.addPlaneSurface([126], 127)
 
-# The API to create surface loops ("shells") and volumes is similar to the
-# one used to create curve loops and surfaces.
 factory.addSurfaceLoop([127, 119, 121, 123, 125, 11], 128)
 factory.addVolume([128], 129)
 
-# Extrusion works as expected, by providing a vector of (dim, tag) pairs as
-# input, the translation vector, and a vector of (dim, tag) pairs as output.
+# When a volume can be extruded from a surface, it is usually easier to use the
+# `extrude()' function directly instead of creating all the points, curves and
+# surfaces by hand. For example, the following command extrudes the surface 11
+# along the z axis and automatically creates a new volume (as well as all the
+# needed points, curves and surfaces). As expected, the function takes a vector
+# of (dim, tag) pairs as input as well as the translation vector, and returns a
+# vector of (dim, tag) pairs as output:
 ov2 = factory.extrude([ov[1]], 0, 0, 0.12)
 
 # Mesh sizes associated to geometrical points can be set by passing a vector of
-# (dim, tag) pairs for the corresponding points.
-
+# (dim, tag) pairs for the corresponding points:
 factory.mesh.setSize([(0,103), (0,105), (0,109), (0,102), (0,28),
                       (0, 24), (0,6), (0,5)], lc * 3)
 
+# We finally group volumes 129 and 130 in a single physical group with tag `1'
+# and name "The volume":
 model.addPhysicalGroup(3, [129,130], 1)
 model.setPhysicalName(3, 1, "The volume")
 
+# We finish by synchronizing the data from the built-in geometry kernel with the
+# Gmsh model, and by generating and saving the mesh:
 factory.synchronize()
-
 model.mesh.generate(3)
-
 gmsh.write("t2.msh")
 
+# Note that, if the transformation tools are handy to create complex geometries,
+# it is also sometimes useful to generate the `flat' geometry, with an explicit
+# representation of all the elementary entities.
+#
+# With the built-in geometry kernel, this can be achieved by saving the model in
+# the `Gmsh Unrolled GEO' format:
+#
+# gmsh.write("t2.geo_unrolled");
+#
+# With the OpenCASCADE geometry kernel, unrolling the geometry can be achieved
+# by exporting in the `OpenCASCADE BRep' format:
+#
+# gmsh.write("t2.brep");
+#
+# (OpenCASCADE geometries can also be exported as STEP files.)
+
+# It is important to note that Gmsh never translates geometry data into a common
+# representation: all the operations on a geometrical entity are performed
+# natively with the associated geometry kernel. Consequently, one cannot export
+# a geometry constructed with the built-in kernel as an OpenCASCADE BRep file;
+# or export an OpenCASCADE model as an Unrolled GEO file.
+
 gmsh.finalize()
diff --git a/tutorial/python/t3.py b/tutorial/python/t3.py
index 5248ca82d28f7047378f62048b6bb11369edc7d8..ff79e0e992a1b5402a2625e10ff039c7221f0b74 100644
--- a/tutorial/python/t3.py
+++ b/tutorial/python/t3.py
@@ -1,4 +1,10 @@
-# This files reimplements gmsh/tutorial/t3.geo in Python.
+# ------------------------------------------------------------------------------
+#
+#  Gmsh Python tutorial 3
+#
+#  Extruded meshes, options
+#
+# ------------------------------------------------------------------------------
 
 import gmsh
 import math
@@ -23,30 +29,75 @@ factory.addLine(3, 4, 3)
 factory.addLine(4, 1, 4)
 factory.addCurveLoop([4, 1, -2, 3], 1)
 factory.addPlaneSurface([1], 1)
-model.addPhysicalGroup(0, [1, 2], 1)
-model.addPhysicalGroup(1, [1, 2], 2)
-model.addPhysicalGroup(2, [1], 6)
-model.setPhysicalName(2, 6, "My surface")
-# ...end of copy
+model.addPhysicalGroup(1, [1, 2, 4], 5)
+ps = model.addPhysicalGroup(2, [1])
+model.setPhysicalName(2, ps, "My surface")
+
+# As in `t2.py', we plan to perform an extrusion along the z axis.  But here,
+# instead of only extruding the geometry, we also want to extrude the 2D
+# mesh. This is done with the same `extrude()' function, but by specifying
+# element 'Layers' (2 layers in this case, the first one with 8 subdivisions and
+# the second one with 2 subdivisions, both with a height of h/2). The number of
+# elements for each layer and the (end) height of each layer are specified in
+# two vectors:
 
 h = 0.1
 angle = 90.
-
-# Extruding the mesh in addition to the geometry works as in .geo files: the
-# number of elements for each layer and the (end) height of each layer are
-# specified in two vectors.
 ov = factory.extrude([(2,1)], 0, 0, h, [8,2], [0.5,1])
 
-#/ Rotational and twisted extrusions are available as well with the built-in CAD
-# kernel. The last (optional) argument for the Extrude/Revolve/Twist commands
-# specified whether the extruded mesh should be recombined or not.
+# The extrusion can also be performed with a rotation instead of a translation,
+# and the resulting mesh can be recombined into prisms (we use only one layer
+# here, with 7 subdivisions). All rotations are specified by an an axis point
+# (-0.1, 0, 0.1), an axis direction (0, 1, 0), and a rotation angle (-Pi/2):
 ov = factory.revolve([(2,28)], -0.1,0,0.1, 0,1,0, -math.pi/2, [7])
+
+# Using the built-in geometry kernel, only rotations with angles < Pi are
+# supported. To do a full turn, you will thus need to apply at least 3
+# rotations. The OpenCASCADE geometry kernel does not have this limitation.
+
+# A translation (-2*h, 0, 0) and a rotation ((0,0.15,0.25), (1,0,0), Pi/2) can
+# also be combined to form a "twist".  The last (optional) argument for the
+# extrude() and twist() functions specifies whether the extruded mesh should be
+# recombined or not.
 ov = factory.twist([(2,50)], 0,0.15,0.25, -2*h,0,0, 1,0,0, angle*math.pi/180.,
                    [10], [], True)
 
+factory.synchronize()
+
+# All the extrusion functions return a vector of extruded entities: the "top" of
+# the extruded surface (in `ov[0]'), the newly created volume (in `ov[1]') and
+# the tags of the lateral surfaces (in `ov[2]', `ov[3]', ...).
+
+# We can then define a new physical volume (with tag 101) to group all the
+# elementary volumes:
 model.addPhysicalGroup(3, [1, 2, ov[1][1]], 101)
 
-factory.synchronize()
 model.mesh.generate(3)
 gmsh.write("t3.msh")
+
+# Let us now change some options... Since all interactive options are accessible
+# through the API, we can for example make point tags visible or redefine some
+# colors:
+
+gmsh.option.setNumber("Geometry.PointNumbers", 1);
+gmsh.option.setColor("Geometry.Points", 255, 165, 0);
+gmsh.option.setColor("General.Text", 255, 255, 255);
+gmsh.option.setColor("Mesh.Points", 255, 0, 0);
+
+# Note that color options are special: setting a color option of "X.Y"
+# actually sets the option "X.Color.Y".
+
+r, g, b, a = gmsh.option.getColor("Geometry.Points");
+gmsh.option.setColor("Geometry.Surfaces", r, g, b, a);
+
+# Launch the GUI to see the effects of the color changes:
+
+# gmsh.fltk.run();
+
+# When the GUI is launched, you can use the `Help->Current options' menu to
+# see the current values of all options. To save the options in a file, use
+# `File->Export->Gmsh options', or through the api:
+
+# gmsh.write("t3.opt");
+
 gmsh.finalize()
diff --git a/tutorial/python/t4.py b/tutorial/python/t4.py
index 6a3765ec8f95e2eed10775e857e295538ef31c43..2fd41252cd566c9f50e531ad77c30414a7b2e9f4 100644
--- a/tutorial/python/t4.py
+++ b/tutorial/python/t4.py
@@ -1,4 +1,10 @@
-# This file reimplements gmsh/tutorial/t4.geo in Python.
+# ------------------------------------------------------------------------------
+#
+#  Gmsh Python tutorial 4
+#
+#  Holes in surfaces, annotations, entity colors
+#
+# ------------------------------------------------------------------------------
 
 import gmsh
 import math
@@ -24,6 +30,7 @@ def hypot(a, b):
 ccos = (-h5*R1 + e2 * hypot(h5, hypot(e2, R1))) / (h5*h5 + e2*e2)
 ssin = math.sqrt(1 - ccos*ccos)
 
+# We start by defining some points and some lines:
 factory.addPoint(-e1-e2, 0    , 0, Lc1, 1)
 factory.addPoint(-e1-e2, h1   , 0, Lc1, 2)
 factory.addPoint(-e3-r , h1   , 0, Lc2, 3)
@@ -49,14 +56,22 @@ factory.addPoint( 0  , h1+h3+h4, 0, Lc2, 20)
 factory.addPoint( R2 , h1+h3+h4, 0, Lc2, 21)
 factory.addPoint( R2 , h1+h3   , 0, Lc2, 22)
 factory.addPoint( 0  , h1+h3   , 0, Lc2, 23)
-                                                
+
 factory.addPoint( 0, h1+h3+h4+R2, 0, Lc2, 24)
 factory.addPoint( 0, h1+h3-R2,    0, Lc2, 25)
 
 factory.addLine(1 , 17, 1)
 factory.addLine(17, 16, 2)
 
+# Gmsh provides other curve primitives than straight lines: splines, B-splines,
+# circle arcs, ellipse arcs, etc. Here we define a new circle arc, starting at
+# point 14 and ending at point 16, with the circle's center being the point 15:
 factory.addCircleArc(14,15,16, 3)
+
+# Note that, in Gmsh, circle arcs should always be smaller than Pi. The
+# OpenCASCADE geometry kernel does not have this limitation.
+
+# We can then define additional lines and circles, as well as a new surface:
 factory.addLine(14,13, 4)
 factory.addLine(13,12, 5)
 factory.addLine(12,11, 6)
@@ -77,25 +92,79 @@ factory.addLine(21,22, 20)
 
 factory.addCurveLoop([17,-15,18,19,-20,16], 21)
 factory.addPlaneSurface([21], 22)
-factory.addCurveLoop([11,-12,13,14,1,2,-3,4,5,6,7,-8,9,10], 23)
 
-# A surface with one hole is specified using 2 curve loops:
+# But we still need to define the exterior surface. Since this surface has a
+# hole, its definition now requires two curves loops:
+factory.addCurveLoop([11,-12,13,14,1,2,-3,4,5,6,7,-8,9,10], 23)
 factory.addPlaneSurface([23,21], 24)
 
-# FIXME: this will be implemented through the gmshView API
-#  View "comments" {
-#    T2(10, -10, 0){ StrCat("Created on ", Today, " with Gmsh") };
-#    T3(0, 0.11, 0, TextAttributes("Align", "Center", "Font", "Helvetica")){ "Hole" };
-#    T3(0, 0.09, 0, TextAttributes("Align", "Center")){ "file://image.png@0.01x0" };
-#    T3(-0.01, 0.09, 0, 0){ "file://image.png@0.01x0,0,0,1,0,1,0" };
-#    T3(0, 0.12, 0, TextAttributes("Align", "Center")){ "file://image.png@0.01x0#" };
-#    T2(350, -7, 0){ "file://image.png@20x0" };
-# };
+# As a general rule, if a surface has N holes, it is defined by N+1 curve loops:
+# the first loop defines the exterior boundary; the other loops define the
+# boundaries of the holes.
 
 factory.synchronize()
 
+# Finally, we can add some comments by creating a post-processing view
+# containing some strings:
+v = gmsh.view.add("comments")
+
+# Add a text string in window coordinates, 10 pixels from the left and 10 pixels
+# from the bottom:
+gmsh.view.addListDataString(v, [10, -10], ["Created with Gmsh"])
+
+# Add a text string in model coordinates centered at (X,Y,Z) = (0, 0.11, 0),
+# with some style attributes:
+gmsh.view.addListDataString(v, [0, 0.11, 0], ["Hole"],
+                            ["Align", "Center", "Font", "Helvetica"])
+
+# If a string starts with `file://', the rest is interpreted as an image
+# file. For 3D annotations, the size in model coordinates can be specified after
+# a `@' symbol in the form `widthxheight' (if one of `width' or `height' is
+# zero, natural scaling is used; if both are zero, original image dimensions in
+# pixels are used):
+gmsh.view.addListDataString(v, [0, 0.09, 0],
+                            ["file://../t4_image.png@0.01x0"],
+                            ["Align", "Center"])
+
+# The 3D orientation of the image can be specified by proving the direction
+# of the bottom and left edge of the image in model space:
+gmsh.view.addListDataString(v, [-0.01, 0.09, 0],
+                            ["file://../t4_image.png@0.01x0,0,0,1,0,1,0"])
+
+# The image can also be drawn in "billboard" mode, i.e. always parallel to
+# the camera, by using the `#' symbol:
+gmsh.view.addListDataString(v, [0, 0.12, 0],
+                            ["file://../t4_image.png@0.01x0#"],
+                            ["Align", "Center"])
+
+# The size of 2D annotations is given directly in pixels:
+gmsh.view.addListDataString(v, [150, -7], ["file://../t4_image.png@20x0"])
+
+# These annotations are handled by a list-based post-processing view. For
+# large post-processing datasets, that contain actual field values defined on
+# a mesh, you should use model-based post-processing views instead, which
+# allow to efficiently store continuous or discontinuous scalar, vector and
+# tensor fields, or arbitrary polynomial order.
+
+# Views and geometrical entities can be made to respond to double-click
+# events, here to print some messages to the console:
+gmsh.option.setString("View[0].DoubleClickedCommand",
+                      "Printf('View[0] has been double-clicked!');")
+gmsh.option.setString("Geometry.DoubleClickedLineCommand",
+                      "Printf('Curve %g has been double-clicked!', "
+                      "Geometry.DoubleClickedEntityTag);")
+
+# We can also change the color of some entities:
+gmsh.model.setColor([(2, 22)], 127, 127, 127) # Gray50
+gmsh.model.setColor([(2, 24)], 160, 32, 240) # Purple
+gmsh.model.setColor([(1, i) for i in range(1, 15)], 255, 0, 0) # Red
+gmsh.model.setColor([(1, i) for i in range(15, 21)], 255, 255, 0) # Yellow
+
 model.mesh.generate(2)
 
 gmsh.write("t4.msh")
 
+# Launch the GUI to see the results:
+gmsh.fltk.run()
+
 gmsh.finalize()
diff --git a/tutorial/python/t5.py b/tutorial/python/t5.py
index 0aafc993e79114d58ac658b6756423aab1e76458..06702c51668daf4b12a11d35a614d1d8eba6bf9e 100644
--- a/tutorial/python/t5.py
+++ b/tutorial/python/t5.py
@@ -1,4 +1,10 @@
-# This file reimplements gmsh/tutorial/t5.geo in Python.
+# ------------------------------------------------------------------------------
+#
+#  Gmsh Python tutorial 5
+#
+#  Characteristic lengths, holes in volumes
+#
+# ------------------------------------------------------------------------------
 
 import gmsh
 import math
@@ -15,6 +21,28 @@ lcar1 = .1
 lcar2 = .0005
 lcar3 = .055
 
+# If we wanted to change these mesh sizes globally (without changing the above
+# definitions), we could give a global scaling factor for all characteristic
+# lengths with e.g.
+#
+# gmsh.option.setNumber("Mesh.CharacteristicLengthFactor", 0.1);
+#
+# Since we pass `argc' and `argv' to `gmsh.initialize()', we can also give the
+# option on the command line with the `-clscale' switch. For example, with:
+#
+# > ./t5.exe -clscale 1
+#
+# this tutorial produces a mesh of approximately 3000 nodes and 14,000
+# tetrahedra. With
+#
+# > ./t5.exe -clscale 0.2
+#
+# the mesh counts approximately 231,000 nodes and 1,360,000 tetrahedra. You can
+# check mesh statistics in the graphical user interface (gmsh.fltk.run()) with
+# the `Tools->Statistics' menu.
+
+# We proceed by defining some elementary entities describing a truncated cube:
+
 factory.addPoint(0.5,0.5,0.5, lcar2, 1)
 factory.addPoint(0.5,0.5,0, lcar1, 2)
 factory.addPoint(0,0.5,0.5, lcar1, 3)
@@ -63,11 +91,13 @@ factory.addPlaneSurface([38], 39)
 
 shells = []
 
-# When the tag is not specified, a new one is automatically provided
 sl = factory.addSurfaceLoop([35,31,29,37,33,23,39,25,27])
 shells.append(sl)
 
 def cheeseHole(x, y, z, r, lc, shells):
+    # This function will create a spherical hole in a volume. We don't specify
+    # tags manually, and let the functions return them automatically:
+
     p1 = factory.addPoint(x,  y,  z,   lc)
     p2 = factory.addPoint(x+r,y,  z,   lc)
     p3 = factory.addPoint(x,  y+r,z,   lc)
@@ -98,6 +128,17 @@ def cheeseHole(x, y, z, r, lc, shells):
     l7 = factory.addCurveLoop([-c2,-c7,-c12])
     l8 = factory.addCurveLoop([-c6,-c9,c2])
 
+    # We need non-plane surfaces to define the spherical holes. Here we use the
+    # `gmsh.model.geo.addSurfaceFilling()' function, which can be used for
+    # surfaces with 3 or 4 curves on their boundary. With the he built-in
+    # kernel, if the curves are circle arcs, ruled surfaces are created;
+    # otherwise transfinite interpolation is used.
+    #
+    # With the OpenCASCADE kernel, `gmsh.model.occ.addSurfaceFilling()' uses a
+    # much more general generic surface filling algorithm, creating a BSpline
+    # surface passing through an arbitrary number of boundary curves. The
+    # `gmsh.model.geo.addThruSections()' allows to create ruled surfaces.
+
     s1 = factory.addSurfaceFilling([l1])
     s2 = factory.addSurfaceFilling([l2])
     s3 = factory.addSurfaceFilling([l3])
@@ -112,6 +153,7 @@ def cheeseHole(x, y, z, r, lc, shells):
     shells.append(sl)
     return v
 
+# We create five holes in the cube:
 x = 0; y = 0.75; z = 0; r = 0.09
 for t in range(1, 6):
     x += 0.166
@@ -119,12 +161,49 @@ for t in range(1, 6):
     v = cheeseHole(x, y, z, r, lcar3, shells)
     model.addPhysicalGroup(3, [v], t)
 
+# The volume of the cube, without the 5 holes, is defined by 6 surface loops:
+# the first surface loop defines the exterior surface; the surface loops other
+# than the first one define holes:
 factory.addVolume(shells, 186)
 
+# Note that using solid modelling with the OpenCASCADE geometry kernel, the same
+# geometry could be built quite differently: see `t16.py'.
+
+# We finally define a physical volume for the elements discretizing the cube,
+# without the holes (for which physical groups were already defined in the
+# `cheeseHole()' function):
 model.addPhysicalGroup(3, [186], 10)
+
 factory.synchronize()
 
+# We could make only part of the model visible to only mesh this subset:
+# ent = gmsh.model.getEntities()
+# gmsh.model.setVisibility(ent, False)
+# gmsh.model.setVisibility([(3, 5(], True, True)
+# gmsh.option.setNumber("Mesh.MeshOnlyVisible", 1)
+
+# Meshing algorithms can changed globally using options:
+gmsh.option.setNumber("Mesh.Algorithm", 6) # 2D algorithm set to Frontal-Delaunay
+gmsh.option.setNumber("Mesh.Algorithm3D", 1) # 3D algorithm set to Delaunay
+
+# They can also be set for individual surfaces, e.g. for using `MeshAdapt' on
+# surface 1:
+gmsh.model.mesh.setAlgorithm(2, 33, 1)
+
+# To generate a curvilinear mesh and optimize it to produce provably valid
+# curved elements (see A. Johnen, J.-F. Remacle and C. Geuzaine. Geometric
+# validity of curvilinear finite elements. Journal of Computational Physics
+# 233, pp. 359-372, 2013; and T. Toulorge, C. Geuzaine, J.-F. Remacle,
+# J. Lambrechts. Robust untangling of curvilinear meshes. Journal of
+# Computational Physics 254, pp. 8-26, 2013), you can uncomment the following
+# lines:
+#
+# gmsh.option.setNumber("Mesh.ElementOrder", 2)
+# gmsh.option.setNumber("Mesh.HighOrderOptimize", 2)
+
 model.mesh.generate(3)
 gmsh.write("t5.msh")
 
+# gmsh.fltk.run()
+
 gmsh.finalize()
diff --git a/tutorial/python/t6.py b/tutorial/python/t6.py
index f67f4e222a4bccffe61a7bf598c683a47d0daa1a..aea94af6d6651032c1327b8e8809659bd3041a30 100644
--- a/tutorial/python/t6.py
+++ b/tutorial/python/t6.py
@@ -1,4 +1,10 @@
-# This file reimplements gmsh/tutorial/t6.geo in Python.
+# ------------------------------------------------------------------------------
+#
+#  Gmsh Python tutorial 6
+#
+#  Transfinite meshes
+#
+# ------------------------------------------------------------------------------
 
 import gmsh
 import math
@@ -23,54 +29,51 @@ factory.addLine(3, 4, 3)
 factory.addLine(4, 1, 4)
 factory.addCurveLoop([4, 1, -2, 3], 1)
 factory.addPlaneSurface([1], 1)
-model.addPhysicalGroup(0, [1, 2], 1)
-model.addPhysicalGroup(1, [1, 2], 2)
-model.addPhysicalGroup(2, [1], 6)
-model.setPhysicalName(2, 6, "My surface")
-# ...end of copy
 
-# Delete surface 1 and left boundary (curve 4)
+# Delete the surface and the left line, and replace the line with 3 new ones:
 factory.remove([[2,1], [1,4]])
 
-# Replace left boundary with 3 new lines
 p1 = factory.addPoint(-0.05, 0.05, 0, lc)
 p2 = factory.addPoint(-0.05, 0.1, 0, lc)
 l1 = factory.addLine(1, p1)
 l2 = factory.addLine(p1, p2)
 l3 = factory.addLine(p2, 4)
 
-# Recreate surface
+# Create surface
 factory.addCurveLoop([2, -1, l1, l2, l3, -3], 2)
 factory.addPlaneSurface([-2], 1)
 
-# Put 20 points with a refinement toward the extremities on curve 2
-factory.mesh.setTransfiniteCurve(2, 20, "Bump", 0.05)
+# The `setTransfiniteCurve()' meshing constraints explicitly specifies the
+# location of the nodes on the curve. For example, the following command forces
+# 20 uniformly placed nodes on curve 2 (including the nodes on the two end
+# points):
+factory.mesh.setTransfiniteCurve(2, 20)
 
-# Put 20 points total on combination of curves l1, l2 and l3 (beware that the
-# points p1 and p2 are shared by the curves, so we do not create 6 + 6 + 10 = 22
-# points, but 20!)
+# Let's put 20 points total on combination of curves `l1', `l2' and `l3' (beware
+# that the points `p1' and `p2' are shared by the curves, so we do not create 6
+# + 6 + 10 = 22 nodes, but 20!)
 factory.mesh.setTransfiniteCurve(l1, 6)
 factory.mesh.setTransfiniteCurve(l2, 6)
 factory.mesh.setTransfiniteCurve(l3, 10)
 
-# Put 30 points following a geometric progression on curve 1 (reversed) and on
-# curve 3
+# Finally, we put 30 nodes following a geometric progression on curve 1
+# (reversed) and on curve 3: Put 30 points following a geometric progression
 factory.mesh.setTransfiniteCurve(1, 30, "Progression", -1.2)
 factory.mesh.setTransfiniteCurve(3, 30, "Progression", 1.2)
 
-# Define the Surface as transfinite, by specifying the four corners of the
-# transfinite interpolation
+# The `setTransfiniteSurface()' meshing constraint uses a transfinite
+# interpolation algorithm in the parametric plane of the surface to connect the
+# nodes on the boundary using a structured grid. If the surface has more than 4
+# corner points, the corners of the transfinite interpolation have to be
+# specified by hand:
 factory.mesh.setTransfiniteSurface(1, "Left", [1,2,3,4])
 
-# Recombine the triangles into quads
+# To create quadrangles instead of triangles, one can use the `setRecombine'
+# constraint:
 factory.mesh.setRecombine(2, 1)
 
-# Apply an elliptic smoother to the grid
-gmsh.option.setNumber("Mesh.Smoothing", 100)
-model.addPhysicalGroup(2, [1], 1)
-
-# When the surface has only 3 or 4 control points, the transfinite constraint
-# can be applied automatically (without specifying the corners explictly).
+# When the surface has only 3 or 4 points on its boundary the list of corners
+# can be omitted in the `setTransfiniteSurface()' call:
 factory.addPoint(0.2, 0.2, 0, 1.0, 7)
 factory.addPoint(0.2, 0.1, 0, 1.0, 8)
 factory.addPoint(0, 0.3, 0, 1.0, 9)
@@ -85,7 +88,15 @@ factory.addPlaneSurface([14], 15)
 for i in range(10,14):
     factory.mesh.setTransfiniteCurve(i, 10)
 factory.mesh.setTransfiniteSurface(15)
-model.addPhysicalGroup(2, [15], 2)
+
+# The way triangles are generated can be controlled by specifying "Left",
+# "Right" or "Alternate" in `setTransfiniteSurface()' command. Try e.g.
+#
+# factory.mesh.setTransfiniteSurface(15, "Alternate")
+
+# Finally we apply an elliptic smoother to the grid to have a more regular
+# mesh:
+gmsh.option.setNumber("Mesh.Smoothing", 100)
 
 model.mesh.generate(2)
 gmsh.write("t6.msh")
diff --git a/tutorial/python/t7.py b/tutorial/python/t7.py
index 7f091ac62b578219933d27a048b811bc3cda234d..0fccf203aaab7ef7d3551e87c5d4d97b47c1cedb 100644
--- a/tutorial/python/t7.py
+++ b/tutorial/python/t7.py
@@ -1,17 +1,24 @@
-# This file reimplements gmsh/tutorial/t7.geo in Python.
+# ------------------------------------------------------------------------------
 #
-# Background mesh
+#  Gmsh Python tutorial 7
+#
+#  Background mesh
+#
+# ------------------------------------------------------------------------------
 
 import gmsh
 import os
 
+# Mesh sizes can be specified very accurately by providing a background mesh,
+# i.e., a post-processing view that contains the target characteristic lengths.
+
 model = gmsh.model
 factory = model.geo
 
 gmsh.initialize()
 gmsh.option.setNumber("General.Terminal", 1)
 
-# Copied from t1.py...
+# Create a simple rectangular geometry
 lc = 1e-2
 factory.addPoint(0, 0, 0, lc, 1)
 factory.addPoint(.1, 0,  0, lc, 2)
@@ -23,26 +30,23 @@ factory.addLine(3, 4, 3)
 factory.addLine(4, 1, 4)
 factory.addCurveLoop([4, 1, -2, 3], 1)
 factory.addPlaneSurface([1], 1)
-model.addPhysicalGroup(0, [1, 2], 1)
-model.addPhysicalGroup(1, [1, 2], 2)
-model.addPhysicalGroup(2, [1], 6)
-model.setPhysicalName(2, 6, "My surface")
-# ...end of copy
 
 factory.synchronize()
 
-# add the background mesh file as a view
+# Merge a post-processing view containing the target mesh sizes
 path = os.path.dirname(os.path.abspath(__file__))
 gmsh.merge(os.path.join(path, '..', 't7_bgmesh.pos'))
 
-# add the post-processing view as a new size field
+# Add the post-processing view as a new size field
 bg_field = model.mesh.field.add("PostView")
+
+# Apply the view as the current background mesh
 model.mesh.field.setAsBackgroundMesh(bg_field)
 
 gmsh.model.mesh.generate(2)
 gmsh.write("t7.msh")
 
-# show the mesh file
-gmsh.fltk.run()
+# Show the mesh
+# gmsh.fltk.run()
 
 gmsh.finalize()
diff --git a/tutorial/python/t8.py b/tutorial/python/t8.py
index 6ea1d1d6c0a4b6be15f141d3710372ae9ec4799c..264d2fac551fed50b949d70ecb63a88baa4d6757 100644
--- a/tutorial/python/t8.py
+++ b/tutorial/python/t8.py
@@ -1,17 +1,24 @@
-# This file reimplements gmsh/tutorial/t8.geo in Python.
+# ------------------------------------------------------------------------------
 #
-# Post-processing, scripting, animations, options
+#  Gmsh Python tutorial 8
+#
+#  Post-processing, animations, options
+#
+# ------------------------------------------------------------------------------
 
 import gmsh
 import os
 
+# In addition to creating geometries and meshes, the Python API can also be used
+# to manipulate post-processing datasets (called "views" in Gmsh).
+
 model = gmsh.model
 factory = model.geo
 
 gmsh.initialize()
 gmsh.option.setNumber("General.Terminal", 1)
 
-# Copied from t1.py...
+# We first create a simple geometry
 lc = 1e-2
 factory.addPoint(0, 0, 0, lc, 1)
 factory.addPoint(.1, 0,  0, lc, 2)
@@ -23,25 +30,25 @@ factory.addLine(3, 4, 3)
 factory.addLine(4, 1, 4)
 factory.addCurveLoop([4, 1, -2, 3], 1)
 factory.addPlaneSurface([1], 1)
-model.addPhysicalGroup(0, [1, 2], 1)
-model.addPhysicalGroup(1, [1, 2], 2)
-model.addPhysicalGroup(2, [1], 6)
-model.setPhysicalName(2, 6, "My surface")
-# ...end of copy
 
 factory.synchronize()
 
-# add post-processing views to work on
+# We merge some post-processing views to work on
 path = os.path.dirname(os.path.abspath(__file__))
 gmsh.merge(os.path.join(path, '..', 'view1.pos'))
 gmsh.merge(os.path.join(path, '..', 'view1.pos'))
 gmsh.merge(os.path.join(path, '..', 'view4.pos')) # contains 2 views inside
 
-# set general options
-option = gmsh.option
+# Gmsh can read post-processing views in various formats. Here the `view1.pos'
+# and `view4.pos' files are in the Gmsh "parsed" format, which is interpreted by
+# the GEO script parser. The parsed format should only be used for relatively
+# small datasets of course: for larger datasets using e.g. MSH files is much
+# more efficient. Post-processing views can also be created directly from the
+# Python API.
 
+# We then set some general options:
+option = gmsh.option
 option.setNumber("General.Trackball", 0)
-
 option.setNumber("General.RotationX", 0)
 option.setNumber("General.RotationY", 0)
 option.setNumber("General.RotationZ", 0)
@@ -62,10 +69,10 @@ set_color("General.Text", black)
 option.setNumber("General.Orthographic", 0)
 option.setNumber("General.Axes", 0); option.setNumber("General.SmallAxes", 0)
 
-# show the GUI
+# Show the GUI
 gmsh.fltk.initialize()
 
-# set options for each view
+# We also set some options for each post-processing view:
 
 # If we were to follow the geo example blindly, we would read the number of
 # views from the relevant option value. A nicer way is to use
@@ -75,7 +82,7 @@ view_tags = [v0, v1, v2, v3] = gmsh.view.getTags()
 # View name format helper function: returns "View[index]." for a given view tag
 view_fmt = lambda v_tag: "View[" + str(gmsh.view.getIndex(v_tag)) + "]."
 
-# option setter
+# Option setter
 def set_opt(name, val):
     # if it's a string, call the string method
     val_type = type(val)
@@ -92,6 +99,7 @@ def set_opt(name, val):
 # We'll use this helper function for our views
 set_view_opt = lambda v_tag, name, val: set_opt(view_fmt(v_tag) + name, val)
 
+# We set some options for each post-processing view:
 # v0
 set_view_opt(v0, "IntervalsType", 2)
 set_view_opt(v0, "OffsetZ", 0.05)
@@ -102,7 +110,7 @@ set_view_opt(v0, "SmoothNormals", 1)
 
 # v1
 set_view_opt(v1, "IntervalsType", 1)
-# can't set ColorTable in API yet
+# We can't yet set the ColorTable in API
 # option.setColorTable(view_opt[v1] + "ColorTable", "{ Green, Blue }")
 set_view_opt(v1, "NbIso", 10)
 set_view_opt(v1, "ShowScale", 0)
@@ -122,11 +130,20 @@ set_view_opt(v2, "Height", 130)
 # v3
 set_view_opt(v3, "Visible", 0)
 
-t = 0
-# step through time for each view
+# You can save an MPEG movie directly by selecting `File->Export' in the
+# GUI. Several predefined animations are setup, for looping on all the time
+# steps in views, or for looping between views.
+
+# But the API can be used to build much more complex animations, by changing
+# options at run-time and re-rendering the graphics. Each frame can then be
+# saved to disk as an image, and multiple frames can be encoded to form a
+# movie. Below is an example of such a custom animation.
+
+t = 0 # Initial step
+
 for num in range(1, 4):
 
-    # update timesteps
+    # Set time step
     for v in view_tags:
         set_view_opt(v, "TimeStep", t)
 
@@ -149,14 +166,15 @@ for num in range(1, 4):
 
     frames = 50
     for num2 in range(frames):
+        # Incrementally rotate the scene
         adjust_num_opt("General.RotationX", 10)
         set_opt("General.RotationY", option.getNumber("General.RotationX") / 3)
         adjust_num_opt("General.RotationZ", 0.1)
-        gmsh.graphics.draw()
 
-        # write out the graphics scene to an image file
-        # Gmsh will try to detect the file extension
+        # Draw the scene
+        gmsh.graphics.draw()
 
+        # Uncomment the following lines to save each frame to an image file
         # if num == 3:
         #     gmsh.write("t2-{:.2g}.gif".format(num2))
         #     gmsh.write("t2-{:.2g}.ppm".format(num2))
diff --git a/tutorial/python/t9.py b/tutorial/python/t9.py
index 5c3e6b7ff9b7003ce155c6f1a2251cb2d81dbd05..865621773f23fbeaa6c8dc83a8baed9a6677ecce 100644
--- a/tutorial/python/t9.py
+++ b/tutorial/python/t9.py
@@ -1,30 +1,42 @@
-# This file reimplements gmsh/tutorial/t9.geo in Python.
+# ------------------------------------------------------------------------------
 #
-# Post-processing plugins (levelsets, sections, annotations)
+#  Gmsh Python tutorial 9
+#
+#  Post-processing plugins (levelsets, sections, annotations)
+#
+# ------------------------------------------------------------------------------
 
 import gmsh
 import os
 
+# Plugins can be added to Gmsh in order to extend its capabilities. For example,
+# post-processing plugins can modify views, or create new views based on
+# previously loaded views. Several default plugins are statically linked with
+# Gmsh, e.g. Isosurface, CutPlane, CutSphere, Skin, Transform or Smooth.
+#
+# Plugins can be controlled through the API functions with the `gmsh.plugin'
+# prefix, or from the graphical interface (right click on the view button, then
+# `Plugins').
+
 model = gmsh.model
 factory = model.geo
 
 gmsh.initialize()
 gmsh.option.setNumber("General.Terminal", 1)
 
-# add a three-dimensional scalar view to work on:
+# Let us for example include a three-dimensional scalar view:
 path = os.path.dirname(os.path.abspath(__file__))
 gmsh.merge(os.path.join(path, '..', 'view3.pos'))
 
-# set plugin options
+# We then set some options for the `Isosurface' plugin (which extracts an
+# isosurface from a 3D scalar view), and run it:
 plugin = gmsh.plugin
-
-# First plugin is Isosurface
 plugin.setNumber("Isosurface", "Value", 0.67)
 plugin.setNumber("Isosurface", "View", 0)
-
 plugin.run("Isosurface")
 
-# Second is CutPlane
+# We also set some options for the `CutPlane' plugin (which computes a section
+# of a 3D view using the plane A*x+B*y+C*z+D=0), and then run it:
 plugin.setNumber("CutPlane", "A", 0)
 plugin.setNumber("CutPlane", "B", 0.2)
 plugin.setNumber("CutPlane", "C", 1)
@@ -32,9 +44,10 @@ plugin.setNumber("CutPlane", "D", 0)
 plugin.setNumber("CutPlane", "View", 0)
 plugin.run("CutPlane")
 
-# Third is Annotate
+# Add a title (By convention, for window coordinates a value greater than 99999
+# represents the center. We could also use `General.GraphicsWidth / 2', but that
+# would only center the string for the current window size.):
 plugin.setString("Annotate", "Text", "A nice title")
-# By convention, window coordinates larger than 99999 represent the center
 plugin.setNumber("Annotate", "X", 1.e5)
 plugin.setNumber("Annotate", "Y", 50)
 plugin.setString("Annotate", "Font", "Times-BoldItalic")
@@ -49,9 +62,8 @@ plugin.setString("Annotate", "Font", "Times-Roman")
 plugin.setNumber("Annotate", "FontSize", 12)
 plugin.run("Annotate")
 
-# set some general options
+# We finish by setting some options:
 option = gmsh.option
-
 option.setNumber("View[0].Light", 1)
 option.setNumber("View[0].IntervalsType", 1)
 option.setNumber("View[0].NbIso", 6)
diff --git a/tutorial/python/x1.py b/tutorial/python/x1.py
new file mode 100644
index 0000000000000000000000000000000000000000..befb7f6a7822267bd4f1530bdd9044bfb83879fc
--- /dev/null
+++ b/tutorial/python/x1.py
@@ -0,0 +1,98 @@
+# -----------------------------------------------------------------------------
+#
+#  Gmsh Python extended tutorial 1
+#
+#  Accessing geometrical and mesh data
+#
+# -----------------------------------------------------------------------------
+
+# The Python API allows to do much more than what can be done in .geo files. These
+# additional features are introduced gradually in the extended tutorials,
+# starting with `x1.py'.
+
+# In this first extended tutorial, we start by using the API to access basic
+# geometrical and mesh data.
+
+import gmsh
+import sys
+
+if len(sys.argv) < 2:
+    print "Usage: " + sys.argv[0] + " file"
+    exit
+
+gmsh.initialize()
+gmsh.option.setNumber("General.Terminal", 1)
+gmsh.open(sys.argv[1])
+
+# Geometrical data is made of elementary model `entities', called `points'
+# (entities of dimension 0), `curves' (entities of dimension 1), `surfaces'
+# (entities of dimension 2) and `volumes' (entities of dimension 3). As we have
+# seen in the other Python tutorials, elementary model entities are identified
+# by their dimension and by a `tag': a strictly positive identification
+# number. Model entities can be either CAD entities (from the built-in `geo'
+# kernel or from the OpenCASCADE `occ' kernel) or `discrete' entities (defined
+# by a mesh). `Physical groups' are collections of model entities and are also
+# identified by their dimension and by a tag.
+
+# Get all the elementary entities in the model, as a vector of (dimension, tag)
+# pairs:
+entities = gmsh.model.getEntities()
+
+for e in entities:
+    # Mesh data is made of `elements' (points, lines, triangles, ...), defined
+    # by an ordered list of their `nodes'. Elements and nodes are identified by
+    # `tags' as well (strictly positive identification numbers), and are stored
+    # ("classified") in the model entity they discretize. Tags for elements and
+    # nodes are globally unique (and not only per dimension, like entities).
+
+    # A model entity of dimension 0 (a geometrical point) will contain a mesh
+    # element of type point, as well as a mesh node. A model curve will contain
+    # line elements as well as its interior nodes, while its boundary nodes will
+    # be stored in the bounding model points. A model surface will contain
+    # triangular and/or quadrangular elements and all the nodes not classified
+    # on its boundary or on its embedded entities. A model volume will contain
+    # tetrahedra, hexahedra, etc. and all the nodes not classified on its
+    # boundary or on its embedded entities.
+
+    # Dimension and tag of the entity:
+    dim = e[0]
+    tag = e[1]
+
+    # Get the mesh nodes for the entity (dim, tag):
+    nodeTags, nodeCoords, nodeParams = gmsh.model.mesh.getNodes(dim, tag)
+
+    # Get the mesh elements for the entity (dim, tag):
+    elemTypes, elemTags, elemNodeTags = gmsh.model.mesh.getElements(dim, tag)
+
+    # Let's print a summary of the information available on the entity and its
+    # mesh.
+
+    # * Type of the entity:
+    print("Entity " + str(e) + " of type " + gmsh.model.getType(e[0], e[1]))
+
+    # * Number of mesh nodes and elements:
+    numElem = sum(len(i) for i in elemTags)
+    print(" - Mesh has " + str(len(nodeTags)) + " nodes and " + str(numElem) +
+          " elements")
+
+    # * Entities on its boundary:
+    boundary = gmsh.model.getBoundary([e])
+    if len(boundary):
+        print(" - Boundary entities: " + str(boundary))
+
+    # * Does the entity belong to physical groups?
+    physicalTags = gmsh.model.getPhysicalGroupsForEntity(dim, tag)
+    if len(physicalTags):
+        print(" - Physical group tags: " + str(physicalTags))
+
+    # * Is the entity a partition entity? If so, what is its parent entity?
+    partitions = gmsh.model.getPartitions(e[0], e[1])
+    if len(partitions):
+           print(" - Partition tags: " + str(partitions) +
+                 " - parent entity " + str(gmsh.model.getParent(e[0], e[1])))
+
+    # * List all types of elements making up the mesh of the entity:
+    for t in elemTypes:
+        name, dim, order, numv, parv, _ = gmsh.model.mesh.getElementProperties(t)
+        print(" - Element type: " + name + ", order " + str(order) + " (" +
+              str(numv) + " nodes in param coord: " + str(parv) + ")")
diff --git a/tutorial/t11.geo b/tutorial/t11.geo
index 55eb82f09912b5a4aa5e52c3e77acb9f7ff9a123..ddf30247c193eedf7c506cd0f5a56372ea966283 100644
--- a/tutorial/t11.geo
+++ b/tutorial/t11.geo
@@ -7,7 +7,7 @@
 // -----------------------------------------------------------------------------
 
 // We have seen in tutorials `t3.geo' and `t6.geo' that extruded and transfinite
-// meshes can be "recombined" into quads/prisms/hexahedra by using the
+// meshes can be "recombined" into quads, prisms or hexahedra by using the
 // "Recombine" keyword. Unstructured meshes can be recombined in the same
 // way. Let's define a simple geometry with an analytical mesh size field:
 
diff --git a/tutorial/t2.geo b/tutorial/t2.geo
index 39863e799e73c76e89c045f49e876a441783953a..5f421aa3c9d6d031dbd87a95096b34d238cfd645 100644
--- a/tutorial/t2.geo
+++ b/tutorial/t2.geo
@@ -42,7 +42,7 @@ Plane Surface(11) = {10};
 // obtain the tags of newly created entities, one can use the return value of
 // the transformation commands directly. For example, the `Translate' command
 // returns a list containing the tags of the translated entities. Let's
-// translate copies of the two surfaces 6 and 11 to the right with the following
+// translate copies of the two surfaces 1 and 11 to the right with the following
 // command:
 
 my_new_surfs[] = Translate {0.12, 0, 0} { Duplicata{ Surface{1, 11}; } };