Incorrect viewport is zoomed with trame.widgets.vtklocal.LocalView

Hi!

First of all, thank you so much for your work on Trame/VTK/WASM.

I’ve got an issue where zooming while hovering the cursor over different render viewports always zoomes the first renderer. I’m using the LocalView from trame.widgets.vtklocal.

Relevant package versions:

$ python -V
Python 3.13.1
$ pip list | grep -e 'vtk' -e 'trame'
trame                   3.12.0
trame-client            3.10.1
trame-common            1.0.1
trame-components        2.5.0
trame-server            3.6.0
trame-vtk               2.9.1
trame-vtklocal          0.15.2
trame-vuetify           3.0.2
vtk                     9.5.20250531.dev0

Here’s fairly minimal example using the multiple viewports VTK example (https://examples.vtk.org/site/Python/Visualization/MultipleViewports/) and a simple app layout with Trame. Running in a Jupyter kernel (VS Code or split on # %% as individual cells in a Jupyter notebook):

# %%
# noinspection PyUnresolvedReferences
import vtkmodules.vtkInteractionStyle

# noinspection PyUnresolvedReferences
import vtkmodules.vtkRenderingOpenGL2
from vtkmodules.vtkFiltersSources import (
    vtkConeSource,
    vtkCubeSource,
    vtkCylinderSource,
    vtkSphereSource,
)
from vtk import vtkInteractorStyleTrackballCamera
from vtkmodules.vtkRenderingCore import (
    vtkActor,
    vtkPolyDataMapper,
    vtkRenderer,
    vtkRenderWindow,
    vtkRenderWindowInteractor,
)

from trame.app import get_server
from trame.ui.vuetify3 import VAppLayout
from trame.widgets import vtklocal, vtk as trame_vtk, vuetify3


def main(local: bool = True):
    # One render window, multiple viewports.
    rw = vtkRenderWindow()
    iren = vtkRenderWindowInteractor()
    iren.SetInteractorStyle(vtkInteractorStyleTrackballCamera())
    iren.SetRenderWindow(rw)

    # Define viewport ranges.
    xmins = [0, 0.5, 0, 0.5]
    xmaxs = [0.5, 1, 0.5, 1]
    ymins = [0, 0, 0.5, 0.5]
    ymaxs = [0.5, 0.5, 1, 1]

    sources = get_sources()
    for i in range(4):
        ren = vtkRenderer()
        rw.AddRenderer(ren)
        ren.SetViewport(xmins[i], ymins[i], xmaxs[i], ymaxs[i])

        mapper = vtkPolyDataMapper()
        mapper.SetInputConnection(sources[i].GetOutputPort())
        actor = vtkActor()
        actor.SetMapper(mapper)
        ren.AddActor(actor)

        ren.ResetCamera()

    layout = setup_trame(rw, local)

    return layout


def get_sources():
    sources = list()

    # Create a sphere
    sphere = vtkSphereSource()
    sphere.SetCenter(0.0, 0.0, 0.0)
    sphere.Update()
    sources.append(sphere)

    # Create a cone
    cone = vtkConeSource()
    cone.SetCenter(0.0, 0.0, 0.0)
    cone.SetDirection(0, 1, 0)
    cone.Update()
    sources.append(cone)

    # Create a cube
    cube = vtkCubeSource()
    cube.SetCenter(0.0, 0.0, 0.0)
    cube.Update()
    sources.append(cube)

    # Create a cylinder
    cylinder = vtkCylinderSource()
    cylinder.SetCenter(0.0, 0.0, 0.0)
    cylinder.Update()
    sources.append(cylinder)

    return sources


def setup_trame(rw: vtkRenderWindow, local: bool) -> VAppLayout:
    server = get_server(client_type="vue3")
    ctrl = server.controller

    with VAppLayout(server) as layout:
        with vuetify3.VContainer(
            fluid=True, classes="pa-0 fill-height", trame_server=server
        ):
            with vuetify3.VContainer(fluid=True, classes="pa-0 fill-height"):
                if local:
                    setup_local_view(rw, ctrl)
                else:
                    setup_remove_view(rw, ctrl)

    return layout


def setup_local_view(rw: vtkRenderWindow, controller) -> None:
    view = vtklocal.LocalView(rw)
    controller.view_update = view.update_throttle
    controller.view_reset_camera = view.reset_camera


def setup_remove_view(rw: vtkRenderWindow, controller) -> None:
    view = trame_vtk.VtkRemoteView(rw)
    controller.view_update = view.update
    controller.view_reset_camera = view.reset_camera


# %%
layout = main(local=True)
await layout.ready
layout

Result: [Cannot upload a short 7s video as I’m a new user.]

Thanks for bringing that up. I’ve created an issue on the repo here.