I used a netizen’s own tool to complete the mask cutting under remote rendering, the vtkInteractorStyleDrawPolygon code is below.
import numpy as np
import vtkmodules.all as vtk
from vtkmodules.util import numpy_support as nps
from vtkmodules.vtkCommonCore import vtkCommand
class InteractorStyleDrawPolygon(vtk.vtkInteractorStyle):
"""
InteractorStyleDrawPolygon is a re-write of the vtk class of the same name in python.
The python class vtk.vtkInteractorStyleDrawPolygon is not a full wrap of the underlying
++ class. For example, vtkInteractorStyleDrawPolygon doesn't have a method for
'GetPolygonPoints' in Python. It's in the header, source and documentation, but not python.
Reports are this is because it returns a vtkVector2i, a template based generic class, which
is difficult to wrap.
References:
- http://vtk.1045678.n5.nabble.com/Missing-method-for-vtkInteractorStyleDrawPolygon-td5747100.html
- https://gitlab.kitware.com/vtk/vtk/blob/master/Interaction/Style/vtkInteractorStyleDrawPolygon.h
- https://gitlab.kitware.com/vtk/vtk/-/blob/master/Interaction/Style/vtkInteractorStyleDrawPolygon.cxx
"""
class vtkInternal(object):
"""
Class internal to InteractorStyleDrawPolygon to help with drawing the polygon
"""
def __init__(self, parent=None):
super().__init__()
self._points = []
@property
def points(self):
return np.array(self._points)
def AddPoint(self, x, y):
self._points.append([x,y])
def GetPoint(self, index):
if index < 0 or index > len(self._points)-1: return
return np.array(self._points[index])
def GetNumberOfPoints(self):
return len(self._points)
def Clear(self):
self._points = []
def DrawPixels(self, StartPos, EndPos, pixels, size):
# C++ args: const vtkVector2i& StartPos, const vtkVector2i& EndPos, unsigned char* pixels, const int* size
# NOTE: ^ operator = bitwise exclusive OR. Same in C++ and Python
length = int(round(np.linalg.norm(StartPos-EndPos)))
if length == 0: return
x1, y1 = StartPos
x2, y2 = EndPos
x = [int(round(v)) for v in np.linspace(x1,x2,length)]
y = [int(round(v)) for v in np.linspace(y1,y2,length)]
indices = np.array([row * size[0] + col for col,row in zip(x,y)])
pixels[indices] = 255 ^ pixels[indices]
def __init__(self, parent=None, interactor=None, renderer=None):
self.parent = parent
self.interactor = interactor
self.renderer = renderer
self.AddObserver("MouseMoveEvent", self.OnMouseMove)
self.AddObserver("LeftButtonPressEvent", self.OnLeftButtonDown)
self.AddObserver("LeftButtonReleaseEvent", self.OnLeftButtonUp)
self.setup()
def setup(self):
self.Internal = self.vtkInternal()
self.StartPosition = np.zeros(2, dtype=np.int32)
self.EndPosition = np.zeros(2, dtype=np.int32)
self.Moving = False
self.DrawPolygonPixels = True
self.PixelArray = vtk.vtkUnsignedCharArray()
def DrawPolygon(self):
"""
Draw the polygon defined by the mouse move points
"""
tmpPixelArray = vtk.vtkUnsignedCharArray()
tmpPixelArray.DeepCopy(self.PixelArray)
pixels = nps.vtk_to_numpy(tmpPixelArray)
renWin = self.interactor.GetRenderWindow()
size = renWin.GetSize()
# Draw each line segment
for i in range(self.Internal.GetNumberOfPoints()-1):
a = self.Internal.GetPoint(i)
b = self.Internal.GetPoint(i+1)
self.Internal.DrawPixels(a, b, pixels, size)
# Draw a line from the end to the start
if (self.Internal.GetNumberOfPoints() >= 3):
start = self.Internal.GetPoint(0)
end = self.Internal.GetPoint(self.Internal.GetNumberOfPoints()-1)
self.Internal.DrawPixels(start, end, pixels, size)
# NOTE: In SetPixelData, must add 0 as 7th variable (in C++ it has a default
# value of 0, but not in Python)
# NOTE: SetPixelData takes a long time to run, particularly if the screen is
# maximised, which increases the number of pixels
renWin.SetPixelData(0, 0, size[0]-1, size[1]-1, pixels.flatten(), 0, 0)
renWin.Frame()
def DrawPolygonPixelsOn(self):
self.DrawPolygonPixels = True
def DrawPolygonPixelsOff(self):
self.DrawPolygonPixels = False
def GetPolygonPoints(self):
"""
Return the polygon points as a numpy array
NOTE: This function is not available in wrapped Python vtk
"""
return self.Internal.points
def OnLeftButtonDown(self, obj, event):
"""
Left mouse button press event
"""
if self.interactor is None: return
self.Moving = True
renWin = self.interactor.GetRenderWindow()
eventPos = self.interactor.GetEventPosition()
self.StartPosition[0], self.StartPosition[1] = eventPos[0], eventPos[1]
self.EndPosition = self.StartPosition
self.PixelArray.Initialize()
self.PixelArray.SetNumberOfComponents(3)
size = renWin.GetSize()
self.PixelArray.SetNumberOfTuples(size[0] * size[1])
self.pixels = None
# Note: In GetPixelData, must add 0 as 7th variable (in C++ it has a default
# value of 0, but not in Python)
renWin.GetPixelData(0, 0, size[0]-1, size[1]-1, 1, self.PixelArray, 0)
self.Internal.Clear()
self.Internal.AddPoint(self.StartPosition[0], self.StartPosition[1])
self.InvokeEvent(vtk.vtkCommand.StartInteractionEvent)
# Call parent function
#super().OnLeftButtonDown()
def OnLeftButtonUp(self, obj, event):
"""
Left mouse button release event
When LMB is released, a EndPickEvent and EndInteractionEvent are emitted
NOTE: This is different to the C++ class, which emits a SelectionChangedEvent
instead of an EndPickEvent
"""
if self.interactor is None or not self.Moving: return
if self.DrawPolygonPixels:
renWin = self.interactor.GetRenderWindow()
size = renWin.GetSize()
pixels = nps.vtk_to_numpy(self.PixelArray)
renWin.SetPixelData(0, 0, size[0]-1, size[1]-1, pixels.flatten(), 0, 0)
renWin.Frame()
self.Moving = False
self.InvokeEvent(vtkCommand.SelectionChangedEvent)
self.InvokeEvent(vtkCommand.EndPickEvent)
self.InvokeEvent(vtkCommand.EndInteractionEvent)
# Call parent function
#super().OnLeftButtonUp()
def OnMouseMove(self, obj, event):
"""
On mouse move event
"""
if self.interactor is None or not self.Moving: return
# Get lastest mouse position
eventPos = self.interactor.GetEventPosition()
self.EndPosition[0], self.EndPosition[1] = eventPos[0], eventPos[1]
size = self.interactor.GetRenderWindow().GetSize()
if self.EndPosition[0] > size[0]-1: self.EndPosition[0] = size[0]-1
if self.EndPosition[0] < 0: self.EndPosition[0] = 0
if self.EndPosition[1] > size[1]-1: self.EndPosition[1] = size[1]-1
if self.EndPosition[1] < 0: self.EndPosition[1] = 0
# Update the polygon to include the lastest mouse position
lastPoint = self.Internal.GetPoint(self.Internal.GetNumberOfPoints()-1)
newPoint = self.EndPosition
if np.linalg.norm(lastPoint-newPoint) > 10:
self.Internal.AddPoint(*newPoint)
if self.DrawPolygonPixels:
self.DrawPolygon()
# Call parent function
#super().OnMouseMove()
def SetDrawPolygonPixels(self, drawPolygonPixels):
self.DrawPolygonPixels = drawPolygonPixels
def __str__(self):
"""
Replaces PrintSelf in C++ class
"""
indent = 2*' '
s = super().__str__().rstrip()+'\n'
s += f'{indent}Moving : {self.Moving}\n'
s += f'{indent}DrawPolygonPixels: {self.DrawPolygonPixels}\n'
s += f'{indent}StartPosition: {self.StartPosition[0]}, {self.StartPosition[1]}\n'
s += f'{indent}EndPosition: {self.EndPosition[0]}, {self.EndPosition[1]}\n'
return s
when I use the tool to make area selection, why does the program running in the background appear jitter, and changing jitter causes the box selection area to be wrong.
Why does the background window shrink a little when interacting with a web page?