超好用!!!VTK几种拾取交互方式的封装(python版),包含Cell、Point、Actor的拾取高亮,可选择单选or多选模式

文章介绍了如何在VTK中封装拾取功能,创建了ActorPickInteractorStyle、CellPickInteractorStyle和PointPickInteractorStyle,以便于点、线段、三角形和点云的选取,提供不同交互方式和颜色设置。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

最近把VTK的拾取功能封装到了InteractorStyle的继承类里面,可以很方便的进行拾取,如下

点选取
Cell选取

源码附上,纯个人手敲和测试,觉得有用的uu别忘点赞收藏一下哈 ,有问题随时评论或私信沟通~(博主python版本为3.9,在函数定义的时候加了一些对参数类型的限制,低于3.9的版本可能会报错,把那些冒号后面的参数类型要求删掉就好)

import vtkmodules.all as vtk
import numpy as np
from typing import Union
from vtkmodules.util.numpy_support import numpy_to_vtk


def genHighlightActor(polydata: vtk.vtkPolyData, cell_id: int, color: tuple[float, float, float],
                      mark_size: int = 4) -> vtk.vtkActor:
    """根据polydata和cell_id生成一个对应的actor"""
    cell = polydata.GetCell(cell_id)
    ptn = cell.GetNumberOfPoints()
    pts = np.zeros((ptn, 3), dtype=np.float64)
    for i in range(ptn):
        point_id = cell.GetPointId(i)
        polydata.GetPoint(point_id, pts[i])
    if ptn == 1:  # 如果是点
        actor = Geometry.genPixelActor(pts[0], mark_size, color)
    elif ptn == 3:  # 如果是三角形
        actor = Geometry.genTriangleFrameActor(pts, mark_size, color)
    else:  # 如果是线段或折线
        actor = Geometry.genWholeBreakLineActor(pts, mark_size, color)
    return actor


class Geometry:
    """几何元素生成函数封装类"""
    @staticmethod
    def genActorFromPolyData(polydata: vtk.vtkPolyData) -> vtk.vtkActor:
        """由polydata生成actor"""
        mapper = vtk.vtkPolyDataMapper()
        mapper.SetInputData(polydata)
        mapper.Update()
        actor = vtk.vtkActor()
        actor.SetMapper(mapper)
        actor.Modified()
        return actor

    @staticmethod
    def genSphereActor(position: Union[tuple[float, float, float], list[float], np.ndarray], radius: float = 2.0,
                       color: tuple[float, float, float] = (1.0, 0.0, 0.0)) -> vtk.vtkActor:
        """以球体形式生成点actor,输入位置、半径、颜色"""
        pt = vtk.vtkSphereSource()
        pt.SetRadius(radius)
        pt.SetCenter(position[0], position[1], position[2])
        pt.SetPhiResolution(20)  # 球体显示的分辨率参数
        pt.SetThetaResolution(20)  # 球体显示的分辨率参数
        pt.Update()
        actor = Geometry.genActorFromPolyData(pt.GetOutput())
        actor.GetProperty().SetColor(color)
        actor.Modified()
        return actor

    @staticmethod
    def genPixelActor(position: Union[tuple[float, float, float], list[float], np.ndarray],
                      size: int = 5, color: tuple[float, float, float] = (1.0, 0.0, 0.0)) -> vtk.vtkActor:
        """以像素点形式生成点actor,输入位置、像素大小、颜色"""
        pts = np.array([position])
        actor = Geometry.genPointCloudActor(pts, size, color)
        return actor

    @staticmethod
    def genLineActor(start_pos: Union[tuple[float, float, float], list[float], np.ndarray],
                     end_pos: Union[tuple[float, float, float], list[float], np.ndarray],
                     line_width: int = 2, color: tuple[float, float, float] = (1.0, 0.0, 0.0)) -> vtk.vtkActor:
        """以像素线段形式生成线actor,可自定义线宽颜色"""
        pts = np.array([start_pos, end_pos])
        actor = Geometry.genWholeBreakLineActor(pts, line_width, color)
        return actor

    @staticmethod
    def genTriangleFrameActor(pts: np.ndarray, line_width: int = 2,
                              color: tuple[float, float, float] = (1.0, 0.0, 0.0)) -> vtk.vtkActor:
        """以线框形式生成三角形actor,可自定义线宽颜色"""
        points = vtk.vtkPoints()
        points.SetData(numpy_to_vtk(np.copy(pts)))
        cells = vtk.vtkCellArray()
        polydata = vtk.vtkPolyData()
        cells.InsertNextCell(4, [0, 1, 2, 0])
        polydata.SetPoints(points)
        polydata.SetLines(cells)
        actor = Geometry.genActorFromPolyData(polydata)
        actor.GetProperty().SetLineWidth(line_width)
        actor.GetProperty().SetColor(color)
        return actor

    @staticmethod
    def genWholeBreakLineActor(pts: np.ndarray, line_width: int = 2,
                               color: tuple[float, float, float] = (1.0, 0.0, 0.0)) -> vtk.vtkActor:
        """生成整段的折线actor,其中只有一个Cell,可自定义线宽颜色"""
        nL = pts.shape[0]
        points = vtk.vtkPoints()
        points.SetData(numpy_to_vtk(np.copy(pts)))
        cells = vtk.vtkCellArray()
        polydata = vtk.vtkPolyData()
        cells.InsertNextCell(nL, [i for i in range(nL)])
        polydata.SetPoints(points)
        polydata.SetLines(cells)
        actor = Geometry.genActorFromPolyData(polydata)
        actor.GetProperty().SetLineWidth(line_width)
        actor.GetProperty().SetColor(color)
        return actor

    @staticmethod
    def genPolyLineActor(pts: np.ndarray, line_width: int = 2,
                         color: tuple[float, float, float] = (1.0, 0.0, 0.0)) -> vtk.vtkActor:
        """生成分段的折线actor,其中每一条线段是一个Cell,可自定义线宽颜色"""
        nL = pts.shape[0]
        points = vtk.vtkPoints()
        points.SetData(numpy_to_vtk(np.copy(pts)))
        cells = vtk.vtkCellArray()
        polydata = vtk.vtkPolyData()
        for i in range(nL - 1):
            cells.InsertNextCell(2, [i, i + 1])
        polydata.SetPoints(points)
        polydata.SetLines(cells)
        actor = Geometry.genActorFromPolyData(polydata)
        actor.GetProperty().SetLineWidth(line_width)
        actor.GetProperty().SetColor(color)
        return actor

    @staticmethod
    def genAxesActor(axes_length: int = 600) -> vtk.vtkAxesActor:
        """坐标轴创建,输入三轴长度"""
        axes = vtk.vtkAxesActor()
        axes.SetTotalLength(axes_length, axes_length, axes_length)  # 设置轴长度
        axes.SetShaftType(1)  # 设置轴类型,1为线0为圆柱

        # axes.SetCylinderRadius(1)  # 设置圆柱半径
        axes.SetConeRadius(0.05)  # 设置箭头大小
        axes.GetXAxisCaptionActor2D().SetWidth(0.03)  # 设置坐标轴标签大小
        axes.GetYAxisCaptionActor2D().SetWidth(0.03)
        axes.GetZAxisCaptionActor2D().SetWidth(0.03)
        return axes

    @staticmethod
    def genPointCloudActor(pts: np.ndarray, size: int = 5,
                           color: tuple[float, float, float] = (1.0, 0.0, 0.0)) -> vtk.vtkActor:
        """生成点云actor,用固定像素大小的点表示,可定义大小与颜色"""
        npt = pts.shape[0]
        points = vtk.vtkPoints()
        points.SetData(numpy_to_vtk(pts))
        cells = vtk.vtkCellArray()
        polydata = vtk.vtkPolyData()
        for i in range(npt):
            cells.InsertNextCell(1, [i])
        polydata.SetPoints(points)
        polydata.SetVerts(cells)
        actor = Geometry.genActorFromPolyData(polydata)
        actor.GetProperty().SetPointSize(size)
        actor.GetProperty().SetColor(color)
        return actor

    @staticmethod
    def genPointCloudSpheres(pts: np.ndarray, radius: float,
                             color: tuple[float, float, float] = (1.0, 0.0, 0.0)) -> list[vtk.vtkActor]:
        """用球体对象组表示点云,可定义半径与颜色"""
        npt = pts.shape[0]
        actor_list = []
        for i in range(npt):
            actor = Geometry.genSphereActor(tuple(pts[i]), radius, color)
            actor_list.append(actor)
        return actor_list


class ActorPickInteractorStyle(vtk.vtkInteractorStyleTrackballCamera):
    """Actor选取交互器,color表示选中高亮actor的颜色,select_mode为0表示单选,select_mode为1表示多选"""
    def __init__(self, parent=None, color: tuple[float, float, float] = (1.0, 1.0, 0.0), select_mode: int = 0):  # parent不能删掉
        super().__init__()
        self.AddObserver("LeftButtonPressEvent", self.__leftButtonPressEvent)
        self.picked_actor_list = []  # 已选取的actor列表
        self.original_color_list = []  # 已选取的actor原始颜色列表,与picked_actor_list对应
        self.color = color
        if select_mode != 0 and select_mode != 1:
            raise ValueError
        self.select_mode = select_mode

    def __leftButtonPressEvent(self, obj, event):  # obj和event参数不能删掉
        """左键单击事件"""
        click_pos = self.GetInteractor().GetEventPosition()  # 获取鼠标左键点击时的屏幕坐标
        picker = vtk.vtkPropPicker()
        picker.Pick(click_pos[0], click_pos[1], 0, self.GetDefaultRenderer())
        picked_actor = picker.GetActor()
        if picked_actor is not None:
            # 如果选择的actor已被选,则复原
            if picked_actor in self.picked_actor_list:
                index = self.picked_actor_list.index(picked_actor)
                self.picked_actor_list[index].GetProperty().SetColor(self.original_color_list[index])
                self.picked_actor_list[index].Modified()
                self.picked_actor_list.pop(index)
                self.original_color_list.pop(index)
            # 如果选择的是新actor
            else:
                # 如果是单选模式,需要先把之前选择的actor复原
                if self.select_mode == 0 and len(self.picked_actor_list) > 0:
                    self.picked_actor_list[0].GetProperty().SetColor(self.original_color_list[0])
                    self.picked_actor_list[0].Modified()
                    self.picked_actor_list.pop(0)
                    self.original_color_list.pop(0)
                # 储存选中actor的原始颜色,并把选中的actor高亮
                picked_color = picked_actor.GetProperty().GetColor()
                self.original_color_list.append(picked_color)
                picked_actor.GetProperty().SetColor(self.color)
                picked_actor.Modified()
                self.picked_actor_list.append(picked_actor)
        self.OnLeftButtonDown()


class CellPickInteractorStyle(vtk.vtkInteractorStyleTrackballCamera):
    """
    Cell选取交互器,mark_size表示选中高亮元素的线宽或点大小,color表示选中高亮颜色,select_mode为0表示单选,select_mode为1表示多选
    easy_to_pick若设置为True,则可以降低选取精度以更方便地选取由线和点组成的单元
    """
    def __init__(self, parent=None, mark_size: int = 4, color: tuple[float, float, float] = (1.0, 0.0, 0.0),
                 select_mode: int = 0, easy_to_pick: bool = False):  # parent不能删掉
        super().__init__()
        self.AddObserver("LeftButtonPressEvent", self.__leftButtonPressEvent)
        self.picked_cellUnit_list = []  # 已选取的[actor, cell_id]列表
        self.highlight_cellActor_list = []  # 已生成的高亮actor列表,与picked_cellUnit_list对应
        self.mark_size = mark_size
        self.color = color
        if select_mode != 0 and select_mode != 1:
            raise ValueError
        self.select_mode = select_mode
        self.easy_to_pick = easy_to_pick

    def __get_selected_cellUnit_index(self, actor: vtk.vtkActor, cell_id: int) -> int:
        """看选的cell是否在已选列表里,是就返回index,不是就返回-1"""
        selected_num = len(self.highlight_cellActor_list)
        if selected_num < 1:
            return -1
        for i in range(selected_num):
            if actor == self.picked_cellUnit_list[i][0] and cell_id == self.picked_cellUnit_list[i][1]:
                return i
        else:
            return -1

    def __leftButtonPressEvent(self, obj, event):  # obj和event参数不能删掉
        """左键单击事件"""
        click_pos = self.GetInteractor().GetEventPosition()  # 获取鼠标左键点击时的屏幕坐标
        picker = vtk.vtkCellPicker()
        if self.easy_to_pick:
            picker.SetTolerance(0.001)  # 设置误差限
        picker.Pick(click_pos[0], click_pos[1], 0, self.GetDefaultRenderer())
        picked_cell_id = picker.GetCellId()
        if picked_cell_id == -1:
            picked_cell_id = None
        if picked_cell_id is not None:
            picked_actor = picker.GetActor()
            index = self.__get_selected_cellUnit_index(picked_actor, picked_cell_id)
            # 如果选择的cell未被选
            if index == -1:
                # 如果是单选模式,需要先把之前选择的cell复原
                if self.select_mode == 0 and len(self.highlight_cellActor_list) > 0:
                    self.GetDefaultRenderer().RemoveActor(self.highlight_cellActor_list[0])
                    self.highlight_cellActor_list.pop(0)
                    self.picked_cellUnit_list.pop(0)
                # 生成高亮actor
                picked_mapper = picked_actor.GetMapper()
                picked_polydata = picked_mapper.GetInput()
                newHighlightActor = genHighlightActor(picked_polydata, picked_cell_id, self.color, self.mark_size)
                newHighlightActor.SetPickable(False)
                newHighlightActor.Modified()
                self.picked_cellUnit_list.append([picked_actor, picked_cell_id])
                self.highlight_cellActor_list.append(newHighlightActor)
                self.GetDefaultRenderer().AddActor(newHighlightActor)
            # 如果选择的cell已被选,则直接复原
            else:
                self.GetDefaultRenderer().RemoveActor(self.highlight_cellActor_list[index])
                self.highlight_cellActor_list.pop(index)
                self.picked_cellUnit_list.pop(index)
        self.GetDefaultRenderer().Modified()
        self.OnLeftButtonDown()


class PointPickInteractorStyle(vtk.vtkInteractorStyleTrackballCamera):
    """Point选取交互器,mark_size表示选中高亮点大小,mode为0表示以像素显示,mode为1表示以球体显示,color表示选中高亮颜色,select_mode为0表示单选,select_mode为1表示多选"""
    def __init__(self, parent=None, pt_size: int = 4, color: tuple[float, float, float] = (1.0, 0.0, 0.0),
                 mode: int = 0, select_mode: int = 0):  # parent不能删掉
        super().__init__()
        self.AddObserver("LeftButtonPressEvent", self.__leftButtonPressEvent)
        self.picked_ptUnit_list = []  # 已选取的[actor, pt_id]列表
        self.highlight_ptActor_list = []  # 已生成的高亮actor列表,与picked_cellUnit_list对应
        self.pt_size = pt_size
        self.color = color
        if select_mode != 0 and select_mode != 1:
            raise ValueError
        if mode != 0 and mode != 1:
            raise ValueError
        self.mode = mode
        self.select_mode = select_mode

    def __get_selected_ptUnit_index(self, actor: vtk.vtkActor, pt_id: int) -> int:
        """看选的cell是否在已选列表里,是就返回index,不是就返回-1"""
        selected_num = len(self.highlight_ptActor_list)
        if selected_num < 1:
            return -1
        for i in range(selected_num):
            if actor == self.picked_ptUnit_list[i][0] and pt_id == self.picked_ptUnit_list[i][1]:
                return i
        else:
            return -1

    def __leftButtonPressEvent(self, obj, event):  # obj和event参数不能删掉
        """左键单击事件"""
        click_pos = self.GetInteractor().GetEventPosition()  # 获取鼠标左键点击时的屏幕坐标
        picker = vtk.vtkPointPicker()
        # picker.SetTolerance(0.001)  # 设置误差限
        picker.Pick(click_pos[0], click_pos[1], 0, self.GetDefaultRenderer())
        picked_pt_id = picker.GetPointId()
        if picked_pt_id == -1:
            picked_pt_id = None
        if picked_pt_id is not None:
            picked_actor = picker.GetActor()
            index = self.__get_selected_ptUnit_index(picked_actor, picked_pt_id)
            # 如果选择的point未被选
            if index == -1:
                # 如果是单选模式,需要先把之前选择的point复原
                if self.select_mode == 0 and len(self.highlight_ptActor_list) > 0:
                    self.GetDefaultRenderer().RemoveActor(self.highlight_ptActor_list[0])
                    self.highlight_ptActor_list.pop(0)
                    self.picked_ptUnit_list.pop(0)
                # 生成高亮actor
                picked_mapper = picked_actor.GetMapper()
                picked_polydata = picked_mapper.GetInput()
                pos = picked_polydata.GetPoint(picked_pt_id)
                if self.mode == 0:
                    newHighlightActor = Geometry.genPixelActor(pos, self.pt_size, self.color)
                else:
                    newHighlightActor = Geometry.genSphereActor(pos, self.pt_size, self.color)
                newHighlightActor.SetPickable(False)
                newHighlightActor.Modified()
                self.picked_ptUnit_list.append([picked_actor, picked_pt_id])
                self.highlight_ptActor_list.append(newHighlightActor)
                self.GetDefaultRenderer().AddActor(newHighlightActor)
            # 如果选择的point已被选,则直接复原
            else:
                self.GetDefaultRenderer().RemoveActor(self.highlight_ptActor_list[index])
                self.highlight_ptActor_list.pop(index)
                self.picked_ptUnit_list.pop(index)
        self.GetDefaultRenderer().Modified()
        self.OnLeftButtonDown()

使用方法:

# 生成两个球体来测试
actor1 = Geometry.genSphereActor(pos=(0., 0., 0.), radius=30, color=(1., 1., 1.))
actor2 = Geometry.genSphereActor(pos=(100., 0., 0.), radius=20, color=(0.7, 0.7, 0.7))

renWin = vtk.vtkRenderWindow()  # 渲染窗口
renWin.SetSize(800, 800)  # 设置窗口大小
ren = vtk.vtkRenderer()  # 渲染器
ren.SetBackground(0.1, 0.1, 0.1)  # 场景背景色
renWin.AddRenderer(ren)

# 添加actor到场景
ren.AddActor(actor1)
ren.AddActor(actor2)
ren.Modified()

style = CellPickInteractorStyle(mark_size=4, color=(1., 0., 0.), select_mode=0)  # 换成上述代码中的任一种style,参数自己按需求定义即可
style.SetDefaultRenderer(ren)
style.Modified()
iren = vtk.vtkRenderWindowInteractor()
iren.SetRenderWindow(renWin)
iren.SetInteractorStyle(style)
renWin.Render()
iren.Initialize()
iren.Start()

您好!要在 Python 中使用 VTK(Visualization Toolkit)进行拾取(Picking),您可以按照以下步骤操作: 1. 导入所需的模块: ```python import vtk ``` 2. 创建 VTK 渲染窗口和交互器: ```python renderer = vtk.vtkRenderer() render_window = vtk.vtkRenderWindow() render_window.AddRenderer(renderer) interactor = vtk.vtkRenderWindowInteractor() interactor.SetRenderWindow(render_window) ``` 3. 创建一个拾取器对象并将其与渲染器关联: ```python picker = vtk.vtkCellPicker() picker.SetTolerance(0.005) # 设置拾取容差 picker.PickFromListOn() # 设置只拾取特定的 actor,若不需要则可忽略此行 renderer.SetPicker(picker) ``` 4. 创建一个 VTK 模型(例如,一个球体): ```python sphere_source = vtk.vtkSphereSource() sphere_mapper = vtk.vtkPolyDataMapper() sphere_mapper.SetInputConnection(sphere_source.GetOutputPort()) sphere_actor = vtk.vtkActor() sphere_actor.SetMapper(sphere_mapper) renderer.AddActor(sphere_actor) ``` 5. 将所有组件添加到渲染窗口中并启动交互: ```python render_window.Render() interactor.Start() ``` 6. 在交互器中注册拾取回调函数,并在回调函数中处理拾取事件: ```python def on_pick(obj, event): picker = obj.GetPicker() picked_point = picker.GetPickPosition() # 获取拾取点的坐标 picked_actor = picker.GetActor() # 获取被拾取的 actor if picked_actor: print("Picked actor:", picked_actor) interactor.AddObserver("LeftButtonPressEvent", on_pick) # 注册拾取回调函数 ``` 以上是一个简单的示例,您可以根据自己的需求进行调整和扩展。拾取器提供了其他有用的方法和属性,可以根据需要进行使用。希望对您有所帮助!如果您还有其他问题,请随时提问。
评论 16
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值