【VTK+有限元后处理】节点属性值查询

功能

有限元在后处理过程中,我们如果想获取某一个节点的属性数据值,最直接的方法就是点击这个节点,然后显示其属性数据。

代码实现

首先我们需要使用到VTK的点拾取类vtkPointPicker类。

从需求可知,我们需要与窗口进行交互,所以先自定义一个继承自vtkInteractorStyleTrackballCamera的类(在类中定义了点拾取的交互类型)。我最开始参考了这篇文章VTK:交互与拾取——点拾取的代码,虽然运行成功了,但有些地方似乎不符合预期。第一,其中点拾取代码中有一行为actor->SetScale(0.05);,即把选中点的圆点标记大小设为常值。带来的结果是,当在一个模型整体尺寸小于这一设定常值(0.05)的时候,标记点会变得特别大(甚至直接覆盖原模型),相反,当模型尺寸远大于这一常值,标记点会变得特别小(很难发现那种)。第二,对于一些不在模型上的点也会被选中并标记,在后处理中,我们希望选中的都是有限元模型节点。对于上述两个问题,我进行了一些优化。优化后的代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
class PointPickerInteractorStyle(vtk.vtkInteractorStyleTrackballCamera):

def __init__(self, parent = None, dataset = None):
self.AddObserver("LeftButtonPressEvent", self.leftButtonPressEvent)
self.dataset = dataset
self.points = dataset.GetPoints()
# 对拾取点进行标记
sphereSource = vtk.vtkSphereSource()
sphereSource.Update()
self.mapper = vtk.vtkPolyDataMapper()
self.mapper.SetInputConnection(sphereSource.GetOutputPort())
self.marker_actor = vtk.vtkActor()
self.marker_actor.SetMapper(self.mapper)
# Setup the text and add it to the renderer
self.textActor = vtk.vtkTextActor()
self.textActor.SetPosition(10, 10)
self.textActor.GetTextProperty().SetFontSize(24)
self.textActor.GetTextProperty().SetColor(241 / 255, 135 / 255, 184 / 255)
# kd-tree
self.tree = vtk.vtkKdTree()
self.tree.BuildLocatorFromPoints(self.points)

def leftButtonPressEvent(self, obj, event):
clickPos = self.GetInteractor().GetEventPosition()
# 打印鼠标左键像素位置
# print(f"Picking pixel: {clickPos[0]} {clickPos[1]}")
# 注册拾取点函数
pointPicker = self.GetInteractor().GetPicker()
pointPicker.Pick(clickPos[0], clickPos[1], 0, self.GetDefaultRenderer())
# 打印拾取点空间位置
pickId = pointPicker.GetPointId() # 获取拾取点的ID,无ID返回-1
if pickId != -1:
# 显示模型上被拾取的点
pickPos = pointPicker.GetPickPosition()
# print(f"Picked value: {pickPos[0]} {pickPos[1]} {pickPos[2]}")
self.marker_actor.SetPosition(pickPos)
# Find the 2 closest points to pickPos
ClosestIdList = vtk.vtkIdList()
self.tree.FindClosestNPoints(2, pickPos, ClosestIdList)
pt1 = self.points.GetPoint(ClosestIdList.GetId(0))
pt2 = self.points.GetPoint(ClosestIdList.GetId(1))
distance = np.sqrt(vtk.vtkMath.Distance2BetweenPoints(pt1, pt2))
self.marker_actor.SetScale(distance / 5)
self.marker_actor.GetProperty().SetColor(0.0, 1.0, 0.0)
self.GetDefaultRenderer().AddActor(self.marker_actor)
# 打印节点信息
if self.dataset.GetPointData().GetScalars():
scalars = self.dataset.GetPointData().GetScalars()
self.textActor.SetInput("Picked Point: %.2f %.2f %.2f\nAttribute Value: %.2f" % (
pickPos[0], pickPos[1], pickPos[2], scalars.GetValue(pickId)))
else:
self.textActor.SetInput("Picked Point: %.2f %.2f %.2f\n" % (pickPos[0], pickPos[1], pickPos[2]))
self.GetDefaultRenderer().AddActor2D(self.textActor)
self.OnLeftButtonDown()

为了能根据模型尺寸改变标记点的大小,我目前的思路是:寻找到距离被拾取点最近的一个点(不包括拾取点本身),计算与其之间的距离distance,然后设置标记对象的尺寸为distance / 5,即设置标签点的直径为与其最近点的五分之一(反正比distance小就行),这样就实现了动态的尺寸调整。当然这种方法也不是唯一解,只要是能动态合理调节标记对象尺寸的方法都可。因为只有节点才具有ID,所以我们对其ID进行判断,实现只标记节点。

1
2
3
pickId = pointPicker.GetPointId()  # 获取拾取点的ID,无ID返回-1
if pickId != -1:
# 标记代码

主干代码如下:

1
2
3
4
5
6
7
8
def piontPick(self):
dataset = self.main_actor.GetMapper().GetInput()

pointPicker = vtk.vtkPointPicker()
self.interactor.SetPicker(pointPicker) # 设置pointPicker
style = PointPickerInteractorStyle(dataset = dataset) # 设置自定义的点拾取交互类型
style.SetDefaultRenderer(self.renderer)
self.interactor.SetInteractorStyle(style)

这里传入数据对象dataset的原因是实现对不同的模型尺寸动态调节标记尺寸。

结果

下面测试结果演示了标记点随模型整体尺寸动态变化。

![GIF 2022-11-22 16-02-30](D:\OneDrive\桌面\GIF 2022-11-22 16-02-30.gif)

下面测试结果演示了对有限元节点的属性值查询。

![GIF 2022-11-22 15-57-08](D:\OneDrive\桌面\GIF 2022-11-22 15-57-08.gif)

参考

[1] VTK:交互与拾取——点拾取

[2] VTK: vtkKdTree Class Reference

[3] VTK: vtkMath Class Reference

[4] VTK: vtkPointPicker Class Reference