Skip to content
Snippets Groups Projects
Forked from gmsh / gmsh
15530 commits behind the upstream repository.
classificationEditor.cpp 21.60 KiB
// Gmsh - Copyright (C) 1997-2009 C. Geuzaine, J.-F. Remacle
//
// See the LICENSE.txt file for license information. Please report all
// bugs and problems to <gmsh@geuz.org>.

#include <FL/Fl_Tabs.H>
#include <FL/Fl_Value_Input.H>
#include "GUI.h"
#include "classificationEditor.h"
#include "paletteWindow.h"
#include "Numeric.h"
#include "Draw.h"
#include "Options.h"
#include "Context.h"
#include "GmshMessage.h"
#include "MLine.h"
#include "meshGFaceDelaunayInsertion.h"
#include "meshGFaceOptimize.h"
#include "discreteEdge.h"
#include "discreteFace.h"

static void NoElementsSelectedMode(classificationEditor *e)
{
  e->_buttons[CLASSBUTTON_DEL]->deactivate();
  e->_buttons[CLASSBUTTON_ADD]->deactivate();
  e->_buttons[CLASSBUTTON_CLEAR]->deactivate();
  //  e->_buttons[CLASSBUTTON_OK]->deactivate();
  e->_togbuttons[CLASSTOGBUTTON_CLOS]->deactivate();
  e->_inputs[CLASSVALUE_ANGLE]->deactivate();

  e->_buttons[CLASSBUTTON_SELECT]->activate(); 
  e->_togbuttons[CLASSTOGBUTTON_HIDE]->activate(); 
}

static void ElementsSelectedMode(classificationEditor *e)
{
  e->_buttons[CLASSBUTTON_DEL]->activate();
  e->_buttons[CLASSBUTTON_ADD]->activate();
  e->_buttons[CLASSBUTTON_CLEAR]->activate();
  e->_togbuttons[CLASSTOGBUTTON_CLOS]->activate();
  e->_inputs[CLASSVALUE_ANGLE]->activate();
  //  e->_buttons[CLASSBUTTON_OK]->activate();

  e->_buttons[CLASSBUTTON_SELECT]->deactivate(); 
  e->_togbuttons[CLASSTOGBUTTON_HIDE]->deactivate(); 
}

// we should 
static void class_selectgface_cb(Fl_Widget *w, void *data)
{
  classificationEditor *e = (classificationEditor*)data;
  std::vector<GFace*> temp;

  opt_geometry_surfaces(0, GMSH_SET | GMSH_GUI, 1);

  while(1) {
    CTX::instance()->mesh.changed = ENT_ALL;
    Draw();

    Msg::StatusBar(3, false, "Select Model Face\n"
		   "[Press 'e' to end selection or 'q' to abort]");
    
    char ib = GUI::instance()->selectEntity(ENT_SURFACE);
    if(ib == 'l') {
      for(unsigned int i = 0; i < GUI::instance()->selectedFaces.size(); i++){
        GUI::instance()->selectedFaces[i]->setSelection(1);
        temp.push_back(GUI::instance()->selectedFaces[i]);
      }
    }
    // ok store the list of gfaces !
    if(ib == 'e') {
      GModel::current()->setSelection(0);
      for(unsigned int i = 0; i < temp.size(); i++){
        e->_faces.insert(temp[i]);
      }
      break;
    }
    // do nothing
    if(ib == 'q') {
      GModel::current()->setSelection(0);
      break;
    }
  } 
  CTX::instance()->mesh.changed = ENT_ALL;
  Draw();  
  Msg::StatusBar(3, false, "");
}

static void class_deleteedge_cb(Fl_Widget *w, void *data)
{
  classificationEditor *e = (classificationEditor*)data;
  std::vector<MLine*> ele;
  
  CTX::instance()->pickElements = 1;
  
  while(1) {
    CTX::instance()->mesh.changed = ENT_ALL;
    Draw();

    Msg::StatusBar(3, false, "Select Elements\n"
		   "[Press 'e' to end selection or 'q' to abort]");
    
    char ib = GUI::instance()->selectEntity(ENT_ALL);
    if(ib == 'l') {
      if(CTX::instance()->pickElements){
        for(unsigned int i = 0; i < GUI::instance()->selectedElements.size(); i++){
          MElement *me = GUI::instance()->selectedElements[i];
          if(me->getNumEdges() == 1 && me->getVisibility() != 2){
            me->setVisibility(2); ele.push_back((MLine*)me);
          }
        }
      }
    }
    if(ib == 'r') {
      for(unsigned int i = 0; i < GUI::instance()->selectedElements.size(); i++)
        GUI::instance()->selectedElements[i]->setVisibility(1);
    }
    // ok, we compute edges !
    if(ib == 'e') {
      GModel::current()->setSelection(0);
      break;
    }
    // do nothing
    if(ib == 'q') {
      GModel::current()->setSelection(0);
      ele.clear();
      break;
    }
  }

  std::sort(ele.begin(),ele.end());
  //  look in all temporary edges if a deleted one is present and delete it !
  std::vector<MLine*> temp = e->temporary->lines;
  e->temporary->lines.clear();
       
  for(unsigned int i = 0; i < temp.size(); i++){      
    std::vector<MLine*>::iterator it = std::find(ele.begin(), ele.end(), temp[i]);
    if(it != ele.end()){
      delete temp[i];
    }
    else e->temporary->lines.push_back(temp[i]);
  }
  
  CTX::instance()->mesh.changed = ENT_ALL;
  CTX::instance()->pickElements = 0;
  Draw();  
  Msg::StatusBar(3, false, "");
}

static void class_save_cb(Fl_Widget *w, void *data)
{
  classificationEditor *e = (classificationEditor*)data;

  e->saved->lines.insert(e->saved->lines.end(), e->temporary->lines.begin(),
                         e->temporary->lines.end());
  e->temporary->lines.clear();
  e->_elements.clear();
  e->edges_detected.clear();

  CTX::instance()->mesh.changed = ENT_ALL;
  CTX::instance()->pickElements = 0;
  NoElementsSelectedMode(e);
  Draw();  
  Msg::StatusBar(3, false, "");
}

static void class_clear_cb(Fl_Widget *w, void *data)
{
  classificationEditor *e = (classificationEditor*)data;

  for(unsigned int i = 0; i < e->temporary->lines.size(); i++){      
    delete e->temporary->lines[i];
  }
  e->temporary->lines.clear();

  CTX::instance()->mesh.changed = ENT_ALL;
  CTX::instance()->pickElements = 0;
  NoElementsSelectedMode(e);
  Draw();  
  Msg::StatusBar(3, false, "");
}

static void class_ok_cb(Fl_Widget *w, void *data)
{
  classificationEditor *e = (classificationEditor*)data;
  e->edge_detec->deactivate();
  e->edge_detec->hide();
  e->face_color->activate();
  e->face_color->show();
  class_save_cb(w,data);
  opt_mesh_lines(0, GMSH_SET | GMSH_GUI, e->op[0]);
  opt_mesh_surfaces_edges(0, GMSH_SET | GMSH_GUI, e->op[1]);
  opt_mesh_surfaces_faces(0, GMSH_SET | GMSH_GUI, e->op[2]);
  opt_mesh_line_width(0, GMSH_SET | GMSH_GUI, e->op[3]);

  Msg::StatusBar(3, false, "");
}

static int maxEdgeNum()
{
  GModel::eiter it = GModel::current()->firstEdge();
  GModel::eiter ite = GModel::current()->lastEdge();
  int MAXX = 0;
  while(it != ite){
    MAXX = std::max(MAXX, (*it)->tag());
    ++it;
  }
  return MAXX;
}
static int maxFaceNum()
{
  GModel::fiter it =  GModel::current()->firstFace();
  GModel::fiter ite = GModel::current()->lastFace();
  int MAXX = 0;
  while(it != ite){
    MAXX = std::max(MAXX, (*it)->tag());
    ++it;
  }
  return MAXX;
}

struct compareMLinePtr 
{
  bool operator () (MLine *l1, MLine *l2) const
  {
    static Less_Edge le;
    return le(l1->getEdge(0), l2->getEdge(0)); 
  }
};

static void recurClassify(MTri3 *t, GFace *gf,
                          std::map<MLine*, GEdge*, compareMLinePtr> &lines,
                          std::map<MTriangle*, GFace*> &reverse)
{
  if(!t->isDeleted()){
    gf->triangles.push_back(t->tri());
    reverse[t->tri()] = gf;
    t->setDeleted(true);
    for(int i = 0; i < 3; i++){
      MTri3 *tn = t->getNeigh(i);
      if(tn){
        edgeXface exf(t, i);
        MLine ml(exf.v[0], exf.v[1]);       
        std::map<MLine*, GEdge*, compareMLinePtr>::iterator it = lines.find(&ml);
        if(it == lines.end())
          recurClassify(tn, gf, lines, reverse);
      }
    }  
  }
}

static GEdge *getNewModelEdge(GFace *gf1, GFace *gf2, 
                              std::map<std::pair<int, int>, GEdge* > &newEdges)
{
  int t1 = gf1 ? gf1->tag() : -1;
  int t2 = gf2 ? gf2->tag() : -1;
  int i1 = std::min(t1,t2);
  int i2 = std::max(t1,t2);

  if(i1 == i2) return 0;

  std::map<std::pair<int, int>, GEdge*>::iterator it = 
    newEdges.find(std::make_pair<int, int>(i1, i2));
  if(it == newEdges.end()){
    discreteEdge *temporary = new discreteEdge(GModel::current(), maxEdgeNum() + 1, 0, 0);
    printf("add new edge gf1=%d gf2=%d \n", t1, t2);
    GModel::current()->add(temporary);
    newEdges[std::make_pair<int, int>(i1, i2)] = temporary;
    return temporary;
  }
  else
    return it->second;  
}

static void recurClassifyEdges(MTri3 *t, 
                               std::map<MTriangle*, GFace*> &reverse,
                               std::map<MLine*, GEdge*, compareMLinePtr> &lines,
                               std::set<MLine*> &touched,
                               std::map<std::pair<int, int>, GEdge*> &newEdges)
{
  if(!t->isDeleted()){

    t->setDeleted(true);
    GFace *gf1 = reverse[t->tri()];
    for(int i = 0; i < 3; i++){
      GFace *gf2 = 0;
      MTri3 *tn = t->getNeigh(i);
      if(tn)
        gf2 = reverse[tn->tri()];
      edgeXface exf(t, i);
      MLine ml(exf.v[0], exf.v[1]);
      std::map<MLine*, GEdge*, compareMLinePtr>::iterator it = lines.find(&ml);
      if(it != lines.end()){
        if(touched.find(it->first) == touched.end()){
          GEdge *ge =  getNewModelEdge(gf1, gf2, newEdges);
          if(ge) ge->lines.push_back(it->first);
          touched.insert(it->first);
        }
      }
      if(tn)
        recurClassifyEdges(tn, reverse, lines, touched, newEdges);
    }

  }
}

static void class_color_cb(Fl_Widget* w, void* data)
{
  classificationEditor *e = (classificationEditor*)data;
  std::map<MLine*, GEdge*, compareMLinePtr> lines;
  {
    GModel::eiter it =  GModel::current()->firstEdge();
    GModel::eiter ite = GModel::current()->lastEdge();
    for( ; it != ite; ++it){
      for(unsigned int i = 0; i < (*it)->lines.size();i++) 
        lines[(*it)->lines[i]] = *it;
    }
  }

  std::list<MTri3*> tris;
  {
    std::set<GFace*>::iterator it = e->_faces.begin();
    while(it != e->_faces.end()){
      GFace *gf = *it;
      for(unsigned int i = 0; i < gf->triangles.size(); i++){
        tris.push_back(new MTri3(gf->triangles[i], 0));
      }
      gf->triangles.clear();
      ++it;
    }
  }

  if ( !tris.size() )return;


  connectTriangles(tris);

  {
    std::map<MTriangle*,GFace*> reverse;

    // color all triangles
    std::list<MTri3*> ::iterator it = tris.begin();
    while(it != tris.end()){
      if(!(*it)->isDeleted()){
        discreteFace *temporary = new discreteFace(GModel::current(), maxFaceNum() + 1);
        recurClassify(*it, temporary, lines, reverse);
        GModel::current()->add(temporary);
      }
      ++it;
    }
    
    // color some lines
    it = tris.begin();
    while(it != tris.end()){
      (*it)->setDeleted(false);
      ++it;
    }
    
    it = tris.begin();
    
    //classify edges that are bound by different GFaces
    //--------------------------------------------------
    std::map<std::pair<int, int>, GEdge*> newEdges;
    std::set<MLine*> touched;
    recurClassifyEdges(*it, reverse, lines, touched, newEdges);
    GModel::current()->remove(e->saved);

    //check if new edges should not be splitted 
    //splitted if composed of several open or closed edges
    //-----------------------------------------------------
    for (std::map<std::pair<int, int>, GEdge*>::iterator it = newEdges.begin() ; it != newEdges.end() ; ++it){

      GEdge *ge = it->second;
      printf("NEW edge with tag  = %d \n", ge->tag());

      std::list<MLine*> segments;
      for (unsigned int i = 0; i < ge->lines.size(); i++){
	segments.push_back(ge->lines[i]);
      }

      //for each actual GEdge
      while (!segments.empty()) {

	std::vector<MLine*> myLines;
	std::list<MLine*>::iterator it = segments.begin();

	MVertex *vB = (*it)->getVertex(0);
	MVertex *vE = (*it)->getVertex(1);
	myLines.push_back(*it);
	segments.erase(it);
	it++;

	//printf("***candidate mline %d %d of size %d \n", vB->getNum(), vE->getNum(), segments.size());

   	for (int i=0; i<2; i++) {

	  for (std::list<MLine*>::iterator it = segments.begin() ; it != segments.end(); ++it){	
	    MVertex *v1 = (*it)->getVertex(0);
	    MVertex *v2 = (*it)->getVertex(1);
	    //printf("mline %d %d \n", v1->getNum(), v2->getNum());
	    
	    std::list<MLine*>::iterator itp;
	    if ( v1 == vE  ){
	      //printf("->push back this mline \n");
	      myLines.push_back(*it);
	      itp = it;
	      it++;
	      segments.erase(itp);
	      vE = v2;
	      i = -1;
	    }
	    else if ( v2 == vE){
	      //printf("->push back this mline \n");
	      myLines.push_back(*it);
	      itp = it;
	      it++;
	      segments.erase(itp);
	      vE = v1;
	      i=-1;
	    }

	    if (it == segments.end()) break;

	  }

	  if (vB == vE) break;

	  if (segments.empty()) break;

	  //printf("not found VB=%d vE=%d\n", vB->getNum(), vE->getNum());
	  MVertex *temp = vB;
	  vB = vE;
	  vE = temp;
	  //printf("not found VB=%d vE=%d\n", vB->getNum(), vE->getNum());

	}
	
//  	printf("************ CANDIDATE NEW EDGE \n");
//  	for (std::vector<MLine*>::iterator it = myLines.begin() ; it != myLines.end() ; ++it){
//  	  MVertex *v1 = (*it)->getVertex(0);
//  	  MVertex *v2 = (*it)->getVertex(1);
//  	  printf("Line %d %d \n", v1->getNum(), v2->getNum());
//  	}
	GEdge *newGe = new discreteEdge(GModel::current(), maxEdgeNum() + 1, 0, 0);
	newGe->lines.insert(newGe->lines.end(), myLines.begin(), myLines.end());
	GModel::current()->add(newGe);
	printf("create new edge with tag =%d\n", maxEdgeNum());
	
      }//end for each actual GEdge

    }

    printf("end new edge with tag \n");

    for (std::map<std::pair<int, int>, GEdge*>::iterator it = newEdges.begin() ; it != newEdges.end() ; ++it){
      GEdge *ge = it->second;
      GModel::current()->remove(ge);
    }



    while(it != tris.end()){
      delete *it;
      ++it;
    }
  }
  
  printf("before drawing \n");
  
  CTX::instance()->mesh.changed = ENT_ALL;
  Draw();  
  Msg::StatusBar(3, false, "");
}

static void updateedges_cb(Fl_Widget* w, void* data)
{
  classificationEditor *e = (classificationEditor*)data;
 
  printf("%d inside edges detected\n", (int)e->edges_detected.size());

  for(unsigned int i = 0; i < e->temporary->lines.size(); i++){
    delete e->temporary->lines[i];
  }
  e->temporary->lines.clear();

  for(unsigned int i = 0; i < e->edges_detected.size(); i++){
    edge_angle ea =  e->edges_detected[i];
    // printf("angle = %g\n",ea.angle);
    if(ea.angle <= e->_inputs[CLASSVALUE_ANGLE]->value() / 180 * M_PI)
      break;
    e->temporary->lines.push_back(new MLine(ea.v1, ea.v2));
  } 

  printf("%d boundary edges detected\n", (int)e->edges_lonly.size());
  if(e->_togbuttons[CLASSTOGBUTTON_CLOS]->value()){
    for(unsigned int i = 0 ; i < e->edges_lonly.size(); i++){
      edge_angle ea = e->edges_lonly[i];
      e->temporary->lines.push_back(new MLine(ea.v1, ea.v2));
      //check if closed loop
    } 
  }
  
  CTX::instance()->mesh.changed = ENT_ALL;
  Draw();   
}

static void class_hide_cb(Fl_Widget *w, void *data)
{
  CTX::instance()->hideUnselected = !CTX::instance()->hideUnselected;
  CTX::instance()->mesh.changed = ENT_ALL;
  Draw();
}

static void buildListOfEdgeAngle(e2t_cont adj, std::vector<edge_angle> &edges_detected,
                                 std::vector<edge_angle> &edges_lonly)
{
  e2t_cont::iterator it = adj.begin();
  for(; it != adj.end(); ++it){
    if(it->second.second)
      edges_detected.push_back(edge_angle(it->first.getVertex(0), 
                                          it->first.getVertex(1), 
                                          it->second.first, it->second.second));
    else 
      edges_lonly.push_back(edge_angle(it->first.getVertex(0),
                                       it->first.getVertex(1), 
                                       it->second.first, it->second.second));
  }
  std::sort(edges_detected.begin(), edges_detected.end());
}

static void class_select_cb(Fl_Widget *w, void *data)
{
  classificationEditor *e = (classificationEditor*)data;
  std::vector<MTriangle*> &ele(e->getElements());

  CTX::instance()->pickElements = 1;

  while(1) {
    CTX::instance()->mesh.changed = ENT_ALL;
    Draw();

    Msg::StatusBar(3, false, "Select Elements\n"
		   "[Press 'e' to end selection or 'q' to abort]");
    
    char ib = GUI::instance()->selectEntity(ENT_ALL);
    if(ib == 'l') {
      if(CTX::instance()->pickElements){
        for(unsigned int i = 0; i < GUI::instance()->selectedElements.size(); i++){
          MElement *me = GUI::instance()->selectedElements[i];
          if(me->getNumEdges() == 3 && me->getVisibility() != 2){
            me->setVisibility(2); ele.push_back((MTriangle*)me);
          }
        }
      }
    }
    if(ib == 'r') {
      for(unsigned int i = 0; i < GUI::instance()->selectedElements.size(); i++)
        GUI::instance()->selectedElements[i]->setVisibility(1);
    }
    // ok, we compute edges !
    if(ib == 'e') {
      GModel::current()->setSelection(0);
      e2t_cont adj;
      buildEdgeToTriangle(ele, adj);
      buildListOfEdgeAngle(adj, e->edges_detected, e->edges_lonly);
      ElementsSelectedMode(e);
      break;
    }
    // do nothing
    if(ib == 'q') {
      GModel::current()->setSelection(0);
      ele.clear();
      break;
    }
  }
  
  updateedges_cb(0, data);

  CTX::instance()->mesh.changed = ENT_ALL;
  CTX::instance()->pickElements = 0;
  Draw();  
  Msg::StatusBar(3, false, "");
}

edge_angle::edge_angle(MVertex *_v1, MVertex *_v2, MElement *t1, MElement *t2)
  : v1(_v1), v2(_v2)
{
  if(!t2) angle = 0;
  else{
    double c1[3];
    double c2[3];
    double c3[3];
    {
      MVertex *p1 = t1->getVertex(0);
      MVertex *p2 = t1->getVertex(1);
      MVertex *p3 = t1->getVertex(2);
      double a[3] = {p1->x() - p2->x(), p1->y() - p2->y(), p1->z() - p2->z()};
      double b[3] = {p1->x() - p3->x(), p1->y() - p3->y(), p1->z() - p3->z()};
      c1[2] = a[0] * b[1] - a[1] * b[0];
      c1[1] = -a[0] * b[2] + a[2] * b[0];
      c1[0] = a[1] * b[2] - a[2] * b[1];
    }
    {
      MVertex *p1 = t2->getVertex(0);
      MVertex *p2 = t2->getVertex(1);
      MVertex *p3 = t2->getVertex(2);
      double a[3] = {p1->x() - p2->x(), p1->y() - p2->y(), p1->z() - p2->z()};
      double b[3] = {p1->x() - p3->x(), p1->y() - p3->y(), p1->z() - p3->z()};
      c2[2] = a[0] * b[1] - a[1] * b[0];
      c2[1] = -a[0] * b[2] + a[2] * b[0];
      c2[0] = a[1] * b[2] - a[2] * b[1];
    }
    norme(c1);
    norme(c2);
    prodve(c1, c2, c3);
    double cosa; prosca(c1, c2, &cosa);
    double sina = norme(c3);
    angle = atan2(sina, cosa);
  }
}

classificationEditor::classificationEditor()
{
  op[0] = opt_mesh_lines(0, GMSH_GET, 0.);
  op[1] = opt_mesh_surfaces_edges(0, GMSH_GET, 0.);
  op[2] = opt_mesh_surfaces_faces(0, GMSH_GET, 0.);
  op[3] = opt_mesh_line_width(0, GMSH_SET | GMSH_GET,0.);

  opt_mesh_lines(0, GMSH_SET | GMSH_GUI, 1);
  opt_mesh_surfaces_edges(0, GMSH_SET | GMSH_GUI, 0);
  opt_mesh_surfaces_faces(0, GMSH_SET | GMSH_GUI, 1);
  opt_mesh_line_width(0, GMSH_SET | GMSH_GUI, 1.5);

  // construct GUI in terms of standard sizes
  int BBB = (int)(1.4 * BB); // labels too long
  const int width = (int)(3.5 * BBB), height = 10 * BH;

  _window = new paletteWindow
    (width, height, CTX::instance()->nonModalWindows ? true : false, "Classify");
  
  new Fl_Tabs(WB, WB, width - 2 * WB, height - 2 * WB);
  {
    Fl_Group *o = new Fl_Group
      (WB, WB + BH, width - 2 * WB, height - 2 * WB - BH, "Edge Detection");
    edge_detec = o;
    
    _buttons[CLASSBUTTON_OK] = new Fl_Button 
      (4*WB+2*BBB, 7*WB+6*BH, BBB, BH, "OK");
    _buttons[CLASSBUTTON_OK]->callback(class_ok_cb, this);
    
    _buttons[CLASSBUTTON_SELECT] = new Fl_Button 
      (2*WB, 2*WB+1*BH, BBB, BH, "Select Elements");
    _buttons[CLASSBUTTON_SELECT]->callback(class_select_cb, this);
    
    _togbuttons[CLASSTOGBUTTON_HIDE] = new Fl_Toggle_Button
      (3*WB+BBB, 2*WB+1*BH, BBB, BH, "Hide Unselected");
    _togbuttons[CLASSTOGBUTTON_HIDE]->callback(class_hide_cb,this);

    _togbuttons[CLASSTOGBUTTON_CLOS] = new Fl_Toggle_Button
      (2*WB, 4*WB+3*BH, BBB, BH, "Include Closure");
    _togbuttons[CLASSTOGBUTTON_CLOS]->callback(updateedges_cb,this);
    
    _inputs[CLASSVALUE_ANGLE] = new Fl_Value_Input
      (3*WB+BBB, 4*WB+3*BH, BBB, BH, "Treshold Angle");
    _inputs[CLASSVALUE_ANGLE]->value(40);
    _inputs [CLASSVALUE_ANGLE]->maximum(90);
    _inputs[CLASSVALUE_ANGLE]->minimum(0);
    _inputs[CLASSVALUE_ANGLE]->align(FL_ALIGN_RIGHT);
    _inputs[CLASSVALUE_ANGLE]->step(1);
    _inputs[CLASSVALUE_ANGLE]->when(FL_WHEN_RELEASE);  
    _inputs[CLASSVALUE_ANGLE]->callback(updateedges_cb,this);

    _buttons[CLASSBUTTON_DEL] = new Fl_Button 
      (2*WB, 5*WB+4*BH, BBB, BH, "Delete Edge");
    _buttons[CLASSBUTTON_DEL]->callback(class_deleteedge_cb, this);    
    _buttons[CLASSBUTTON_DEL]->deactivate();

    _buttons[CLASSBUTTON_ADD] = new Fl_Button
      (2*WB, 6*WB+5*BH, BBB, BH, "Save Selection");
    _buttons[CLASSBUTTON_ADD]->callback(class_save_cb, this);    
    _buttons[CLASSBUTTON_ADD]->deactivate();

    _buttons[CLASSBUTTON_CLEAR] = new Fl_Button 
      (2*WB, 7*WB+6*BH, BBB, BH, "Clear Selection");
    _buttons[CLASSBUTTON_CLEAR]->callback(class_clear_cb, this);    
    _buttons[CLASSBUTTON_CLEAR]->deactivate();
    o->end();
  }
  {
    Fl_Group *o = new Fl_Group
      (WB, WB + BH, width - 2 * WB, height - 2 * WB - BH, "Face Colouring");
    face_color = o;
    o->deactivate();
    o->hide();
    _buttons[CLASSBUTTON_SELFAC] = new Fl_Button
      (2*WB, 2*WB+1*BH, BBB, BH, "Select Model Face");
    _buttons[CLASSBUTTON_SELFAC]->callback(class_selectgface_cb, this);
    _buttons[CLASSBUTTON_COLOR] = new Fl_Button 
      (2*WB, 3*WB+2*BH, BBB, BH, "Classify Mesh Faces");
    _buttons[CLASSBUTTON_COLOR]->callback(class_color_cb, this);
    o->end();
  }
  {
    Fl_Group *o = new Fl_Group
      (WB, WB + BH, width - 2 * WB, height - 2 * WB - BH, "Reparametrize Surfaces");
    reverse_eng = o;
    o->hide();
    o->deactivate();
    o->end();
  }
  NoElementsSelectedMode(this);

  // allocate detected edges
  // temporary for the selection
  // saved for the ones that have been saved by the user
  // and that will be used for next step

  temporary = new discreteEdge(GModel::current(), maxEdgeNum() + 1, 0, 0);
  GModel::current()->add(temporary);
  saved = new discreteEdge(GModel::current(), maxEdgeNum() + 1, 0, 0);
  GModel::current()->add(saved);
  
  _window->end();
  _window->hotspot(_window);
  _window->size_range(width, (int)(0.85 * height));    
}

void mesh_classify_cb(Fl_Widget* w, void* data)
{
  // create the (static) editor
  static classificationEditor *editor = 0;
  if(!editor){
    editor = new classificationEditor();
  }
  editor->show();
}