1 /****************************************************************************
2 * MeshLab                                                           o o     *
3 * An extendible mesh processor                                    o     o   *
4 *                                                                _   O  _   *
5 * Copyright(C) 2005, 2006                                          \/)\/    *
6 * Visual Computing Lab                                            /\/|      *
7 * ISTI - Italian National Research Council                           |      *
8 *                                                                    \      *
9 * All rights reserved.                                                      *
10 *                                                                           *
11 * This program is free software; you can redistribute it and/or modify      *
12 * it under the terms of the GNU General Public License as published by      *
13 * the Free Software Foundation; either version 2 of the License, or         *
14 * (at your option) any later version.                                       *
15 *                                                                           *
16 * This program is distributed in the hope that it will be useful,           *
17 * but WITHOUT ANY WARRANTY; without even the implied warranty of            *
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the             *
19 * GNU General Public License (http://www.gnu.org/licenses/gpl.txt)          *
20 * for more details.                                                         *
21 *                                                                           *
22 ****************************************************************************/
23 #include "filter_isoparametrization.h"
24 
25 #include <QStringList>
26 #include <QFileInfo>
27 #include "defines.h"
28 
29 #include "../../common/meshmodel.h"
30 #include <iso_transfer.h>
31 
32 
33 #include <stdlib.h>
34 #include <time.h>
35 
36 using namespace std;
37 using namespace vcg;
38 
FilterIsoParametrization()39 FilterIsoParametrization::FilterIsoParametrization()
40 {
41   typeList << ISOP_PARAM
42            << ISOP_REMESHING
43            << ISOP_DIAMPARAM
44            << ISOP_TRANSFER;
45 
46   FilterIDType tt;
47   foreach(tt , types())
48     actionList << new QAction(filterName(tt), this);
49 
50 }
51 
~FilterIsoParametrization()52 FilterIsoParametrization::~FilterIsoParametrization()
53 {
54   for (int i = 0; i < actionList.count() ; i++ )
55     delete actionList.at(i);
56 }
57 
filterName(FilterIDType filter) const58 QString FilterIsoParametrization::filterName(FilterIDType filter) const
59 {
60   switch(filter)
61   {
62   case ISOP_PARAM :			return "Iso Parametrization: Main";
63   case ISOP_REMESHING :     return "Iso Parametrization Remeshing";
64   case ISOP_DIAMPARAM :     return "Iso Parametrization Build Atlased Mesh";
65   case ISOP_TRANSFER:		return "Iso Parametrization transfer between meshes";
66   default: assert(0);
67   }
68   return QString("error!");
69 }
70 static const QString bibRef("For more details see: <br>"
71                             "<b>N. Pietroni, M. Tarini and P. Cignoni</b>, <br><a href=\"http://vcg.isti.cnr.it/Publications/2010/PTC10/\">'Almost isometric mesh parameterization through abstract domains'</a> <br>"
72                             "IEEE Transaction of Visualization and Computer Graphics, 2010");
73 
filterInfo(FilterIDType filterId) const74 QString FilterIsoParametrization::filterInfo(FilterIDType filterId) const
75 {
76   switch(filterId)
77   {
78   case ISOP_PARAM : return "The filter builds the abstract domain mesh representing the Isoparameterization of a watertight two-manifold triangular mesh. <br>"
79                            "This abstract mesh can be used to uniformly remesh the input mesh, or to build a atlased texture parametrization. Abstract Mesh can be also loaded and saved. <br>"
80         "In short this filter build a very coarse almost regular triangulation such that original mesh can be reprojected from this abstract mesh with minimal distortion.<br>"+bibRef;
81   case ISOP_REMESHING : return "Uniform Remeshing based on Isoparameterization, each triangle of the domain is recursively subdivided. <br>"
82                                +bibRef;
83   case ISOP_DIAMPARAM : return "The filter build a new mesh with a standard atlased per wedge texture. The atlas is simply done by "
84                                "exploiting the low distortion, coarse, regular, mesh of the abstract domain<br>" +bibRef;
85   case ISOP_TRANSFER:return "Transfer the Isoparametrization between two meshes, the two meshes must be reasonably similar and well aligned."
86                             " It is useful to transfer back an isoparam onto the original mesh after having computed it on a dummy, clean watertight model.<br>"
87                             +bibRef;
88   default: assert(0);
89   }
90   return QString("error!");
91 }
92 
getRequirements(QAction *)93 int FilterIsoParametrization::getRequirements(QAction *)
94 {
95 	return MeshModel::MM_NONE;
96 }
97 
initParameterSet(QAction * a,MeshDocument & md,RichParameterSet & par)98 void FilterIsoParametrization::initParameterSet(QAction *a, MeshDocument& md, RichParameterSet & par)
99 {
100 
101   switch(ID(a))
102   {
103   case ISOP_PARAM:
104   {
105     par.addParam(new RichInt("targetAbstractMinFaceNum",150,"AM  Min Size",
106                              "This number and the following one indicate the range face number of the abstract mesh that is used for the parametrization process.<br>"
107                              "The algorithm will choose the best abstract mesh with the number of triangles within the specified interval.<br>"
108                              "If the mesh has a very simple structure this range can be very low and strict;"
109                              "for a roughly spherical object if you can specify a range of [8,8] faces you get a octahedral abstract mesh, e.g. a geometry image.<br>"
110                              "Large numbers (greater than 400) are usually not of practical use."));
111     par.addParam(new RichInt("targetAbstractMaxFaceNum",200,"AM Max Size", "Please notice that a large interval requires huge amount of memory to be allocated, in order save the intermediate results. <br>"
112                                                                                       "An interval of 50 should be fine."));
113     QStringList stopCriteriaList;
114     stopCriteriaList.push_back("Best Heuristic");
115     stopCriteriaList.push_back("Area + Angle");
116     stopCriteriaList.push_back("Regularity");
117     stopCriteriaList.push_back("L2");
118 
119     par.addParam(new RichEnum("stopCriteria", 1, stopCriteriaList, tr("Optimization Criteria"),
120                               tr(//"<p style=\'white-space:pre\'>"
121                                  "Choose a metric to stop the parametrization within the interval<br>"
122                                  "1: Best Heuristic : stop considering both isometry and number of faces of base domain<br>"
123                                  "2: Area + Angle : stop at minimum area and angle distorsion<br>"
124                                  "3: Regularity : stop at minimum number of irregular vertices<br>"
125                                  "4: L2 : stop at minimum OneWay L2 Stretch Eff")));
126 
127     par.addParam(new RichInt("convergenceSpeed",1, "Convergence Precision", "This parameter controls the convergence speed/precision of the optimization of the texture coordinates. Larger the number slower the processing and ,eventually, slightly better results"));
128     par.addParam(new RichBool("DoubleStep",true,"Double Step","Use this bool to divide the parameterization in 2 steps. Double step makes the overall process faster and robust."
129                                                               "<br> Consider to disable this bool in case the object has topologycal noise or small handles."));
130     par.addParam(new RichString("AbsLoadName", "", "Load AM", "The filename of the abstract mesh that has to be loaded. If empty, the abstract mesh will be computed according to the above parameters (suggested extension '.abs')."));
131     par.addParam(new RichString("AbsSaveName", "", "Save AM", "The filename where the computed abstract mesh will be saved. If empty, nothing will be done."));
132     break;
133   }
134   case ISOP_REMESHING :
135   {
136     par.addParam(new RichInt("SamplingRate",10,"Sampling Rate", "This specify the sampling rate for remeshing. Must be greater than 2"));
137     break;
138   }
139   case ISOP_DIAMPARAM :
140   {
141     par.addParam(new RichDynamicFloat("BorderSize",0.1f,0.01f,0.5f,"BorderSize ratio",
142                                       "This parameter controls the amount of space that must be left between each diamond when building the atlas."
143                                       "It directly affects how many triangle are split during this conversion. <br>"
144                                       "In abstract parametrization mesh triangles can naturally cross the triangles of the abstract domain, so when converting "
145                                       "to a standard parametrization we must cut all the triangles that protrudes outside each diamond more than the specified threshold."
146                                       "The unit of the threshold is in percentage of the size of the diamond,"
147                                       "The bigger the threshold the less triangles are split, but the more UV space is used (wasted)."));
148     break;
149   }
150   case ISOP_TRANSFER:
151   {
152     par.addParam(new RichMesh ("sourceMesh",md.mm(),&md, "Source Mesh",	"The mesh already having an Isoparameterization"));
153     par.addParam(new RichMesh ("targetMesh",md.mm(),&md, "Target Mesh",	"The mesh to be Isoparameterized"));
154   }
155   }
156 }
157 
PrintStats(CMeshO * mesh)158 void FilterIsoParametrization::PrintStats(CMeshO *mesh)
159 {
160   tri::UpdateTopology<CMeshO>::FaceFace(*mesh);
161   tri::UpdateTopology<CMeshO>::VertexFace(*mesh);
162   int non_reg=NumRegular<CMeshO>(*mesh);
163   CMeshO::ScalarType minE,maxE,avE,stdE;
164   CMeshO::ScalarType minAr,maxAr,avAr,stdAr;
165   CMeshO::ScalarType minAn,maxAn,avAn,stdAn;
166 
167   StatEdge<CMeshO>(*mesh,minE,maxE,avE,stdE);
168   StatArea<CMeshO>(*mesh,minAr,maxAr,avAr,stdAr);
169   StatAngle<CMeshO>(*mesh,minAn,maxAn,avAn,stdAn);
170   Log(" REMESHED ");
171   Log("Irregular Vertices:%d ",non_reg);
172   Log("stdDev Area:  %5.2f",stdAr/avAr);
173   Log("stdDev Angle: %5.2f",stdAn/avAn);
174   Log("stdDev Edge:  %5.2f",stdE/avE);
175 }
176 
applyFilter(QAction * filter,MeshDocument & md,RichParameterSet & par,vcg::CallBackPos * cb)177 bool FilterIsoParametrization::applyFilter(QAction *filter, MeshDocument& md, RichParameterSet & par, vcg::CallBackPos  *cb)
178 {
179   MeshModel* m = md.mm();  //get current mesh from document
180   CMeshO *mesh=&m->cm;
181   switch(ID(filter))
182   {
183   case ISOP_PARAM :  //////////////// the main filter //////////////////////
184   {
185     int targetAbstractMinFaceNum = par.getInt("targetAbstractMinFaceNum");
186     int targetAbstractMaxFaceNum = par.getInt("targetAbstractMaxFaceNum");
187     int convergenceSpeed = par.getInt("convergenceSpeed");
188     int stopCriteria=par.getEnum("stopCriteria");
189     bool doublestep=par.getBool("DoubleStep");
190 
191     m->updateDataMask(MeshModel::MM_FACEFACETOPO);
192     m->updateDataMask(MeshModel::MM_VERTQUALITY); // needed to store patch index
193     m->updateDataMask(MeshModel::MM_VERTMARK | MeshModel::MM_FACEMARK);
194 
195     bool isTXTenabled=m->hasDataMask(MeshModel::MM_VERTTEXCOORD);
196     if (!isTXTenabled)
197       m->updateDataMask(MeshModel::MM_VERTTEXCOORD);
198 
199     bool isVColorenabled=m->hasDataMask(MeshModel::MM_VERTCOLOR);
200     if (!isVColorenabled)
201       m->updateDataMask(MeshModel::MM_VERTCOLOR);
202 
203     bool isFColorenabled=m->hasDataMask(MeshModel::MM_FACECOLOR);
204     if (!isFColorenabled)
205       m->updateDataMask(MeshModel::MM_FACECOLOR);
206     int tolerance = targetAbstractMaxFaceNum-targetAbstractMinFaceNum;
207 
208     CMeshO::PerMeshAttributeHandle<IsoParametrization> isoPHandle =
209         tri::Allocator<CMeshO>::GetPerMeshAttribute<IsoParametrization>(*mesh,"isoparametrization");
210 
211     QString AbsLoadName = par.getString("AbsLoadName");
212     if(AbsLoadName.isEmpty())
213     {
214       IsoParametrizator Parametrizator;
215       switch (stopCriteria)
216       {
217       case 0:Parametrizator.SetParameters(cb,targetAbstractMinFaceNum,tolerance,IsoParametrizator::SM_Euristic,convergenceSpeed);break;
218       case 1:Parametrizator.SetParameters(cb,targetAbstractMinFaceNum,tolerance,IsoParametrizator::SM_Corr,convergenceSpeed);break;
219       case 2:Parametrizator.SetParameters(cb,targetAbstractMinFaceNum,tolerance,IsoParametrizator::SM_Reg,convergenceSpeed);break;
220       case 3:Parametrizator.SetParameters(cb,targetAbstractMinFaceNum,tolerance,IsoParametrizator::SM_L2,convergenceSpeed);break;
221       default:Parametrizator.SetParameters(cb,targetAbstractMinFaceNum,tolerance,IsoParametrizator::SM_Euristic,convergenceSpeed);break;
222       }
223       tri::ParamEdgeCollapseParameter pecp;
224       IsoParametrizator::ReturnCode ret=Parametrizator.Parametrize<CMeshO>(mesh,pecp,doublestep);
225 
226       if (ret==IsoParametrizator::Done)
227       {
228         Parametrizator.PrintAttributes();
229         float aggregate,L2;
230         int n_faces;
231         Parametrizator.getValues(aggregate,L2,n_faces);
232         Log("Num Faces of Abstract Domain: %d, One way stretch efficiency: %.4f, Area+Angle Distorsion %.4f  ",n_faces,L2,aggregate*100.f);
233       }
234       else
235       {
236         if (!isTXTenabled)    m->clearDataMask(MeshModel::MM_VERTTEXCOORD);
237         if (!isVColorenabled) m->clearDataMask(MeshModel::MM_VERTCOLOR);
238         if (!isFColorenabled) m->clearDataMask(MeshModel::MM_FACECOLOR);
239         switch(ret)
240         {
241         case IsoParametrizator::MultiComponent:
242           this->errorMessage="non possible parameterization because of multi component mesh"; return false;
243         case IsoParametrizator::NonSizeCons:
244           this->errorMessage="non possible parameterization because of non size consistent mesh"; return false;
245         case IsoParametrizator::NonManifoldE:
246           this->errorMessage="non possible parameterization because of non manifold edges"; return false;
247         case IsoParametrizator::NonManifoldV:
248           this->errorMessage="non possible parameterization because of non manifold vertices";return false;
249         case IsoParametrizator::NonWatertigh:
250           this->errorMessage="non possible parameterization because of non watertight mesh"; return false;
251         case IsoParametrizator::FailParam:
252           this->errorMessage="non possible parameterization cause one of the following reasons:\n Topologycal noise \n Too Low resolution mesh \n Too Bad triangulation \n"; return false;
253         default:
254           this->errorMessage="unknown error"; return false;
255         }
256       }
257 
258       // At this point we are sure that everything went ok so we can allocate surely the abstract
259       AbstractMesh *abs_mesh = new AbstractMesh();
260       ParamMesh *para_mesh = new ParamMesh();
261       Parametrizator.ExportMeshes(*para_mesh,*abs_mesh);
262 
263       bool isOK=isoPHandle().Init(abs_mesh,para_mesh);
264       if (!isOK) {
265         this->errorMessage="Problems gathering parameterization \n";
266         return false;
267       }
268 
269       isoPHandle().CopyParametrization<CMeshO>(mesh); ///copy back to original mesh
270     }
271     else
272     {
273       AbstractMesh *abs_mesh = new AbstractMesh();
274       ParamMesh *para_mesh = new ParamMesh();
275       bool Done=isoPHandle().LoadBaseDomain<CMeshO>(qUtf8Printable(AbsLoadName),mesh,para_mesh,abs_mesh,true);
276       if (!Done)
277       {
278         this->errorMessage="Abstract domain doesn't fit well with the parametrized mesh";
279         delete para_mesh;
280         delete abs_mesh;
281         return false;
282       }
283     }
284 
285     QString AbsSaveName = par.getString("AbsSaveName");
286     if(!AbsSaveName.isEmpty())
287     {
288       isoPHandle().SaveBaseDomain(qUtf8Printable(AbsSaveName));
289     }
290     return true;
291   }
292   case ISOP_REMESHING :
293   {
294     CMeshO::PerMeshAttributeHandle<IsoParametrization> isoPHandle =
295         tri::Allocator<CMeshO>::FindPerMeshAttribute<IsoParametrization>(*mesh,"isoparametrization");
296 
297     bool b=tri::Allocator<CMeshO>::IsValidHandle<IsoParametrization>(*mesh,isoPHandle);
298     if (!b)
299     {
300       this->errorMessage="You must compute the abstract mesh before remeshing. Use the Isoparametrization main filter.";
301       return false;
302     }
303 
304 
305     int SamplingRate=par.getInt("SamplingRate");
306     if (SamplingRate<2)
307     {
308       this->errorMessage="Sampling rate must be >1";
309       return false;
310     }
311     MeshModel* mm=md.addNewMesh("","Re-meshed");
312 
313     CMeshO *rem=&mm->cm;
314     DiamSampler DiamSampl;
315     DiamSampl.Init(&isoPHandle());
316     bool done=DiamSampl.SamplePos(SamplingRate);
317     assert(done);
318     DiamSampl.GetMesh<CMeshO>(*rem);
319 
320     int n_diamonds,inFace,inEdge,inStar,n_merged;
321     DiamSampl.getResData(n_diamonds,inFace,inEdge,inStar,n_merged);
322 
323     Log("INTERPOLATION DOMAINS");
324     Log("In Face: %d \n",inFace);
325     Log("In Diamond: %d \n",inEdge);
326     Log("In Star: %d \n",inStar);
327     Log("Merged %d vertices\n",n_merged);
328     mm->updateDataMask(MeshModel::MM_FACEFACETOPO);
329     mm->updateDataMask(MeshModel::MM_VERTFACETOPO);
330     PrintStats(rem);
331     mm->UpdateBoxAndNormals();
332     return true;
333   }
334   case ISOP_DIAMPARAM :
335   {
336     CMeshO::PerMeshAttributeHandle<IsoParametrization> isoPHandle =
337         tri::Allocator<CMeshO>::FindPerMeshAttribute<IsoParametrization>(*mesh,"isoparametrization");
338     bool b=tri::Allocator<CMeshO>::IsValidHandle<IsoParametrization>(*mesh,isoPHandle);
339     if (!b)
340     {
341       this->errorMessage="You must compute the abstract mesh before remeshing. Use the Isoparametrization main filter.";
342       return false;
343     }
344 
345     float border_size=par.getDynamicFloat("BorderSize");
346     MeshModel* mm=md.addNewMesh("","Diam-Parameterized");
347     mm->updateDataMask(MeshModel::MM_WEDGTEXCOORD);
348     mm->updateDataMask(MeshModel::MM_VERTCOLOR);
349     CMeshO *rem=&mm->cm;
350     DiamondParametrizator DiaPara;
351     DiaPara.Init(&isoPHandle());
352     DiaPara.SetCoordinates<CMeshO>(*rem,border_size);
353     tri::UpdateNormal<CMeshO>::PerFace(*rem);
354     return true;
355   }
356 //  case ISOP_LOAD :
357 //  {
358 //    QString AbsName = par.getString("AbsName");
359 //    m->updateDataMask(MeshModel::MM_WEDGTEXCOORD);
360 //    m->updateDataMask(MeshModel::MM_VERTTEXCOORD);
361 //    m->updateDataMask(MeshModel::MM_FACECOLOR);
362 //    m->updateDataMask(MeshModel::MM_VERTQUALITY);
363 //    m->updateDataMask(MeshModel::MM_FACEMARK);
364 //    if(!QFile(m->fullName()).exists())
365 //    {
366 //      this->errorMessage="File not exists";
367 //      return false;
368 //    }
369 //    CMeshO::PerMeshAttributeHandle<IsoParametrization> isoPHandle =
370 //        tri::Allocator<CMeshO>::FindPerMeshAttribute<IsoParametrization>(*mesh,"isoparametrization");
371 
372 //    bool b=tri::Allocator<CMeshO>::IsValidHandle<IsoParametrization>(*mesh,isoPHandle);
373 //    if (!b)
374 //      isoPHandle=tri::Allocator<CMeshO>::AddPerMeshAttribute<IsoParametrization>(*mesh,"isoparametrization");
375 
376 //    QByteArray ba = AbsName.toLatin1();
377 //    char *path=ba.data();
378 //    AbstractMesh *abs_mesh = new AbstractMesh();
379 //    ParamMesh *para_mesh = new ParamMesh();
380 //    bool Done=isoPHandle().LoadBaseDomain<CMeshO>(path,mesh,para_mesh,abs_mesh,true);
381 //    if (!Done)
382 //    {
383 //      this->errorMessage="Abstract domain doesn't fit well with the parametrized mesh";
384 //      delete para_mesh;
385 //      delete abs_mesh;
386 //      return false;
387 //    }
388 //    return true;
389 //  }
390 //  case ISOP_SAVE :
391 //  {
392 //    m->updateDataMask(MeshModel::MM_VERTQUALITY);
393 //    CMeshO::PerMeshAttributeHandle<IsoParametrization> isoPHandle =
394 //        tri::Allocator<CMeshO>::FindPerMeshAttribute<IsoParametrization>(*mesh,"isoparametrization");
395 
396 //    bool b=tri::Allocator<CMeshO>::IsValidHandle<IsoParametrization>(*mesh,isoPHandle);
397 //    if (!b)
398 //    {
399 //      this->errorMessage="You must compute the Base domain before remeshing. Use the Isoparametrization command.";
400 //      return false;
401 //    }
402 //    /*QString Qpath=m->fullName();*/
403 
404 //    QString AbsName = par.getString("AbsName");
405 
406 //    QByteArray ba = AbsName.toLatin1();
407 //    char *path=ba.data();
408 //    isoPHandle().SaveBaseDomain(path);
409 //    return true;
410 //  }
411   case ISOP_TRANSFER:
412   {
413     MeshModel *mmtrg = par.getMesh("targetMesh");
414     MeshModel *mmsrc = par.getMesh("targetMesh");
415     CMeshO *trgMesh=&mmtrg->cm;
416     CMeshO *srcMesh=&mmsrc->cm;
417 
418     CMeshO::PerMeshAttributeHandle<IsoParametrization> isoPHandle =
419         tri::Allocator<CMeshO>::FindPerMeshAttribute<IsoParametrization>(*mesh,"isoparametrization");
420 
421     bool b=tri::Allocator<CMeshO>::IsValidHandle<IsoParametrization>(*srcMesh,isoPHandle);
422     if (!b)
423     {
424       this->errorMessage="Your source mesh must have the abstract isoparametrization. Use the Isoparametrization command.";
425       return false;
426     }
427     IsoTransfer IsoTr;
428     AbstractMesh *abs_mesh = isoPHandle().AbsMesh();
429     ParamMesh *para_mesh = isoPHandle().ParaMesh();
430 
431     mmtrg->updateDataMask(MeshModel::MM_WEDGTEXCOORD);
432     mmtrg->updateDataMask(MeshModel::MM_VERTTEXCOORD);
433     mmtrg->updateDataMask(MeshModel::MM_FACECOLOR);
434     mmtrg->updateDataMask(MeshModel::MM_VERTQUALITY);
435     mmtrg->updateDataMask(MeshModel::MM_FACEMARK);
436     IsoTr.Transfer<CMeshO>(isoPHandle(),*trgMesh);
437 
438     isoPHandle().Clear();
439     tri::Allocator<CMeshO>::DeletePerMeshAttribute(*srcMesh,isoPHandle);
440 
441     isoPHandle=tri::Allocator<CMeshO>::AddPerMeshAttribute<IsoParametrization>(*trgMesh,"isoparametrization");
442     isoPHandle().AbsMesh()=abs_mesh;
443     isoPHandle().SetParamMesh<CMeshO>(trgMesh,para_mesh);
444 
445     return true;
446   }
447   }
448   return false;
449 }
450 
getClass(QAction *)451 MeshFilterInterface::FilterClass FilterIsoParametrization::getClass(QAction *)
452 {
453   return MeshFilterInterface::Remeshing;
454 }
455 
postCondition(QAction *) const456 int FilterIsoParametrization::postCondition( QAction* /*filter*/ ) const
457 {
458 	return MeshModel::MM_WEDGTEXCOORD | MeshModel::MM_VERTTEXCOORD;
459 }
460 
filterArity(QAction * filter) const461 MeshFilterInterface::FILTER_ARITY FilterIsoParametrization::filterArity( QAction* filter) const
462 {
463     switch(ID(filter))
464     {
465     case ISOP_PARAM :
466     case ISOP_REMESHING :
467     case ISOP_DIAMPARAM :
468         return MeshFilterInterface::SINGLE_MESH;
469     case ISOP_TRANSFER:
470         return MeshFilterInterface::FIXED;
471     }
472     return MeshFilterInterface::NONE;
473 }
474 
475 
476 MESHLAB_PLUGIN_NAME_EXPORTER(FilterIsoParametrization)
477