添加自定义的属性面板
属性面板是ParaView中主要的调整可视化模型和显示相关参数的界面。插件可以提供新的pqPropertyWidgets子类来用来控制属性/参数组。
使用以下Cmake代码,将一个和特定属性相关窗口类型的pqPropertyWidget子类注册到ParaView中。
paraview_plugin_add_property_widget(
IFACES IFACE_SRCS
TYPE "string-type-name"
CLASS_NAME "class-name")
其中,class-name必须是pqPropertyWiget的子类,并且有如下类型的构造函数:
ClassName(vtkSMProxy *smproxy, vtkSMProperty *smproperty, QWidget *parentObject=nullptr)
TYPE 指定将在ServerManager XML中用作panel_widget属性值的字符串,以请求为vtkSMProperty子类创建此显示界面。
要为属性组(vtkSMPropertyGroup)注册一个与特定显示类型相关联的新pqPropertyWidget子类,请使用以下命令:
add_paraview_property_group_widget(IFACES IFACES_SRCS
TYPE "string-type-name"
CLASS_NAME "class-name")
class-name必须是pqPropertyWidget的子类,并且有以下构造函数:
ClassName(vtkSMProxy *smproxy, vtkSMPropertyGroup *smgroup, QWidget *parent=nullptr)
和之前一样,TYPE指明了在ServerManager XML中<PropertGroup/>标签panel_widget属性的值,该标签用来请求创建该显示组。
另一个添加自定义属性面板的方法是提供pqPropertyWidgetDecorator子类来为自定义逻辑添加显示面板。
Decorators 可以使用如下代码进行注册:
add_paraview_property_widget_decorator(
IFACES IFACE_SRCS
TYPE "string-type-name"
CLASS_NAME "class-name")
其中,class-name必须是pqPropertyWidgetDecorator的子类,并且TYPE是在ServerManagerXML中用来请求创建该decorator的名字。具体描述为此 here (ParaView/Properties Panel)。
Examples/Plugins/PropertyWidgets中是该示例。
为插件添加文档
从ParaView3.14之后的版本中,可以为插件在ParaView Help中添加帮助文档。有两种机制为插件添加文档:
1、在add_paraview_plugin宏中添加 SERVER_MANAGER_XML,会自动解析<Documentation />标签,并自动生产代理和属性的HTML页面。这保证了当点击滤波器或者输入源的 "?" 按钮,帮助文档会显示与其相关的内容。
2、在对ADD_PARAVIEW_PLUGIN()的调用中使用DOCUMENTATION_DIR命令来指定包含html页面和/或图像的目录,该目录被添加到插件的文档中(除了使用SERVER_MANAGER_XML文件生成的文档之外,例如:
add_paraview_plugin(SurfaceLIC "1.0"
DOCUMENTATION_DIR "${CMAKE_CURRENT_SOURCE_DIR}/doc"
SERVER_MANAGER_XML ${SM_XMLS}
... )
当插件加载成功后,该代码将添加文档到“ParaView Online Help”,显示如下:
添加工具栏
滤波器、读取器和输出器是最常用的扩展ParaView的方式。然而插件的功能远不止此。
应用使用工具栏来提供更加简单便捷的方式去使用某些功能。ParaView提供了为插件提供工具栏的方法。插件开发者可以使用c++回调函数来处理每个工具栏按钮的功能。因此,只要对ParaView服务器管理器框架和ParaView GUI组件有所了解,就可以使用工具栏插件进行几乎任何操作。
Examples/Plugins/SourceToolbar中有本部分的完整代码。这里我们添加了两个工具栏按钮,用来创建一个球和圆柱体。
为了添加一个工具栏,需要实现QActionGroup的子类,在该类中添加一系列QAction,每个QAction对应一个工具栏按钮,然后实现点击事件的回调函数。例子中的SourceToolbarActions类是QActionGroup的子类,其中实现了两个按钮。
构建插件的CMakeLists.txt如下:
# This is a macro for adding QActionGroup subclasses automatically as toolbars.
paraview_plugin_add_action_group(
CLASS_NAME SourceToolvarActions
GROUP_NAME "Toolbar/SourceToolBar"
INTERFACES interfaces
SOURCES sources
)
# Now create a plugin for the toolbar. Here we pass the 'interfaces' and
# 'sources' returned by the above call.
paraview_add_plugin(SourceToolVar
VERSION "1.0"
UI_INTERFACES ${interfaces}
SOURCES ${sources} SourceToolbarActions.cxx)
其中GroupName,使用ToolBar/SourceToolbar;这里的ToolBar是一个关键字,表明这个按钮集是一个名字为SourceToolbar的工具栏按钮集(可以在View > Toolbars菜单中显示)。当该插件被加载后,可以在菜单栏中显示两个按钮。
添加菜单
在主界面的菜单栏中添加菜单和添加工具栏的流程基本相同。唯一不同点是需要把GROUP_NAME中,用关键字MenuBar代替ToolBar。所以,如果将paraview_plugin_add_action_group命令改成如下,即可添加MyActions菜单到菜单栏:
paraview_plugin_add_action_group(
CLASS_NAME SourceToolbarActions
GROUP_NAME "MenuBar/MyActions"
INTERFACES interfaces
SOURCES sources)
如果给出的名字在菜单栏中已经存在,则会把该功能添加到该菜单中,而不是新建一个菜单。比如,如果GROUP_NAME 是 MenuBar/File,那么这个命令会添加到File菜单上。
添加一个上下文菜单(右键菜单)
上下文菜单是用户在视图(通常是渲染视图,但也可能是任何视图)中右键单击时创建的弹出菜单。你可以注册一个插件,当用户点击鼠标右键时,它会在光标下创建一个上下文菜单。使用返回非空菜单的pqContextMenuInterface的第一个实例;返回一个空菜单表示当用户右击时选择的对象与您的界面无关。因此,子类应该避免创建与特定应用程序或对象类型无关的菜单。
要将pqContextMenuInterface子类添加到ParaView,只需将您的类名传递给paraview_add_plugin()的UI_INTERFACES参数,并将源代码传递给SOURCES参数:
paraview_add_plugin(FancyMenu
...
UI_INTERFACES FancyMenu
SOURCES FancyMenu.h FancyMenu.cxx
...
)
Examples/Plugins/ContextMenu 中有具体的示例
插件的自动启动
插件自启动是指在ParaView启动时通知该插件,或者在ParaView启动时加载该插件;在ParaView退出时同样也要通知该插件。示例在ParaView源代码的Examples/Plugins/Autostart中。对于这样一个插件,需要提供一个QObject子类(如自定义一个pqMyApplicationStarter),并定义如下函数:
class pqMyApplicationStarter : public QObject
{
Q_OBJECT
public:
// Callback for startup.
// This cannot take any arguments
void onStartup();
// Callback for shutdown.
// This cannot take any arguments
void onShutdown();
};
CMakeLists.txt编写如下:
# Macro for auto-start plugins. We specify the class name and the methods to
# call on startup and shutdown on an instance of that class. It returns the
# interface and sources created in the variables passed to the 'INTERFACES'
# and 'SOURCES' arguments, respectively.
set(interfaces)
set(sources
pqMyApplicationStarter.cxx
pqMyApplicationStarter.h)
paraview_plugin_add_auto_start(
CLASS_NAME pqMyApplicationStarter
STARTUP onStartup
SHUTDOWN onShutdown
INTERFACES autostart_interface
SOURCES autostart_sources)
list(APPEND interfaces
${autostart_interface})
list(APPEND sources
${autostart_sources})
paraview_add_plugin(Autostart
REQUIRED_ON_CLIENT
VERSION "1.0"
UI_INTERFACES ${interfaces}
SOURCES ${sources})
获取动态插件的位置
一些动态加载的插件(dll or shared objecy)在其同级目录中包含一些数据或者文字文件。为了在运行时定位这些文件的位置,插件需要注册一个回调函数。该回调函数在插件加载时被调用,传出当前插件的文件系统路径。
class pyMyPluginLocation : public QObject
{
Q_OBJECT
public:
void SotreLocation(const char *location);
};
CMakeLists.txt代码如下:
# Macro for adding the location callback.
# We specify the class name and the method to call with the filesystem
# location as CLASS_NAME and STORE auguments.
# It returns the interface and sources created int the variables
# passed to the INTERFACES and SOURCES arguments.
paraview_plugin_add_location(
CLASS_NAME pyMyPluginLocation # the class name for our class
STORE StroeLocation # the method to call when the plugin is loaded
INTERFACES interfaces
SOURCES sources)
添加新的3D视图展示插件
ParaView的3D视图是常用的显示多边形和体素数据的窗口,默认情况下,ParaView提供显示点、线、面等类型的数据。同时ParaView也提供插件扩展,用于显示自定义的显示数据类型。
首先需要了解一下3D视图以及其表达形式的相关知识。3D视图使用3种基本的表达方式来进行所有三维数据的渲染:
(1)不规则网状表达:用于vtkUnstructuredGrid
或者一些由vtkUnstructedGrid合成的数据表达。
(2)规则网状表达:用于vtkImageData
或者由vtkImageData组成的数据。
(3)几何表达:用于其他数据。
这些表达方式中的每一个基本上都是复合表达方式,它们使用其他表达方式来进行实际的渲染,比如GeometryRepresentation使用SurfaceRepresentation进行点线面数据的渲染,使用OutlineRepresentation进行数据轮廓线的渲染。随后,3个复合表达方式提供了一个名为representation的属性,该属性允许用户选择他希望看到的数据的表示类型。复合表达方式具有基于用户选择的类型启用其内部表示之一的逻辑。
这些3-复合表达类型是固定的,不能被插件改变。插件可以做的是向这三个复合表示中的任何一个添加更多的内部表示,以支持新的表示类型,用户可以使用显示选项卡或工具栏上的表示类型组合框来选择。
使用新的Mapper
在这个例子种,我们将展示如何继承一种特定的使用VTK编写的polydata mapper到ParaView中。该mapper叫做vvtkMySpecialPolyDataMapper,是vtkPainterPolyDataMapper
的子类。实际上,vtkMySpecialPolyDataMapper可以在内部使用不同的绘制器来执行特殊的渲染任务。
为了继承该mapper到ParView中,我们首先为该mapper创建vtkSMRepresentationProxy子类。在该例子中,因为该mapper是一个标准的vtkPainterPolyDataMapper简单代替,我们可以定义自己的表达代理SurfaceRepresentation如下:
<ServerManagerConfiguration>
<ProxyGroup name="representations">
<RepresentationProxy
name="MySpecialRepresentation"
class="vtkMySpecialRepresentation"
processes="client|renderserver|dataserver"
base_proxygroup="representations"
base_proxyname="SurfaceRepresentation">
<Documentation>
This is the new representation type we are adding.
This is identical to the SurfaceRepresentation expect that
we are overriding the mapper with our mapper.
</Documentation>
</RepresentationProxy>
</ProxyGroup>
</ServerManagerConfiguration>
vtkMySpecialRepresentation是vtkGeometryRepresentationWithFaces
的子类,其中在构造函数中对重写了mappers,代码如下:
vtkMySpecialRepresentation::vtkMySpecialRepresentation()
{
// Replace the mapper created by the superclass.
this->Mapper->Delete();
this->LODMapper->Delete();
this->Mapper = vtkMySpecialPolyDataMapper::New();
this->LODMapper = vtkMySpecialPolyDataMapper::New();
// Since we replaced the mappers, we need to call SetupDefaults() to ensure
// the piplines are setup correctly.
this->SetupDefualts();
}
接着需要将该新类型注册到已有三种表达方式的任何一个或多个,然后自定义类型就可以在GUI上的选择框中进行选择。
要决定我们要将我们的表达方式添加到3个复合表达方式中的哪一个,请考虑我们的表示支持的输入数据类型。如果它可以支持任何类型的数据集,那么我们可以将我们的表示法添加到所有3种表示法中(就像这个例子一样)。如果自定义的表达方式属于体素渲染(vtkUnstructureGred),那么需要只将其添加到UnstructuredGridRepresentation中。
该步骤使用XML中的Extension标签。这仅仅意味着我们用指定的附加内容扩展了代理定义的原始XML。现在,为了是该表达类型对用户可用,我们需要使用RepresentationType元素,其中text是显示在combo-box框中的文字,subproxy指定当用户选择指定类型时要激活的下级表达proxy的名称。可选项为subtype属性,如果存在的话,这是在选择类型时在子代理的属性名表示上设置的值。这允许子代理提供不止一种表示类型。
<ServerManagerConfiguration>
<ProxyGroup name="representations">
<Extension name="GrometryRepresentation">
<Documentation>
Extends standard GeometryRepresentation by adding MySpeciaRepresentation as a new type of representation.
</Documentation>
<RepresentationType
subproxy="MySpecialRepresentation"
text="Special Mapper"
subtype="1" />
<SubProxy>
<Proxy name="MySpecialRepresentation"
proxyGroup="representations"
proxyname="MySpecialRepresentation">
</Proxy>
<ShareProperties subproxy="SurfaceRepresentation">
<Exception name="Input" />
<Exception name="Visibility" />
<Exception name="Representation"/>
</ShareProperties>
</SubProxy>
</Extension>
</ProxyGroup>
</ServerManagerConfiguration>
CMakeLists.txt文件与添加一个简单的过滤器或读取器(其中的表示类被放入包含的模块中)没有太大的不同。
示例代码在Examples/Plugins/Representation文件夹中。