Commit 662997db authored by promayon's avatar promayon
Browse files

NEW tetrahedralize action now independant from MML/SOFA

NEW tetrahedralize action dispatched in 3 actions : clean mesh, fill with points and tetrahedralize per se
NEW VtkMeshUtil method to tranform any vtkPointSet (e.g., returned by a component->getPointSet()) to a vtkPolyData
FIXED Bug (application crash) when max number of recent document was reached
FIXED directory name for mesh actions

git-svn-id: svn+ssh://scm.forge.imag.fr/var/lib/gforge/chroot/scmrepos/svn/camitk/trunk/camitk@138 ec899d31-69d1-42ba-9299-647d76f65fb3
parent 858c36cc
action_extension(DEFAULT
NEEDS_COMPONENT_EXTENSION vtkmesh
DEFINES COMPILE_DECIMATION_ACTION_API)
\ No newline at end of file
# for building tetgen lib
add_subdirectory(tetgen1.4.3)
action_extension(DEFAULT
INCLUDE_DIRECTORIES tetgen1.4.3
LIBRARIES tetgen
NEEDS_COMPONENT_EXTENSION vtkmesh
DEFINES COMPILE_MESHPROCESSING_ACTION_API TETLIBRARY)
......@@ -27,24 +27,29 @@ $CAMITK_LICENCE_END$
#include <Application.h>
#include <MeshComponent.h>
#include <Log.h>
#include <VtkMeshUtil.h>
#include <vtkCleanPolyData.h>
#include <vtkCallbackCommand.h>
#include <QMessageBox>
#include <QFileInfo>
using namespace camitk;
// -------------------- CleanPolyData --------------------
CleanPolyData::CleanPolyData(ActionExtension* extension) : Action(extension) {
this->setName("Clean Mesh");
this->setDescription("Merge duplicate points, and/or remove unused points and/or remove degenerate cells ");
this->setComponent("MeshComponent");
this->setFamily("Mesh Processing");
this->addTag("vtkCleanPolyData");
this->addTag("Clean vtkPolyData");
// Setting name, description and input component
setName("Clean Mesh");
setDescription("Merge duplicate points, and/or remove unused points and/or remove degenerate cells.");
setComponent("MeshComponent");
// Setting classification family and tags
setFamily("Mesh Processing");
addTag("vtkCleanPolyData");
addTag("Clean vtkPolyData");
setProperty("Tolerance", QVariant(1.0)); // Tolerance is given in percentage of the bounding box, if points are closer than 1.0% of the bounding box dimensions, then they are merged
}
......@@ -57,15 +62,18 @@ Action::ApplyStatus CleanPolyData::apply() {
Application::showStatusBarMessage("Cleaning poly data...");
Application::resetProgressBar();
MeshComponent *targetMesh = getTargets().last();
// use the last target
MeshComponent *targetMesh = dynamic_cast<MeshComponent*>(getTargets().last());
//-- check if this is a polydata and if this is an unstructured grid, extract the surface
vtkSmartPointer<vtkPolyData> targetPolyData = VtkMeshUtil::vtkPointSetToVtkPolyData(targetMesh->getPointSet());
//-- clean the polydata
// remove redundant points
int tolerance = 1; // TODO ask the user
vtkSmartPointer<vtkCleanPolyData> cleaner = vtkSmartPointer<vtkCleanPolyData>::New();
cleaner->AddInput ( targetMesh->getPointSet() );
cleaner->AddInput ( targetPolyData );
cleaner->PointMergingOn();
cleaner->SetTolerance ( double ( tolerance ) *0.01 ); // % of the Bounding Box
cleaner->SetTolerance ( property("Tolerance").toDouble() *0.01 ); // % of the Bounding Box
cleaner->ConvertLinesToPointsOn ();
cleaner->ConvertPolysToLinesOn ();
cleaner->ConvertStripsToPolysOn ();
......@@ -80,8 +88,7 @@ Action::ApplyStatus CleanPolyData::apply() {
// Create a mesh Component
vtkSmartPointer<vtkPointSet> resultPointSet = cleaner->GetOutput();
MeshComponent * result = new MeshComponent(resultPointSet, targetMesh->getName() + " clean");
Application::refresh();
Application::refresh();
// restore the normal cursor and progress bar
Application::resetProgressBar();
......
......@@ -39,12 +39,9 @@ public:
/// the destructor
virtual ~CleanPolyData() {};
/// no widget yet
virtual QWidget * getWidget() { return NULL; }
public slots:
/// method applied when the action is called
/// method called when the action is applied
virtual ApplyStatus apply();
};
......
......@@ -42,7 +42,7 @@ Decimation::Decimation(ActionExtension *extension) : Action(extension){
setName("Decimation");
setDescription("Decimate mesh data");
setComponent("MeshComponent");
setFamily("Decimation");
setFamily("Mesh Processing");
addTag("Decimation");
addTag("Simplify");
......@@ -83,6 +83,11 @@ QWidget * Decimation::getWidget() {
// -------------------- apply --------------------
Action::ApplyStatus Decimation::apply() {
// set waiting cursor and status bar
QApplication::setOverrideCursor ( QCursor ( Qt::WaitCursor ) );
Application::showStatusBarMessage("Performing Decimation");
Application::resetProgressBar();
MeshComponent * targetMesh = dynamic_cast<MeshComponent*>(getTargets().last());
vtkSmartPointer<vtkDecimatePro> mcDecimate = vtkSmartPointer<vtkDecimatePro>::New();
......@@ -101,8 +106,6 @@ Action::ApplyStatus Decimation::apply() {
mcDecimate->SetInflectionPointRatio ( property("inflectionRatio").toDouble() );
}
Application::showStatusBarMessage("Performing Decimation");
Application::resetProgressBar();
vtkSmartPointer<vtkCallbackCommand> progressCallback = vtkSmartPointer<vtkCallbackCommand>::New();
progressCallback->SetCallback(&Application::vtkProgressFunction);
mcDecimate->AddObserver(vtkCommand::ProgressEvent, progressCallback);
......
/*****************************************************************************
$CAMITK_LICENCE_BEGIN$
CamiTK - Computer Assisted Medical Intervention ToolKit
Visit http://camitk.imag.fr for more information
Copyright (C) 2012 Celine Fouard, Emmanuel Promayon, Yannick Keraval
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301 USA
$CAMITK_LICENCE_END$
*****************************************************************************/
#include "FillWithPoints.h"
#include <Application.h>
#include <MeshComponent.h>
#include <Log.h>
#include <VtkMeshUtil.h>
#include <vtkPointLocator.h>
#include <vtkCallbackCommand.h>
#include <vtkPolyData.h>
#include <vtkSelectEnclosedPoints.h>
#include <vtkAppendPolyData.h>
#include <QDateTime>
// -------------------- FillWithPoints --------------------
FillWithPoints::FillWithPoints(ActionExtension* extension) : Action(extension) {
setName("Fill With Points");
setDescription("Fill a surfacic mesh with regularly spaced nodes (create new nodes inside the mesh).");
setComponent("MeshComponent");
setFamily("Mesh Processing");
addTag("Add Nodes");
setProperty("Bucket Size", QVariant(1)); // nv of points per buckets, higher = coarser filling, 1 = highest density possible
setProperty("Randomize", QVariant(true)); // randomize the position of the point +/- 0.5
}
// --------------- apply -------------------
Action::ApplyStatus FillWithPoints::apply() {
//CAMITK_INFO("FillWithPoints", "apply", "Adding inside nodes to " << getTargets().last()->getName().toStdString());
// set waiting cursor and status bar
QApplication::setOverrideCursor ( QCursor ( Qt::WaitCursor ) );
Application::showStatusBarMessage("Filling With Nodes...");
Application::resetProgressBar();
// use the last target
MeshComponent *targetMesh = dynamic_cast<MeshComponent*>(getTargets().last());
//-- check if this is a polydata and if this is an unstructured grid, extract the surface
vtkSmartPointer<vtkPolyData> targetPolyData = VtkMeshUtil::vtkPointSetToVtkPolyData(targetMesh->getPointSet());
//CAMITK_INFO("FillWithPoints", "apply", "polydata: points:" << targetPolyData->GetNumberOfPoints() << " , cells:" << targetPolyData->GetNumberOfCells());
//-- computes the subdivision depending on the mesh resolution
vtkSmartPointer<vtkPointLocator> subdivisionLocator = vtkSmartPointer<vtkPointLocator>::New();
subdivisionLocator->SetDataSet ( targetPolyData );
subdivisionLocator->SetNumberOfPointsPerBucket(property("Bucket Size").toInt());
subdivisionLocator->BuildLocator();
// number of subdivisions for the x, y and z axis. If the mesh density is high the nbDiv will be high
// nbDiv is computed so that there is bucketSize nodes per division
int nbDiv[3];
subdivisionLocator->GetDivisions(nbDiv);
//CAMITK_INFO("FillWithPoints", "apply", "Nb divisions: " << nbDiv[0] << "," << nbDiv[1] << "," << nbDiv[2]);
// length of the divisions in the x-axis, y-axis and z-axis
double *bounds = targetPolyData->GetPoints()->GetBounds(); // [xmin,xmax, ymin,ymax, zmin,zmax]
double xDiv = ( bounds[1] - bounds[0] ) / nbDiv[0];
double yDiv = ( bounds[3] - bounds[2] ) / nbDiv[1];
double zDiv = ( bounds[5] - bounds[4] ) / nbDiv[2];
//-- generates a regular grid in the bounding box
vtkSmartPointer<vtkPoints> points = vtkSmartPointer<vtkPoints>::New();
// position for generated points
double x, y, z;
if (property("Randomize").toBool())
qsrand ( QDateTime::currentDateTime().toTime_t() );
for ( int i = 0; i <= nbDiv[0]; i++ ) {
x = bounds[0] + i * xDiv;
for ( int j = 0; j <= nbDiv[1]; j++ ) {
y = bounds[2] + j * yDiv;
for ( int k = 0; k <= nbDiv[2]; k++ ) {
z = bounds[4] + k * zDiv;
// randomize
if (property("Randomize").toBool()) {
x += ( xDiv / 10.0 ) * ( 0.5 - double ( qrand() ) / ( double ( RAND_MAX ) + 1.0 ) );
y += ( yDiv / 10.0 ) * ( 0.5 - double ( qrand() ) / ( double ( RAND_MAX ) + 1.0 ) );
z += ( zDiv / 10.0 ) * ( 0.5 - double ( qrand() ) / ( double ( RAND_MAX ) + 1.0 ) );
}
//CAMITK_INFO("FillWithPoints", "apply", "insert point: (" << x << "," << y << "," << z << ")");
points->InsertNextPoint ( x, y, z );
}
}
}
//-- select internal points only
vtkSmartPointer<vtkPolyData> gridPoints = vtkSmartPointer<vtkPolyData>::New();
gridPoints->SetPoints ( points );
vtkSmartPointer<vtkSelectEnclosedPoints> select = vtkSmartPointer<vtkSelectEnclosedPoints>::New();
select->SetInput ( gridPoints );
select->SetSurface ( targetPolyData );
select->Update();
//-- create a new polydata that contains only the internal selected points
vtkSmartPointer<vtkPoints> insidePointsPoints = vtkSmartPointer<vtkPoints>::New();
for ( int i = 0; i < gridPoints->GetPoints()->GetNumberOfPoints(); i++ ) {
if ( select->IsInside ( i ) ) {
insidePointsPoints->InsertNextPoint ( gridPoints->GetPoints()->GetPoint ( i ) );
}
}
vtkSmartPointer<vtkPolyData> insidePoints = vtkSmartPointer<vtkPolyData>::New();
insidePoints->SetPoints ( insidePointsPoints );
insidePoints->Allocate ( 2 );
vtkIdType *vtkPointIndex = new vtkIdType [insidePointsPoints->GetNumberOfPoints()];
for (vtkIdType i=0; i<insidePointsPoints->GetNumberOfPoints(); i++)
vtkPointIndex[i] = i;
insidePoints->InsertNextCell(VTK_POLY_VERTEX, insidePointsPoints->GetNumberOfPoints(),vtkPointIndex);
insidePoints->Update();
//-- Generates one structure with all the points (append targetPolyData and insidePoints)
vtkSmartPointer<vtkAppendPolyData> appender = vtkSmartPointer<vtkAppendPolyData>::New();
appender->AddInput ( targetPolyData );
appender->AddInput ( insidePoints );
appender->Update();
//CAMITK_INFO("FillWithPoints", "apply", "insidePointsPoints: points:" << insidePointsPoints->GetNumberOfPoints());
//CAMITK_INFO("FillWithPoints", "apply", "targetPolyData: points:" << targetPolyData->GetNumberOfPoints() << " , cells:" << targetPolyData->GetNumberOfCells());
//-- Create a new mesh Component using an unstructured grid (to show the isolated nodes)
vtkSmartPointer<vtkPointSet> resultPointSet = appender->GetOutput();
//CAMITK_INFO("FillWithPoints", "apply", "resultPointSet: points:" << resultPointSet->GetNumberOfPoints() << " , cells:" << resultPointSet->GetNumberOfCells());
MeshComponent * result = new MeshComponent(resultPointSet, targetMesh->getName() + " filled");
Application::refresh();
// restore the normal cursor and progress bar
Application::resetProgressBar();
QApplication::restoreOverrideCursor();
return SUCCESS;
}
/*****************************************************************************
$CAMITK_LICENCE_BEGIN$
CamiTK - Computer Assisted Medical Intervention ToolKit
Visit http://camitk.imag.fr for more information
Copyright (C) 2012 Celine Fouard, Emmanuel Promayon, Yannick Keraval
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301 USA
$CAMITK_LICENCE_END$
*****************************************************************************/
#ifndef FILLWITHPOINTS_H
#define FILLWITHPOINTS_H
#include <Action.h>
using namespace camitk;
/** Fill a surfacic mesh with regularly spaced nodes (create new nodes inside the mesh).
* Use the default widget.
*/
class FillWithPoints : public Action {
public:
/// the constructor
FillWithPoints(ActionExtension* extension);
/// the destructor
virtual ~FillWithPoints() {};
public slots:
/// method called when the action is applied
virtual ApplyStatus apply();
};
#endif // FILLWITHPOINTS_H
......@@ -22,17 +22,23 @@ Boston, MA 02110-1301 USA
$CAMITK_LICENCE_END$
*****************************************************************************/
#include "DecimationExtension.h"
#include "MeshProcessingExtension.h"
#include "Decimation.h"
#include "CleanPolyData.h"
#include "FillWithPoints.h"
#include "Tetrahedralize.h"
#include "Application.h"
// --------------- declare the extension -------------------
Q_EXPORT_PLUGIN2(decimation, DecimationExtension);
Q_EXPORT_PLUGIN2(processing, MeshProcessingExtension);
// -------------------- init --------------------
void DecimationExtension::init() {
void MeshProcessingExtension::init() {
registerNewAction(Decimation);
registerNewAction(CleanPolyData);
registerNewAction(FillWithPoints);
registerNewAction(Tetrahedralize);
}
......
......@@ -24,34 +24,34 @@ $CAMITK_LICENCE_END$
*****************************************************************************/
#ifndef DECIMATION_EXTENSION_H
#define DECIMATION_EXTENSION_H
#ifndef MESHPROCESSINGEXTENSION_H
#define MESHPROCESSINGEXTENSION_H
#include <QObject>
#include <ActionExtension.h>
using namespace camitk;
class DecimationExtension : public ActionExtension {
class MeshProcessingExtension : public ActionExtension {
Q_OBJECT
Q_INTERFACES(camitk::ActionExtension);
public:
/// the constructor
DecimationExtension() : ActionExtension() {};
MeshProcessingExtension() : ActionExtension() {};
/// the destructor
virtual ~DecimationExtension() {};
virtual ~MeshProcessingExtension() {};
/// initialize all the actions
virtual void init();
/// Method that return the action extension name
virtual QString getName() {return "Decimation";};
virtual QString getName() {return "Mesh Processing";};
/// Method that return the action extension descrption
virtual QString getDescription() {return "This extension provides a decimation algorithm of a mesh (using Vtk)";};
virtual QString getDescription() {return "This extension provides some mesh algorithms";};
};
#endif // DECIMATION_EXTENSION_H
\ No newline at end of file
#endif // MESHPROCESSINGEXTENSION_H
\ No newline at end of file
/*****************************************************************************
$CAMITK_LICENCE_BEGIN$
CamiTK - Computer Assisted Medical Intervention ToolKit
Visit http://camitk.imag.fr for more information
Copyright (C) 2012 Celine Fouard, Emmanuel Promayon, Yannick Keraval
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301 USA
$CAMITK_LICENCE_END$
*****************************************************************************/
#include "Tetrahedralize.h"
#include <MeshComponent.h>
#include <Log.h>
#include <Application.h>
#include <VtkMeshUtil.h>
#include <vtkPolyData.h>
#include <vtkCell.h>
#include <vtkTetra.h>
#include <vtkCellArray.h>
#include <vtkUnstructuredGrid.h>
#include "tetgen.h" // Defined tetgenio, tetrahedralize().
// -------------------- Tetrahedralize --------------------
Tetrahedralize::Tetrahedralize(ActionExtension* extension) : Action(extension) {
this->setName("Tetrahedralize");
this->setDescription("Generates a Delaunay tetrahedralization from the mesh points, msh, sofa scn, pml and corresponding lml");
this->setComponent("MeshComponent");
this->setFamily("Mesh Processing");
this->addTag("Tetrahedralize");
// command line parameter see documentation for more information
// http://wias-berlin.de/software/tetgen/files/tetgen-manual.pdf
setProperty("Tetgen Parameters","pq1.414V");
}
// --------------- apply -------------------
Action::ApplyStatus Tetrahedralize::apply() {
CAMITK_INFO("Tetrahedralize", "apply", "Tetrahedalizing " << getTargets().last()->getName().toStdString());
// set waiting cursor and status bar
QApplication::setOverrideCursor ( QCursor ( Qt::WaitCursor ) );
Application::showStatusBarMessage("Tetrahedalizing...");
Application::resetProgressBar();
// use the last target
MeshComponent *targetMesh = dynamic_cast<MeshComponent*>(getTargets().last());
//-- check if this is a polydata and if this is an unstructured grid, extract the surface
vtkSmartPointer<vtkPolyData> targetPolyData = VtkMeshUtil::vtkPointSetToVtkPolyData(targetMesh->getPointSet());
//-- build the tetgen data structures using just the points
tetgenio in;
// All indices start from 1.
in.firstnumber = 1;
in.numberofpoints = targetPolyData->GetPoints()->GetNumberOfPoints();
in.pointlist = new REAL[in.numberofpoints * 3];
int index = 0;
double xyz[3];
for ( int i = 0; i < targetPolyData->GetPoints()->GetNumberOfPoints(); i++ ) {
targetPolyData->GetPoints()->GetPoint ( i, xyz );
in.pointlist[index++] = xyz[0];
in.pointlist[index++] = xyz[1];
in.pointlist[index++] = xyz[2];
}
// generates all facets, use only triangles
unsigned nrOfTriangles = 0;
for (int i = 0;i < targetPolyData->GetNumberOfCells(); i++) {
if (targetPolyData->GetCell(i)->GetNumberOfPoints() == 3)
nrOfTriangles++;
}
in.numberoffacets = nrOfTriangles;
in.facetlist = new tetgenio::facet[in.numberoffacets];
//in.facetmarkerlist = new int[in.numberoffacets];
// generates tetgen triangle facets
for (int i = 0, facetId = 0; i < targetPolyData->GetNumberOfCells(); i++) {
vtkCell *c = targetPolyData->GetCell(i);
if (c->GetNumberOfPoints() == 3) {
//in.facetmarkerlist[facetId] = 1; // set boundary marker (but why?)
tetgenio::facet *f = &in.facetlist[facetId];
f->numberofpolygons = 1;
f->polygonlist = new tetgenio::polygon[f->numberofpolygons];
f->numberofholes = 0;
f->holelist = NULL;
tetgenio::polygon *p = &f->polygonlist[0];
p->numberofvertices = 3;
p->vertexlist = new int[p->numberofvertices];
p->vertexlist[0] = in.firstnumber + c->GetPointId(0);
p->vertexlist[1] = in.firstnumber + c->GetPointId(1);
p->vertexlist[2] = in.firstnumber + c->GetPointId(2);
// next time, process next facet
facetId++;
}
}
// save input mesh
//in.save_poly("tet");
//in.save_faces("tet");
//in.save_nodes("tet");
//in.numberoffacets = 0;
//-- Tetrahedralize the PLC
// Tetrahedralize the PLC. Switches are chosen to read a PLC (p),
// do quality mesh generation (q) with a specified quality bound
// (1.414), and apply a maximum volume constraint (a0.1).
// NOTE (warning) with the q option tetgen will add some vertex, the point list has to be updated!
//tetrahedralize("pq1.414a0.1", &in, &out);
tetgenio firstPass, secondPass, out;
tetrahedralize(property("Tetgen Parameters").toString().toAscii().data(), &in, &out);
/*
tetrahedralize("-rq1.414", &firstPass, &secondPass);
tetrahedralize("-ra0.5", &secondPass, &out);
*/
// save Output elements
//out.save_elements ((char *) QString(QFileInfo(myComponent->getFileName()).completeBaseName() + "_out").toStdString().c_str());
vtkSmartPointer<vtkPoints> thePoints = vtkSmartPointer<vtkPoints>::New();
for (int i = 0;i < out.numberofpoints;i++) {
thePoints->InsertNextPoint(out.pointlist[i*3], out.pointlist[i*3+1], out.pointlist[i*3+2]);
}
//-- export to VTK
// import the cells
vtkSmartPointer<vtkCellArray> theCells = vtkSmartPointer<vtkCellArray>::New();
for (int i = 0; i < out.numberoftetrahedra; i++) {
vtkSmartPointer<vtkCell> c = vtkSmartPointer<vtkTetra>::New();
/* Vtk/Pml tetrahedron Tetgen tetrahedron
* 3 3
* /| \ /| \
* / | \ / | \
* 1..|...\ 2 0..|...\ 2
* \ | / vs \ | /
* \ | / \ | /
* \|/ \|/
* 0 1
* => 1 and 0 are inversed
*/
c->GetPointIds()->SetId(0, out.tetrahedronlist[i * out.numberofcorners + 1] - in.firstnumber);
c->GetPointIds()->SetId(1, out.tetrahedronlist[i * out.numberofcorners + 0] - in.firstnumber);