I’m trying to create a simple display which has:
3 Spheres, 1 at center and 2 which move according to a list of positions
I’m trying to simulate a camera attached to each moving object.
In the scene I’m setting up 3 view ports:
Left half is just a static camera
Top right and bottom right are the moving cameras.
Here’s my attempt at doing it in vtk. I’m quite new to graphics and vtk so forgive any glaring mistakes.
main.py
import sys
import numpy as np
from PyQt5.QtWidgets import QApplication, QMainWindow, QVBoxLayout, QWidget, QSlider
from PyQt5.QtCore import QTimer, Qt
from vtkmodules.qt.QVTKRenderWindowInteractor import QVTKRenderWindowInteractor
from vtkmodules.vtkRenderingCore import vtkRenderer, vtkCamera
from actor import SetPositionsActor, Actor
from camera import AnchoredTrackingCamera
class Visualizer(QMainWindow):
def __init__(self, positions):
super().__init__()
print("Initializing Visualizer")
self.positions = positions
self.timer_interval = 1000
self.frame = QWidget()
self.layout = QVBoxLayout()
self.vtk_widget = QVTKRenderWindowInteractor(self.frame)
self.layout.addWidget(self.vtk_widget)
self.slider = QSlider(Qt.Horizontal)
self.slider.setMinimum(10)
self.slider.setMaximum(2000)
self.slider.setValue(self.timer_interval)
self.slider.valueChanged.connect(self.change_timer_interval)
self.layout.addWidget(self.slider)
self.frame.setLayout(self.layout)
self.setCentralWidget(self.frame)
self.render_window = self.vtk_widget.GetRenderWindow()
self.create_actors()
self.setup_cameras()
self.setup_timer()
self.render_window.Render()
def create_actors(self):
# Static actor 1
self.static_actor = Actor([0, 0, 0], 5.0)
# Moving actors 2 and 3
self.actor2 = SetPositionsActor(self.positions[:, 0], 5.0)
self.actor3 = SetPositionsActor(self.positions[:, 1], 5.0)
# Add actors to default renderer
self.default_renderer = vtkRenderer()
self.default_renderer.SetBackground(0.1, 0.2, 0.4) # Blue
self.default_renderer.AddActor(self.static_actor)
self.default_renderer.AddActor(self.actor2)
self.default_renderer.AddActor(self.actor3)
self.render_window.AddRenderer(self.default_renderer)
print(f"Created static actor at (0, 0, 0)")
print(f"Created moving actor 2 at initial position {self.positions[0, 0]}")
print(f"Created moving actor 3 at initial position {self.positions[0, 1]}")
def setup_cameras(self):
self.default_camera = vtkCamera()
self.default_camera.SetPosition(0, 1000, 0)
self.default_camera.SetFocalPoint(0, 0, 0)
self.default_camera.SetViewUp(0, 0, 1) # Ensure consistent up direction
self.default_renderer.SetActiveCamera(self.default_camera)
self.default_renderer.SetViewport(0.0, 0.0, 0.5, 1.0)
self.camera_actor2 = AnchoredTrackingCamera(self.actor2, self.static_actor)
self.camera_actor2.renderer.SetViewport(0.5, 0.5, 1.0, 1.0)
self.camera_actor2.renderer.SetBackground(0.4, 0.2, 0.1) # Brown
self.render_window.AddRenderer(self.camera_actor2.renderer)
self.camera_actor3 = AnchoredTrackingCamera(self.actor3, self.static_actor)
self.camera_actor3.renderer.SetViewport(0.5, 0.0, 1.0, 0.5)
self.camera_actor3.renderer.SetBackground(0.2, 0.4, 0.1) # Green
self.render_window.AddRenderer(self.camera_actor3.renderer)
self.camera_actor2.object.SetViewAngle(30)
self.camera_actor2.object.SetViewUp(0, 1, 0)
self.camera_actor2.update() # Ensure the camera is updated on initialization
self.camera_actor3.object.SetViewAngle(30)
self.camera_actor3.object.SetViewUp(0, 1, 0)
self.camera_actor3.update() # Ensure the camera is updated on initialization
print(
f"Default camera position: {self.default_camera.GetPosition()}, focal point: {self.default_camera.GetFocalPoint()}")
print(
f"Camera 2 position: {self.camera_actor2.object.GetPosition()}, focal point: {self.camera_actor2.object.GetFocalPoint()}")
print(
f"Camera 3 position: {self.camera_actor3.object.GetPosition()}, focal point: {self.camera_actor3.object.GetFocalPoint()}")
def setup_timer(self):
self.timer = QTimer()
self.timer.timeout.connect(self.update_scene)
self.timer.start(self.timer_interval)
def change_timer_interval(self, value):
self.timer_interval = value
self.timer.setInterval(self.timer_interval)
def update_scene(self):
self.actor2.update()
self.actor3.update()
self.camera_actor2.update()
self.camera_actor3.update()
self.render_window.Render()
def start(self):
self.show()
self.vtk_widget.Initialize()
self.vtk_widget.Start()
if __name__ == "__main__":
app = QApplication(sys.argv)
positions = np.array([
[[20, 0, 0], [40, 0, 0]], # Linear movement positions for two moving actors
[[20, 10, 0], [40, 10, 0]],
[[20, 20, 0], [40, 20, 0]],
[[20, 30, 0], [40, 30, 0]],
[[20, 40, 0], [40, 40, 0]],
])
visualizer = Visualizer(positions)
visualizer.start()
sys.exit(app.exec_())
camera.py
from vtk import vtkRenderer, vtkCamera
from actor import Actor
class Camera:
def __init__(self, position):
self.renderer = vtkRenderer()
self.object = vtkCamera()
self.renderer.SetActiveCamera(self.object)
self.set_position(position)
def set_position(self, position):
self.object.SetPosition(*position)
def update(self):
pass
@property
def position(self):
self.update()
return self.object.GetPosition()
class TrackingCamera(Camera):
def __init__(self, position, tracked: Actor):
super().__init__(position)
self.tracked = tracked
@property
def focus(self):
self.update()
return self.tracked.GetPosition()
def update(self):
position = self.tracked.GetPosition()
self.object.SetFocalPoint(*position)
self.object.SetViewUp(0, 1, 0)
self.object.OrthogonalizeViewUp()
print(
f"Updated TrackingCamera: Position: {self.object.GetPosition()}, Focal Point: {self.object.GetFocalPoint()}, ViewUp: {self.object.GetViewUp()}")
class AnchoredTrackingCamera(TrackingCamera):
def __init__(self, anchor: Actor, tracked: Actor):
self.anchor = anchor
super().__init__(anchor.GetPosition(), tracked)
self.update()
def update(self):
position = self.anchor.GetPosition()
self.set_position(position)
super().update()
print(
f"Updated AnchoredTrackingCamera: Position: {self.object.GetPosition()}, Focal Point: {self.object.GetFocalPoint()}, ViewUp: {self.object.GetViewUp()}")
actor.py
from vtk import vtkSphereSource, vtkActor, vtkPolyDataMapper
class Actor(vtkActor):
def __init__(self, init_position, radius):
super().__init__()
self.sphere_source = vtkSphereSource()
self.sphere_source.SetCenter(*init_position)
self.sphere_source.SetRadius(radius)
self.sphere_source.Update()
mapper = vtkPolyDataMapper()
mapper.SetInputConnection(self.sphere_source.GetOutputPort())
self.SetMapper(mapper)
def update(self):
pass
def GetPosition(self):
return self.sphere_source.GetCenter()
class SetPositionsActor(Actor):
def __init__(self, positions, radius):
super().__init__(positions[0], radius)
self.positions = positions
self.index = 0
self.frames = len(positions)
def update(self):
self.index = (self.index + 1) % self.frames
self.sphere_source.SetCenter(*self.positions[self.index])
self.SetPosition(*self.positions[self.index])
self.sphere_source.Update()
The code produces
I was expecting to see at least the center sphere in the views.