diff --git a/Plugin/CMakeLists.txt b/Plugin/CMakeLists.txt
index 8ed250915f82ed5d37b428fe0ef880c261e82fb5..b93ce557b7ed5973db18ef7ee3a390a6a5ed1c9b 100644
--- a/Plugin/CMakeLists.txt
+++ b/Plugin/CMakeLists.txt
@@ -31,7 +31,7 @@ set(SRC
   Scal2Vec.cpp
   CutMesh.cpp
   NewView.cpp
-  SimplePartition.cpp
+  SimplePartition.cpp Crack.cpp
 )
 
 file(GLOB HDR RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} *.h)
diff --git a/Plugin/Crack.cpp b/Plugin/Crack.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..6468d4dee7e0dce46e650a0d627bf9c3fe4ce730
--- /dev/null
+++ b/Plugin/Crack.cpp
@@ -0,0 +1,243 @@
+// Gmsh - Copyright (C) 1997-2013 C. Geuzaine, J.-F. Remacle
+//
+// See the LICENSE.txt file for license information. Please report all
+// bugs and problems to the public mailing list <gmsh@geuz.org>.
+
+#include "Crack.h"
+#include "GModel.h"
+#include "partitionFace.h"
+#include "partitionEdge.h"
+#include "MElement.h"
+#include "MLine.h"
+#include "MTriangle.h"
+#include "MQuadrangle.h"
+#include "MFace.h"
+#include "MEdge.h"
+#include "mathEvaluator.h"
+#if defined(HAVE_MESH)
+#include "meshPartition.h"
+#endif
+
+StringXNumber CrackOptions_Number[] = {
+  {GMSH_FULLRC, "Dimension", NULL, 1.},
+  {GMSH_FULLRC, "PhysicalGroup", NULL, 1.},
+};
+
+extern "C"
+{
+  GMSH_Plugin *GMSH_RegisterCrackPlugin()
+  {
+    return new GMSH_CrackPlugin();
+  }
+}
+
+std::string GMSH_CrackPlugin::getHelp() const
+{
+  return "Plugin(Crack) creates a crack around the physical group";
+}
+
+int GMSH_CrackPlugin::getNbOptions() const
+{
+  return sizeof(CrackOptions_Number) / sizeof(StringXNumber);
+}
+
+StringXNumber *GMSH_CrackPlugin::getOption(int iopt)
+{
+  return &CrackOptions_Number[iopt];
+}
+
+class EdgeData{
+public:
+  EdgeData(MEdge e) : edge(e) {}
+  MEdge edge;
+  std::vector<MVertex*> data;
+};
+
+struct Less_EdgeData : public std::binary_function<EdgeData, EdgeData, bool> {
+  bool operator()(const EdgeData &e1, const EdgeData &e2) const
+  {
+    if(e1.edge.getMinVertex() < e2.edge.getMinVertex()) return true;
+    if(e1.edge.getMinVertex() > e2.edge.getMinVertex()) return false;
+    if(e1.edge.getMaxVertex() < e2.edge.getMaxVertex()) return true;
+    return false;
+  }
+};
+
+PView *GMSH_CrackPlugin::execute(PView *view)
+{
+  int dim = (int)CrackOptions_Number[0].def;
+  int physical = (int)CrackOptions_Number[1].def;
+
+  if(dim != 1 && dim != 2){
+    Msg::Error("Crack dimension should be 1 or 2");
+    return view;
+  }
+
+  GModel *m = GModel::current();
+  std::map<int, std::vector<GEntity*> > groups[4];
+  m->getPhysicalGroups(groups);
+  std::vector<GEntity*> entities = groups[dim][physical];
+
+  if(entities.empty()){
+    Msg::Error("Physical group %d (dimension %d) is empty", physical, dim);
+    return view;
+  }
+
+  // get crack elements
+  std::vector<MElement*> crackElements;
+  for(unsigned int i = 0; i < entities.size(); i++)
+    for(unsigned int j = 0; j < entities[i]->getNumMeshElements(); j++)
+      crackElements.push_back(entities[i]->getMeshElement(j));
+
+  // get internal crack vertices
+  std::set<MVertex*> crackVertices, bndVertices;
+  if(dim == 1){
+    for(unsigned int i = 0; i < crackElements.size(); i++){
+      for(int j = 0; j < crackElements[i]->getNumVertices(); j++){
+        MVertex *v = crackElements[i]->getVertex(j);
+        crackVertices.insert(v);
+        if(bndVertices.find(v) == bndVertices.end())
+          bndVertices.insert(v);
+        else
+          bndVertices.erase(v);
+      }
+    }
+  }
+  else{
+    std::set<EdgeData, Less_EdgeData> bnd;
+    for(unsigned int i = 0; i < crackElements.size(); i++){
+      for(int j = 0; j < crackElements[i]->getNumVertices(); j++){
+        MVertex *v = crackElements[i]->getVertex(j);
+        crackVertices.insert(v);
+      }
+      for(int j = 0; j < crackElements[i]->getNumEdges(); j++){
+        EdgeData ed(crackElements[i]->getEdge(j));
+        if(bnd.find(ed) == bnd.end()){
+          crackElements[i]->getEdgeVertices(j, ed.data);
+          bnd.insert(ed);
+        }
+        else
+          bnd.erase(ed);
+      }
+    }
+    for(std::set<EdgeData>::iterator it = bnd.begin(); it != bnd.end(); it++)
+      bndVertices.insert(it->data.begin(), it->data.end());
+  }
+  for(std::set<MVertex*>::iterator it = bndVertices.begin();
+      it != bndVertices.end(); it++)
+    crackVertices.erase(*it);
+
+  // compute smoothed normals on crack vertices
+  std::map<MVertex*, std::vector<MElement*> > vxe;
+  for(unsigned int i = 0; i < crackElements.size(); i++){
+    MElement *e = crackElements[i];
+    for(int k = 0; k < e->getNumVertices(); k++)
+      vxe[e->getVertex(k)].push_back(e);
+  }
+  std::map<MVertex*, SVector3> vxn;
+  for(std::map<MVertex*, std::vector<MElement*> >::iterator it = vxe.begin();
+      it != vxe.end(); it++){
+    SVector3 n;
+    for(unsigned int i = 0; i < it->second.size(); i++){
+      if(dim == 1)
+        n += it->second[i]->getEdge(0).normal();
+      else
+        n += it->second[i]->getFace(0).normal();
+    }
+    n.normalize();
+    vxn[it->first] = n;
+  }
+
+  // compute elements on one side of the crack
+  vxe.clear();
+  std::vector<GEntity*> allentities;
+  m->getEntities(allentities);
+  for(unsigned int ent = 0; ent < allentities.size(); ent++){
+    if(allentities[ent]->dim() != dim + 1) continue;
+    for(int i = 0; i < allentities[ent]->getNumMeshElements(); i++){
+      MElement *e = allentities[ent]->getMeshElement(i);
+      for(int j = 0; j < e->getNumVertices(); j++){
+        MVertex *v = e->getVertex(j);
+        if(crackVertices.find(v) != crackVertices.end()){
+          MVertex *vclose = 0;
+          double d = 1e22;
+          SVector3 dv;
+          for(std::set<MVertex*>::iterator it = crackVertices.begin();
+              it != crackVertices.end(); it++){
+            MVertex *v = *it;
+            double d2 = v->point().distance(e->barycenter());
+            if(d2 < d){
+              d = d2;
+              vclose = v;
+              dv = SVector3(e->barycenter(), vclose->point());
+            }
+          }
+          if(dot(vxn[vclose], dv) < 0)
+            vxe[v].push_back(e);
+        }
+      }
+    }
+  }
+
+  // create new crack entity
+  GEdge *crackEdge = 0;
+  GFace *crackFace = 0;
+  if(dim == 1){
+    crackEdge = new discreteEdge(m, m->getMaxElementaryNumber(1) + 1, 0, 0);
+    m->add(crackEdge);
+  }
+  else{
+    crackFace = new discreteFace(m, m->getMaxElementaryNumber(2) + 1);
+    m->add(crackFace);
+  }
+  GEntity *crackEntity = crackEdge ? (GEntity*)crackEdge : (GEntity*)crackFace;
+  crackEntity->physicals.push_back(physical);
+
+  // duplicate crack vertices
+  std::map<MVertex *, MVertex*> vxv;
+  for(std::set<MVertex*>::iterator it = crackVertices.begin();
+      it != crackVertices.end(); it++){
+    MVertex *v = *it;
+    MVertex *newv = new MVertex(v->x(), v->y(), v->z(), crackEntity);
+    crackEntity->mesh_vertices.push_back(newv);
+    vxv[v] = newv;
+  }
+
+  // duplicate crack elements
+  for(unsigned int i = 0; i < crackElements.size(); i++){
+    MElement *e = crackElements[i];
+    std::vector<MVertex*> verts;
+    e->getVertices(verts);
+    for(unsigned int j = 0; j < verts.size(); j++){
+      if(vxv.count(verts[j]))
+        verts[j] = vxv[verts[j]];
+    }
+    MElementFactory f;
+    MElement *newe = f.create(e->getTypeForMSH(), verts, 0, e->getPartition());
+    if(crackEdge && newe->getType() == TYPE_LIN)
+      crackEdge->lines.push_back((MLine*)newe);
+    else if(crackFace && newe->getType() == TYPE_TRI)
+      crackFace->triangles.push_back((MTriangle*)newe);
+    else if(crackFace && newe->getType() == TYPE_QUA)
+      crackFace->quadrangles.push_back((MQuadrangle*)newe);
+  }
+
+  // replace vertices in elements on one side of the crack
+  std::set<MElement*> eles;
+  for(std::map<MVertex*, std::vector<MElement*> >::iterator it = vxe.begin();
+      it != vxe.end(); it++){
+    for(unsigned int i = 0; i < it->second.size(); i++)
+      eles.insert(it->second[i]);
+  }
+  for(std::set<MElement*>::iterator it = eles.begin(); it != eles.end(); it++){
+    MElement *e = *it;
+    for(int i = 0; i < e->getNumVertices(); i++){
+      if(vxv.count(e->getVertex(i)))
+        e->setVertex(i, vxv[e->getVertex(i)]);
+    }
+  }
+
+  CTX::instance()->mesh.changed = ENT_ALL;
+
+  return view;
+}
diff --git a/Plugin/Crack.h b/Plugin/Crack.h
new file mode 100644
index 0000000000000000000000000000000000000000..6ce6c8a59e3bb8e1876f2cf8a4d51d08ff5e64ad
--- /dev/null
+++ b/Plugin/Crack.h
@@ -0,0 +1,31 @@
+// Gmsh - Copyright (C) 1997-2013 C. Geuzaine, J.-F. Remacle
+//
+// See the LICENSE.txt file for license information. Please report all
+// bugs and problems to the public mailing list <gmsh@geuz.org>.
+
+#ifndef _CRACK_H_
+#define _CRACK_H_
+
+#include "Plugin.h"
+
+extern "C"
+{
+  GMSH_Plugin *GMSH_RegisterCrackPlugin();
+}
+
+class GMSH_CrackPlugin : public GMSH_PostPlugin
+{
+ public:
+  GMSH_CrackPlugin(){}
+  std::string getName() const { return "Crack"; }
+  std::string getShortHelp() const
+  {
+    return "Crack generator";
+  }
+  std::string getHelp() const;
+  int getNbOptions() const;
+  StringXNumber* getOption(int iopt);
+  PView *execute(PView *);
+};
+
+#endif
diff --git a/Plugin/PluginManager.cpp b/Plugin/PluginManager.cpp
index 58512784be0df258118c6ddcf5368bc65b086b3a..b1acc9510150b52e004247f1ae5df4471a5b5560 100644
--- a/Plugin/PluginManager.cpp
+++ b/Plugin/PluginManager.cpp
@@ -23,6 +23,7 @@
 #include "MathEval.h"
 #include "ExtractElements.h"
 #include "SimplePartition.h"
+#include "Crack.h"
 #include "HarmonicToTime.h"
 #include "ModulusPhase.h"
 #include "Integrate.h"
@@ -242,6 +243,8 @@ void PluginManager::registerDefaultPlugins()
                       ("NewView", GMSH_RegisterNewViewPlugin()));
     allPlugins.insert(std::pair<std::string, GMSH_Plugin*>
                       ("SimplePartition", GMSH_RegisterSimplePartitionPlugin()));
+    allPlugins.insert(std::pair<std::string, GMSH_Plugin*>
+                      ("Crack", GMSH_RegisterCrackPlugin()));
 #if defined(HAVE_TETGEN)
     allPlugins.insert(std::pair<std::string, GMSH_Plugin*>
                       ("Tetrahedralize", GMSH_RegisterTetrahedralizePlugin()));
diff --git a/doc/VERSIONS.txt b/doc/VERSIONS.txt
index 0ca581f4b65340af3a39de830b8e28592c57d011..7c0206f385cb97bfdb5cbcc2928d96ff9cc41b4b 100644
--- a/doc/VERSIONS.txt
+++ b/doc/VERSIONS.txt
@@ -1,6 +1,6 @@
-?: new single-window GUI, with dynamically customizable widget tree; faster
-STEP/BRep import; arbitrary size image export; faster 2D Delaunay/Frontal
-algorithms; full option viewer/editor; random bug fixes.
+2.7.0 (?): new single-window GUI, with dynamically customizable widget tree;
+faster STEP/BRep import; arbitrary size image export; faster 2D Delaunay/Frontal
+algorithms; full option viewer/editor; many bug fixes.
 
 2.6.1 (July 15, 2012): minor improvements and bug fixes.