// Gmsh - Copyright (C) 1997-2010 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_Box.H> #include "FlGui.h" #include "classificationEditor.h" #include "paletteWindow.h" #include "Numeric.h" #include "drawContext.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" 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); } } struct compareMLinePtr { bool operator () (MLine *l1, MLine *l2) const { static Less_Edge le; return le(l1->getEdge(0), l2->getEdge(0)); } }; 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 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 *ge = new discreteEdge (GModel::current(), GModel::current()->maxEdgeNum() + 1, 0, 0); GModel::current()->add(ge); newEdges[std::make_pair<int, int>(i1, i2)] = ge; return ge; } 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 NoElementsSelectedMode(classificationEditor *e) { e->buttons[CLASS_BUTTON_SELECT_ELEMENTS]->activate(); e->buttons[CLASS_BUTTON_SELECT_ALL_ELEMENTS]->activate(); e->buttons[CLASS_BUTTON_DELETE_FROM_SELECTION]->deactivate(); e->buttons[CLASS_BUTTON_RESET_SELECTION]->deactivate(); e->toggles[CLASS_TOGGLE_BOUNDARY]->deactivate(); e->inputs[CLASS_VALUE_ANGLE]->deactivate(); e->buttons[CLASS_BUTTON_CLASSIFY]->deactivate(); CTX::instance()->mesh.changed = ENT_ALL; CTX::instance()->pickElements = 0; drawContext::global()->draw(); Msg::StatusBar(3, false, ""); } static void ElementsSelectedMode(classificationEditor *e) { e->buttons[CLASS_BUTTON_DELETE_FROM_SELECTION]->activate(); e->buttons[CLASS_BUTTON_RESET_SELECTION]->activate(); e->toggles[CLASS_TOGGLE_BOUNDARY]->activate(); e->inputs[CLASS_VALUE_ANGLE]->activate(); e->buttons[CLASS_BUTTON_SELECT_ELEMENTS]->deactivate(); e->buttons[CLASS_BUTTON_SELECT_ALL_ELEMENTS]->deactivate(); } static void update_edges_cb(Fl_Widget *w, void *data) { classificationEditor *e = (classificationEditor*)data; if(!e->selected) return; printf("%d inside edges detected\n", (int)e->edges_detected.size()); for(unsigned int i = 0; i < e->selected->lines.size(); i++) delete e->selected->lines[i]; e->selected->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[CLASS_VALUE_ANGLE]->value() / 180 * M_PI) break; e->selected->lines.push_back(new MLine(ea.v1, ea.v2)); } printf("%d boundary edges detected\n", (int)e->edges_lonly.size()); if(e->toggles[CLASS_TOGGLE_BOUNDARY]->value()){ for(unsigned int i = 0 ; i < e->edges_lonly.size(); i++){ edge_angle ea = e->edges_lonly[i]; e->selected->lines.push_back(new MLine(ea.v1, ea.v2)); //check if closed loop } } CTX::instance()->mesh.changed = ENT_ALL; drawContext::global()->draw(); } static void class_select_elements_cb(Fl_Widget *w, void *data) { classificationEditor *e = (classificationEditor*)data; std::vector<MTriangle*> &ele(e->elements); // allocate discrete edge to hold the selected mesh segments if(!e->selected){ e->selected = new discreteEdge (GModel::current(), GModel::current()->maxEdgeNum() + 1, 0, 0); GModel::current()->add(e->selected); } CTX::instance()->pickElements = 1; while(1) { CTX::instance()->mesh.changed = ENT_ALL; drawContext::global()->draw(); Msg::StatusBar(3, false, "Select elements\n" "[Press 'e' to end selection or 'q' to abort]"); char ib = FlGui::instance()->selectEntity(ENT_ALL); if(ib == 'l') { for(unsigned int i = 0; i < FlGui::instance()->selectedElements.size(); i++){ MElement *me = FlGui::instance()->selectedElements[i]; if(me->getType() == TYPE_TRI && me->getVisibility() != 2){ me->setVisibility(2); ele.push_back((MTriangle*)me); } } } if(ib == 'r') { for(unsigned int i = 0; i < FlGui::instance()->selectedElements.size(); i++) FlGui::instance()->selectedElements[i]->setVisibility(1); } if(ib == 'e') { // ok, compute the edges GModel::current()->setSelection(0); e2t_cont adj; buildEdgeToTriangle(ele, adj); buildListOfEdgeAngle(adj, e->edges_detected, e->edges_lonly); ElementsSelectedMode(e); break; } if(ib == 'q') { // do nothing GModel::current()->setSelection(0); ele.clear(); break; } } update_edges_cb(0, data); CTX::instance()->mesh.changed = ENT_ALL; CTX::instance()->pickElements = 0; drawContext::global()->draw(); Msg::StatusBar(3, false, ""); } static void class_select_all_elements_cb(Fl_Widget *w, void *data) { classificationEditor *e = (classificationEditor*)data; // allocate discrete edge to hold the selected mesh segments if(!e->selected){ e->selected = new discreteEdge (GModel::current(), GModel::current()->maxEdgeNum() + 1, 0, 0); GModel::current()->add(e->selected); } for(GModel::fiter it = GModel::current()->firstFace(); it != GModel::current()->lastFace(); ++it) e->elements.insert(e->elements.end(), (*it)->triangles.begin(), (*it)->triangles.end()); e2t_cont adj; buildEdgeToTriangle(e->elements, adj); buildListOfEdgeAngle(adj, e->edges_detected, e->edges_lonly); ElementsSelectedMode(e); update_edges_cb(0, data); CTX::instance()->mesh.changed = ENT_ALL; CTX::instance()->pickElements = 0; drawContext::global()->draw(); Msg::StatusBar(3, false, ""); } static void class_hide_cb(Fl_Widget *w, void *data) { CTX::instance()->hideUnselected = !CTX::instance()->hideUnselected; CTX::instance()->mesh.changed = ENT_ALL; drawContext::global()->draw(); } static void show_only_edges_cb(Fl_Widget *w, void *data) { classificationEditor *e = (classificationEditor*)data; static int old_sf = opt_mesh_surfaces_faces(0, GMSH_GET, 0.); static int old_se = opt_mesh_surfaces_edges(0, GMSH_GET, 0.); if(e->toggles[CLASS_TOGGLE_SHOW_ONLY_EDGES]->value()){ opt_mesh_lines(0, GMSH_SET | GMSH_GUI, 1.); opt_mesh_surfaces_faces(0, GMSH_SET | GMSH_GUI, 0.); opt_mesh_surfaces_edges(0, GMSH_SET | GMSH_GUI, 0.); } else{ opt_mesh_surfaces_faces(0, GMSH_SET | GMSH_GUI, old_sf); opt_mesh_surfaces_edges(0, GMSH_SET | GMSH_GUI, old_se); } drawContext::global()->draw(); } static void class_delete_edge_cb(Fl_Widget *w, void *data) { classificationEditor *e = (classificationEditor*)data; if(!e->selected) return; CTX::instance()->pickElements = 1; std::vector<MLine*> ele; while(1) { CTX::instance()->mesh.changed = ENT_ALL; drawContext::global()->draw(); Msg::StatusBar(3, false, "Select elements\n" "[Press 'e' to end selection or 'q' to abort]"); char ib = FlGui::instance()->selectEntity(ENT_ALL); if(ib == 'l') { for(unsigned int i = 0; i < FlGui::instance()->selectedElements.size(); i++){ MElement *me = FlGui::instance()->selectedElements[i]; if(me->getType() == TYPE_LIN && me->getVisibility() != 2){ me->setVisibility(2); ele.push_back((MLine*)me); } } } if(ib == 'r') { for(unsigned int i = 0; i < FlGui::instance()->selectedElements.size(); i++) FlGui::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 selected edges if a deleted one is present and delete it std::vector<MLine*> temp = e->selected->lines; e->selected->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->selected->lines.push_back(temp[i]); } CTX::instance()->mesh.changed = ENT_ALL; CTX::instance()->pickElements = 0; drawContext::global()->draw(); Msg::StatusBar(3, false, ""); e->elements.clear(); e->edges_detected.clear(); } static void class_reset_selection_cb(Fl_Widget *w, void *data) { classificationEditor *e = (classificationEditor*)data; if(!e->selected) return; for(unsigned int i = 0; i < e->selected->lines.size(); i++) delete e->selected->lines[i]; e->selected->lines.clear(); e->selected->deleteVertexArrays(); e->elements.clear(); e->edges_detected.clear(); NoElementsSelectedMode(e); } static void class_select_surfaces_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; drawContext::global()->draw(); Msg::StatusBar(3, false, "Select Surface\n" "[Press 'e' to end selection or 'q' to abort]"); char ib = FlGui::instance()->selectEntity(ENT_SURFACE); if(ib == 'l') { for(unsigned int i = 0; i < FlGui::instance()->selectedFaces.size(); i++){ FlGui::instance()->selectedFaces[i]->setSelection(1); temp.push_back(FlGui::instance()->selectedFaces[i]); } } if(ib == 'e') { // store the list of gfaces GModel::current()->setSelection(0); for(unsigned int i = 0; i < temp.size(); i++){ e->faces.insert(temp[i]); } break; } if(ib == 'q') { // do nothing GModel::current()->setSelection(0); break; } } if(e->faces.size()) e->buttons[CLASS_BUTTON_CLASSIFY]->activate(); CTX::instance()->mesh.changed = ENT_ALL; drawContext::global()->draw(); Msg::StatusBar(3, false, ""); } static void class_select_all_surfaces_cb(Fl_Widget *w, void *data) { classificationEditor *e = (classificationEditor*)data; for(GModel::fiter it = GModel::current()->firstFace(); it != GModel::current()->lastFace(); ++it) e->faces.insert(*it); if(e->faces.size()) e->buttons[CLASS_BUTTON_CLASSIFY]->activate(); CTX::instance()->mesh.changed = ENT_ALL; drawContext::global()->draw(); Msg::StatusBar(3, false, ""); } static void class_classify_cb(Fl_Widget *w, void *data) { classificationEditor *e = (classificationEditor*)data; std::map<MLine*, GEdge*, compareMLinePtr> lines; for(GModel::eiter it = GModel::current()->firstEdge(); it != GModel::current()->lastEdge(); ++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.empty()) 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 *gf = new discreteFace (GModel::current(), GModel::current()->maxFaceNum() + 1); recurClassify(*it, gf, lines, reverse); GModel::current()->add(gf); } ++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); // 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; 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()); } GEdge *newGe = new discreteEdge (GModel::current(), GModel::current()->maxEdgeNum() + 1, 0, 0); newGe->lines.insert(newGe->lines.end(), myLines.begin(), myLines.end()); GModel::current()->add(newGe); } //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; } // remove selected, but do not delete its elements if(e->selected){ GModel::current()->remove(e->selected); e->selected->lines.clear(); delete e->selected; e->selected = 0; } e->elements.clear(); e->edges_detected.clear(); NoElementsSelectedMode(e); } classificationEditor::classificationEditor() : selected(0) { opt_mesh_lines(0, GMSH_SET | GMSH_GUI, 1.); drawContext::global()->draw(); int BBB = (int)(1.4 * BB); const int width = (int)(3.15 * BBB), height = (int)(9.5 * BH); window = new paletteWindow (width, height, CTX::instance()->nonModalWindows ? true : false, "Reclassify"); window->box(GMSH_WINDOW_BOX); int x = WB, y = WB; { Fl_Box *b = new Fl_Box (x, y, width, BH, "1. Select mesh elements on which to perform edge detection"); b->align(FL_ALIGN_LEFT | FL_ALIGN_INSIDE); x += WB; y += BH; buttons[CLASS_BUTTON_SELECT_ELEMENTS] = new Fl_Button (x, y, BBB, BH, "Select elements"); buttons[CLASS_BUTTON_SELECT_ELEMENTS]->callback(class_select_elements_cb, this); buttons[CLASS_BUTTON_SELECT_ALL_ELEMENTS] = new Fl_Button (x + BBB + WB, y, (int)(0.5 * BBB) - WB, BH, "All"); buttons[CLASS_BUTTON_SELECT_ALL_ELEMENTS]->callback(class_select_all_elements_cb, this); toggles[CLASS_TOGGLE_HIDE] = new Fl_Check_Button (x + 1.5 * BBB + WB, y, width - 1.5 * BBB - x - 2 * WB, BH, "Hide unselected elements"); toggles[CLASS_TOGGLE_HIDE]->type(FL_TOGGLE_BUTTON); toggles[CLASS_TOGGLE_HIDE]->callback(class_hide_cb, this); x -= WB; } { y += BH / 2; Fl_Box* b = new Fl_Box(x, y + BH - WB, width - 2 * WB, 2); b->box(FL_ENGRAVED_FRAME); b->labeltype(FL_NO_LABEL); } { y += BH; Fl_Box *b = new Fl_Box (x, y, width, BH, "2. Fine-tune edge selection"); b->align(FL_ALIGN_LEFT | FL_ALIGN_INSIDE); x += WB; y += BH; inputs[CLASS_VALUE_ANGLE] = new Fl_Value_Input (x, y, 2 * BBB / 3, BH, "Threshold angle"); inputs[CLASS_VALUE_ANGLE]->value(40); inputs[CLASS_VALUE_ANGLE]->maximum(180); inputs[CLASS_VALUE_ANGLE]->minimum(0); inputs[CLASS_VALUE_ANGLE]->align(FL_ALIGN_RIGHT); inputs[CLASS_VALUE_ANGLE]->step(1); inputs[CLASS_VALUE_ANGLE]->when(FL_WHEN_RELEASE); inputs[CLASS_VALUE_ANGLE]->callback(update_edges_cb, this); toggles[CLASS_TOGGLE_SHOW_ONLY_EDGES] = new Fl_Check_Button (x + 1.5 * BBB + WB, y, width - x - 1.5 * BBB - 2 * WB, BH, "Show only edges"); toggles[CLASS_TOGGLE_SHOW_ONLY_EDGES]->type(FL_TOGGLE_BUTTON); toggles[CLASS_TOGGLE_SHOW_ONLY_EDGES]->callback(show_only_edges_cb, this); y += BH; toggles[CLASS_TOGGLE_BOUNDARY] = new Fl_Check_Button (x, y, width - x - 2 * WB, BH, "Include edges on boundary (closure)"); toggles[CLASS_TOGGLE_BOUNDARY]->type(FL_TOGGLE_BUTTON); toggles[CLASS_TOGGLE_BOUNDARY]->callback(update_edges_cb, this); y += BH; buttons[CLASS_BUTTON_DELETE_FROM_SELECTION] = new Fl_Button (x, y, 1.5 * BBB, BH, "Delete edges from selection"); buttons[CLASS_BUTTON_DELETE_FROM_SELECTION]->callback(class_delete_edge_cb, this); buttons[CLASS_BUTTON_DELETE_FROM_SELECTION]->deactivate(); buttons[CLASS_BUTTON_RESET_SELECTION] = new Fl_Button (x + 1.5 * BBB + WB, y, BBB, BH, "Reset selection"); buttons[CLASS_BUTTON_RESET_SELECTION]->callback(class_reset_selection_cb, this); buttons[CLASS_BUTTON_RESET_SELECTION]->deactivate(); x -= WB; } { y += BH / 2; Fl_Box* b = new Fl_Box(x, y + BH - WB, width - 2 * WB, 2); b->box(FL_ENGRAVED_FRAME); b->labeltype(FL_NO_LABEL); } { y += BH; Fl_Box *b = new Fl_Box (x, y, width, BH, "3. Reclassify surfaces using selected edges"); b->align(FL_ALIGN_LEFT | FL_ALIGN_INSIDE); x += WB; y += BH; buttons[CLASS_BUTTON_SELECT_SURFACES] = new Fl_Button (x, y, BBB, BH, "Select surfaces"); buttons[CLASS_BUTTON_SELECT_SURFACES]->callback(class_select_surfaces_cb, this); buttons[CLASS_BUTTON_SELECT_ALL_SURFACES] = new Fl_Button (x + BBB + WB, y, (int)(0.5 * BBB) - WB, BH, "All"); buttons[CLASS_BUTTON_SELECT_ALL_SURFACES]->callback(class_select_all_surfaces_cb, this); buttons[CLASS_BUTTON_CLASSIFY] = new Fl_Button (x + 1.5 * BBB + WB, y, BBB, BH, "Reclassify"); buttons[CLASS_BUTTON_CLASSIFY]->callback(class_classify_cb, this); buttons[CLASS_BUTTON_CLASSIFY]->deactivate(); x -= WB; } window->end(); window->hotspot(window); NoElementsSelectedMode(this); } void mesh_classify_cb(Fl_Widget* w, void* data) { // create the (static) editor static classificationEditor *editor = 0; if(!editor) editor = new classificationEditor(); editor->show(); }