在3D可视化开发中,对象选取是最基础也最关键的交互功能之一。今天要分享的是基于VTK(Python版)实现3D场景中鼠标点击高亮选中对象的技术方案,这个功能在医学影像标注、CAD模型审查等场景中都非常实用。
vtkPropPicker是VTK中专门用于拾取3D对象的工具类,其核心工作原理可以分为三个步骤:
关键提示:Pick()方法的第三个参数z值通常设为0,表示在近裁剪平面进行拾取。如果需要深度拾取,可以调整这个参数。
选中对象的高亮效果通过修改对象属性实现,主要包括:
python复制self.NewPickedActor.GetProperty().SetColor(colors.GetColor3d('Red'))
self.NewPickedActor.GetProperty().SetDiffuse(1.0)
self.NewPickedActor.GetProperty().SetSpecular(0.0)
self.NewPickedActor.GetProperty().EdgeVisibilityOn()
我们继承vtkInteractorStyleTrackballCamera来实现自定义交互逻辑:
python复制class MouseInteractorHighLightActor(vtkInteractorStyleTrackballCamera):
def __init__(self):
self.AddObserver("LeftButtonPressEvent", self.leftButtonPressEvent)
self.LastPickedActor = None # 记录上次选中的对象
self.LastPickedProperty = vtkProperty() # 保存原始属性
def leftButtonPressEvent(self, obj, event):
colors = vtkNamedColors()
clickPos = self.GetInteractor().GetEventPosition()
picker = vtkPropPicker()
picker.Pick(clickPos[0], clickPos[1], 0, self.GetDefaultRenderer())
self.NewPickedActor = picker.GetActor()
if self.NewPickedActor:
# 恢复上一个选中对象的原始属性
if self.LastPickedActor:
self.LastPickedActor.GetProperty().DeepCopy(self.LastPickedProperty)
# 保存新选中对象的原始属性
self.LastPickedProperty.DeepCopy(self.NewPickedActor.GetProperty())
# 设置高亮属性
self.NewPickedActor.GetProperty().SetColor(colors.GetColor3d('Red'))
self.NewPickedActor.GetProperty().SetDiffuse(1.0)
self.NewPickedActor.GetProperty().SetSpecular(0.0)
self.NewPickedActor.GetProperty().EdgeVisibilityOn()
self.LastPickedActor = self.NewPickedActor
# 必须调用父类方法保持默认交互
self.OnLeftButtonDown()
创建包含多个随机球体的测试场景:
python复制def main():
NUMBER_OF_SPHERES = 10
colors = vtkNamedColors()
# 初始化渲染管线
renderer = vtkRenderer()
renderer.SetBackground(colors.GetColor3d("SteelBlue"))
renwin = vtkRenderWindow()
renwin.AddRenderer(renderer)
renwin.SetSize(640, 480)
renwin.SetWindowName('HighlightPickedActor')
interactor = vtkRenderWindowInteractor()
interactor.SetRenderWindow(renwin)
# 设置自定义交互器
style = MouseInteractorHighLightActor()
style.SetDefaultRenderer(renderer)
interactor.SetInteractorStyle(style)
# 生成随机分布的球体
randomSequence = vtkMinimalStandardRandomSequence()
randomSequence.SetSeed(8775070)
for i in range(NUMBER_OF_SPHERES):
source = vtkSphereSource()
x = randomSequence.GetRangeValue(-5.0, 5.0)
y = randomSequence.GetRangeValue(-5.0, 5.0)
z = randomSequence.GetRangeValue(-5.0, 5.0)
radius = randomSequence.GetRangeValue(0.5, 1.0)
source.SetRadius(radius)
source.SetCenter(x, y, z)
source.SetPhiResolution(11)
source.SetThetaResolution(21)
mapper = vtkPolyDataMapper()
mapper.SetInputConnection(source.GetOutputPort())
actor = vtkActor()
actor.SetMapper(mapper)
# 设置随机颜色
r = randomSequence.GetRangeValue(0.4, 1.0)
g = randomSequence.GetRangeValue(0.4, 1.0)
b = randomSequence.GetRangeValue(0.4, 1.0)
actor.GetProperty().SetDiffuseColor(r, g, b)
actor.GetProperty().SetDiffuse(.8)
actor.GetProperty().SetSpecular(.5)
actor.GetProperty().SetSpecularColor(colors.GetColor3d('White'))
actor.GetProperty().SetSpecularPower(30.0)
renderer.AddActor(actor)
interactor.Initialize()
renwin.Render()
interactor.Start()
当场景中包含大量对象时,拾取操作可能成为性能瓶颈。以下是几种优化策略:
基础功能之上可以进一步实现:
python复制# 多对象选择示例代码片段
if self.GetInteractor().GetControlKey(): # 检测Ctrl键
self.SelectedActors.append(self.NewPickedActor)
else:
self.clearSelection()
self.SelectedActors = [self.NewPickedActor]
| 问题现象 | 可能原因 | 解决方案 |
|---|---|---|
| 点击对象边缘无法选中 | 默认拾取容差太小 | 调用picker.SetTolerance(像素值)增大拾取范围 |
| 小对象难以选中 | 对象在屏幕空间占比太小 | 使用vtkCellPicker替代,或临时放大对象进行拾取 |
| 透明对象无法选中 | 透明对象默认不参与拾取 | 设置actor.GetProperty().SetPickable(1)强制参与拾取 |
当同时需要旋转场景和高亮对象时,建议采用以下交互逻辑:
python复制def onLeftButtonPress(self):
if self.GetInteractor().GetEventPosition() == self.GetInteractor().GetLastEventPosition():
# 处理单击高亮
else:
# 处理拖动旋转
super().OnLeftButtonDown()
在医疗影像系统中,我们通常会扩展这个基础功能,添加:
python复制# 医疗影像系统中的典型扩展
def onActorPicked(self, actor):
self.updateMeasurementTools(actor)
self.updateAnnotationPanel(actor)
self.syncOtherViews(actor)