功能
有限元在后处理过程中,我们如果想获取某一个节点的属性数据值,最直接的方法就是点击这个节点,然后显示其属性数据。
代码实现
首先我们需要使用到VTK的点拾取类vtkPointPicker
类。
从需求可知,我们需要与窗口进行交互,所以先自定义一个继承自vtkInteractorStyleTrackballCamera的类(在类中定义了点拾取的交互类型)。我最开始参考了这篇文章VTK:交互与拾取——点拾取的代码,虽然运行成功了,但有些地方似乎不符合预期。第一,其中点拾取代码中有一行为actor->SetScale(0.05);
,即把选中点的圆点标记大小设为常值。带来的结果是,当在一个模型整体尺寸小于这一设定常值(0.05)的时候,标记点会变得特别大(甚至直接覆盖原模型),相反,当模型尺寸远大于这一常值,标记点会变得特别小(很难发现那种)。第二,对于一些不在模型上的点也会被选中并标记,在后处理中,我们希望选中的都是有限元模型节点。对于上述两个问题,我进行了一些优化。优化后的代码如下:
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进行判断,实现只标记节点。
pickId = pointPicker.GetPointId() # 获取拾取点的ID,无ID返回-1
if pickId != -1:
# 标记代码
主干代码如下:
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
的原因是实现对不同的模型尺寸动态调节标记尺寸。
结果
下面测试结果演示了标记点随模型整体尺寸动态变化。
下面测试结果演示了对有限元节点的属性值查询。
参考
[1] VTK:交互与拾取——点拾取
[2] VTK: vtkKdTree Class Reference
[3] VTK: vtkMath Class Reference
[4] VTK: vtkPointPicker Class Reference