在MeshLab中对其功能的扩展是通过插件来实现的,通过实现不同的接口,可以将插件放在不同的菜单下或工具栏上,在这里只介绍Filter菜单下的插件实现,其他的类似。
1. 首先在meshlab/src目录下创建myplugins文件夹,然后用QCreator打开meshlab_mini.pro。创建一个新的子工程叫helloplugin,选择空Qt项目即可,并保存在myplugins文件下,如图1所示。
图1 创建的helloplugin空项目
2.MeshLab提供了一个公用的shared.pri,里面包含了通用的插件编译配置,只需在helloplugin.pro包含该文件即完成了插件编译的基本配置。在helloplugin.pro中包含该文件:include (../../shared.pri)。然后在该工程下创建一个HelloPlugin类,继承自QObject,如图2所示。
图2 创建HelloPlugin类
3.MeshLab提供了MeshFilterInterface接口,用于实现Filter菜单下的插件,因此需要实现该接口。
//helloplugin.h #ifndef HELLOPLUGIN_H #define HELLOPLUGIN_H #include <QObject> #include <common/interfaces.h> class HelloPlugin : public QObject, public MeshFilterInterface { Q_OBJECT Q_INTERFACES(MeshFilterInterface) //指名MeshFilterInterface为接口 public: explicit HelloPlugin(QObject *parent = 0); //…… }; #endif // HELLOPLUGIN_H //helloplugin.cpp #include "helloplugin.h" HelloPlugin::HelloPlugin(QObject *parent) : QObject(parent) { } Q_EXPORT_PLUGIN(HelloPlugin) //导出插件
4.接下来逐步完成Filter菜单下的插件,是通过实现MeshFilterInterface接口中的功能来完成。下面介绍下几个核心函数和成员变量:
/** \brief The MeshFilterInterface class provide the interface of the filter plugins. */ class MeshFilterInterface : public MeshCommonInterface { public: /** The FilterClass enum represents the set of keywords that must be used to categorize a filter. Each filter can belong to one or more filtering class, or-ed togheter. */ enum FilterClass { Generic =0x00000, /*!< Should be avoided if possible. */ // Selection =0x00001, /*!< select or de-select something, basic operation on selections (like deleting)*/ Cleaning =0x00002, /*!< Filters that can be used to clean meshes (duplicated vertices etc)*/ Remeshing =0x00004, /*!< Simplification, Refinement, Reconstruction and mesh optimization*/ FaceColoring =0x00008, VertexColoring =0x00010, MeshCreation =0x00020, Smoothing =0x00040, /*!< Stuff that does not change the topology, but just the vertex positions*/ Quality =0x00080, Layer =0x00100, /*!< Layers, attributes */ RasterLayer =0x20000, /*!< Raster Layers, attributes */ Normal =0x00200, /*!< Normal, Curvature, orientation (rotations and transformations fall here)*/ Sampling =0x00400, Texture =0x00800, RangeMap =0x01000, /*!< filters specific for range map processing*/ PointSet =0x02000, Measure =0x04000, /*!< Filters that compute measures and information on meshes.*/ Polygonal =0x08000, /*!< Filters that works on polygonal and quad meshes.*/ Camera =0x10000 /*!< Filters that works on shot of mesh and raster.*/ }; MeshFilterInterface() : MeshCommonInterface() { } virtual ~MeshFilterInterface() {} /** The very short string (a few words) describing each filtering action // This string is used also to define the menu entry */ virtual QString filterName(FilterIDType ) const =0; /** The long, formatted string describing each filtering action. // This string is printed in the top of the parameter window // so it should be at least one or two paragraphs long. The more the better. // you can use simple html formatting tags (like "<br>" "<b>" and "<i>") to improve readability. // This string is used in the 'About plugin' dialog and by meshlabserver to create the filter list wiki page and the doxygen documentation of the filters. // Here is the place where you should put you bibliographic references in a form like this: <br> See: <br /> <i>Luiz Velho, Denis Zorin </i><br/> <b>"4-8 Subdivision"</b><br/> CAGD, volume 18, Issue 5, Pages 397-427.<br/> <br> e.g. italic for authors, bold for title (quoted) and plain for bib ref. */ virtual QString filterInfo(FilterIDType filter) const =0; /** The FilterClass describes in which generic class of filters it fits. // This choice affect the submenu in which each filter will be placed // For example filters that perform an action only on the selection will be placed in the Selection Class */ virtual FilterClass getClass(QAction *) { return MeshFilterInterface::Generic; } /** The filters can have some additional requirements on the mesh capabiliteis. // For example if a filters requires Face-Face Adjacency you shoud re-implement // this function making it returns MeshModel::MM_FACEFACETOPO. // The framework will ensure that the mesh has the requirements satisfied before invoking the applyFilter function // // Furthermore, requirements are checked just before the invocation of a filter. If your filter // outputs a never used before mesh property (e.g. face colors), it will be allocated by a call // to MeshModel::updateDataMask(...) */ virtual int getRequirements(QAction *){return MeshModel::MM_NONE;} /** The FilterPrecondition mask is used to explicitate what kind of data a filter really needs to be applied. // For example algorithms that compute per face quality have as precondition the existence of faces // (but quality per face is not a precondition, because quality per face is created by these algorithms) // on the other hand an algorithm that deletes faces according to the stored quality has both FaceQuality // and Face as precondition. // These conditions do NOT include computed properties like borderFlags, manifoldness or watertightness. // They are also used to grayout menus un-appliable entries. */ virtual int getPreConditions(QAction *) const {return MeshModel::MM_NONE;} /** Function used by the framework to get info about the mesh properties changed by the filter. // It is widely used by the meshlab's preview system. //TO BE REPLACED WITH = 0 */ virtual int postCondition( QAction* ) const {return MeshModel::MM_UNKNOWN;} /** \brief applies the selected filter with the already stabilished parameters * This function is called by the framework after getting values for the parameters specified in the \ref InitParameterSet * NO GUI interaction should be done here. No dialog asking, no messagebox errors. * Think that his function will also be called by the commandline framework. * If you want report errors, use the \ref errorMsg() string. It will displayed in case of filters returning false. * When implementing your applyFilter, you should use the cb function to report to the framework the current state of the processing. * During your (long) processing you should call from time to time cb(perc,descriptiveString), where perc is an int (0..100) * saying what you are doing and at what point of the computation you currently are. * \sa errorMsg * \sa initParameterSet */ virtual bool applyFilter(QAction * filter, MeshDocument &md, RichParameterSet & par, vcg::CallBackPos *cb) =0; /** \brief tests if a filter is applicable to a mesh. This function is a handy wrapper used by the framework for the \a getPreConditions callback; For istance a colorize by quality filter cannot be applied to a mesh without per-vertex-quality. On failure (returning false) the function fills the MissingItems list with strings describing the missing items. */ bool isFilterApplicable(QAction *act, const MeshModel& m, QStringList &MissingItems) const; // This function is called to initialized the list of parameters. // it is always called. If a filter does not need parameter it leave it empty and the framework // will not create a dialog (unless for previewing) virtual void initParameterSet(QAction *,MeshModel &/*m*/, RichParameterSet & /*par*/) {} virtual void initParameterSet(QAction *filter,MeshDocument &md, RichParameterSet &par) {initParameterSet(filter,*(md.mm()),par);} /** \brief is invoked by the framework when the applyFilter fails to give some info to the user about the fiter failure * Filters \b must never use QMessageBox for reporting errors. * Failing filters should put some meaningful information inside the errorMessage string and return false with the \ref applyFilter */ const QString &errorMsg() {return this->errorMessage;} virtual QString filterInfo(QAction *a) const {return this->filterInfo(ID(a));} virtual QString filterName(QAction *a) const {return this->filterName(ID(a));} virtual QString filterScriptFunctionName(FilterIDType /*filterID*/) {return "";} //………………………..略 protected: // Each plugins exposes a set of filtering possibilities. // Each filtering procedure corresponds to a single QAction with a corresponding FilterIDType id. // // The list of actions exported by the plugin. Each actions strictly corresponds to QList <QAction *> actionList; QList <FilterIDType> typeList; // this string is used to pass back to the framework error messages in case of failure of a filter apply. QString errorMessage; };
5.添加到Filter菜单下的插件有好几个分类,已是预定义好的,在FilterClass所定义的枚举类型,前面的代码中有定义。插件中可以有许多的功能,每个功能可以列入FilterClass中的一个分类,这些功能都存在成员变量typeList中,其相应的动作响应由actionList来承接,参见上面代码。
在HelloPlugin.h中定义该插件所具有的功能,加入代码:
enum{//定义HelloPlugin插件所具有的功能 FP_FUNC_ONE, FP_FUNC_TWO, FP_FUNC_THREE }; //重写MeshFilterInterface中的虚函数,实现自己的功能 //给所定义的功能起个名字,该名字是要显示在filter菜单中的子菜单中的,比如:给FP_FUNC_ONE起名为“Function one” virtual QString filterName(FilterIDType filter) const; //给所定义的功能进行描述 virtual QString filterInfo(FilterIDType filter) const; //给所定义的功能进行分类,即加入filter菜单中的哪个子菜单 virtual FilterClass getClass(QAction* a); //定义参数输入的GUI界面,进行交互 virtual void initParameterSet(QAction* a,MeshModel& mm,RichParameterSet& parlst); //判定该所执行的功能在具体运行前,是不是已满足运行条件 virtual int getPreConditions(QAction * a) const; //应用该插件所执行的功能 virtual bool applyFilter(QAction *filter, MeshDocument &md, RichParameterSet &par, vcg::CallBackPos *cb);
然后在HelloPlugin.cpp中实现这些功能,代码如下:
//HelloPlugin.cpp #include "helloplugin.h" HelloPlugin::HelloPlugin(QObject *parent) : QObject(parent),MeshFilterInterface() { typeList << FP_FUNC_ONE <<FP_FUNC_TWO <<FP_FUNC_THREE; foreach(FilterIDType tt,typeList)//为各功能添加响应 actionList<< new QAction(filterName(tt),this); } QString HelloPlugin::filterName(FilterIDType filter) const{ //给各功能所起在名称,即菜单名 switch(filter){ case FP_FUNC_ONE: return tr("Function One"); case FP_FUNC_TWO: return tr("Function Two"); case FP_FUNC_THREE: return tr("Function Three"); default:assert(0); } return tr(""); } QString HelloPlugin::filterInfo(FilterIDType filter) const{ //对各功能进行的解释,描述 switch(filter){ case FP_FUNC_ONE: return tr("the description for function one"); case FP_FUNC_TWO: return tr("the description for function two"); case FP_FUNC_THREE: return tr("the description for function three"); default: assert(0); } return tr(""); } MeshFilterInterface::FilterClass HelloPlugin::getClass(QAction* a){ //定义每个功能所属的子菜单 switch(ID(a)){ case FP_FUNC_ONE: return MeshFilterInterface::Generic;//直接在filter菜单下 case FP_FUNC_TWO: case FP_FUNC_THREE: return MeshFilterInterface::Camera;//在filter->camera子菜单下 default:assert(0); } return MeshFilterInterface::Generic; } void HelloPlugin::initParameterSet(QAction* a,MeshModel& mm,RichParameterSet& parlst){ //定义每个功能接收参数的GUI,至于存在几种控件类型,请查相关资料 switch(ID(a)){ case FP_FUNC_ONE: { parlst.addParam(new RichBool("Original",true,"remove original","if remove the original mesh.")); return; } case FP_FUNC_TWO: { parlst.addParam(new RichInt("Resolution",90,"resolution"," resolution when do voxelization")); parlst.addParam(new RichFloat("Ratio",0.96,"Shreshold","the ratio over this threshold would be thought as the same.")); parlst.addParam(new RichBool("WriteFile",false,"write file","write the lightweighted model to file.")); parlst.addParam(new RichInt("OverNum",16,"over vertex num","if the vertex number of one mesh over this threshold, it will be write to an individual file")); parlst.addParam(new RichBool("QuickSlow",false,"Quick","voxelize Quickly but high memory, else slowly with low memory.")); return; } case FP_FUNC_THREE: {//不需要GUI接收参数 return; } default:assert(0); } return; } int HelloPlugin::getPreConditions(QAction * a) const{ //各个功能执行的先决条件 switch(ID(a)){ case FP_FUNC_ONE: case FP_FUNC_TWO: case FP_FUNC_THREE: return MeshModel::MM_FACENUMBER;//必须存在三维模型 default:assert(0); } return MeshModel::MM_NONE;//不需任何条件 } bool HelloPlugin::applyFilter(QAction *filter, MeshDocument &md, RichParameterSet &par, vcg::CallBackPos *cb){ //your work is here return true; } Q_EXPORT_PLUGIN(HelloPlugin)
如此,一个简单的、不执行任何功能的插件编写完成,上个截图:
辉辉
(FightingBull Studio)