Kitware / trame-vtk

VTK/ParaView widgets for trame
BSD 3-Clause "New" or "Revised" License
18 stars 8 forks source link

view_update might be throttling? #52

Open banesullivan-kobold opened 1 year ago

banesullivan-kobold commented 1 year ago

I'm experiencing an issue where calling view_update() for VtkRemoteView does not always work. It seems like the call might be throttled?

It results in a clunky user interface in this slicing example. After moving the plane widget in the scene on the left, the scene on the right should immediately update. However it does not always update, sometimes requireing a user-click in either of the two views for the one on the right to update

import pyvista as pv
from trame.app import get_server
from trame.ui.vuetify3 import SinglePageLayout
from trame.widgets import vtk as vtk_widgets
from trame.widgets import vuetify3 as vuetify

pv.OFF_SCREEN = True

server = get_server()
state, ctrl = server.state, server.controller

state.trame__title = "Multi Plotters"

mesh = pv.Wavelet()

pl1 = pv.Plotter()
pl1.add_mesh(mesh.contour())
pl1.reset_camera()

pl2 = pv.Plotter()
pl2.add_mesh(mesh.outline(), color='black')
pl2.reset_camera()

def my_callback(normal, origin):
    pl2.add_mesh(mesh.slice(normal, origin), name="slice")
    ctrl.view2_update()  # <-- is this being throttled?

pl1.add_plane_widget(my_callback)

with SinglePageLayout(server) as layout:
    layout.title.set_text("Multi Views")
    layout.icon.click = ctrl.view_reset_camera

    with layout.content:
        with vuetify.VContainer(
            fluid=True,
            classes="pa-0 fill-height",
        ):
            with vuetify.VCol(classes="pa-0 fill-height"):
                view = vtk_widgets.VtkRemoteView(pl1.render_window, ref="view1")
                ctrl.view1_update = view.update
            with vuetify.VCol(classes="pa-0 fill-height"):
                view = vtk_widgets.VtkRemoteView(pl2.render_window, ref="view2")
                ctrl.view2_update = view.update

server.start()

https://github.com/Kitware/trame-vtk/assets/144726278/4962e2a9-c4f0-49c7-8844-42ad6cf5731f

jourdain commented 1 year ago

So it is a timing issue. Both views end up in animation, which makes the view.update() a NoOp. But then the animation get canceled before the encoding queue get fully flushed/consumed.

Can you make the callback happen when you actually drag the widget?

banesullivan-kobold commented 1 year ago

Hm. Using interaction_event='always' (vtk.vtkCommand.InteractionEvent) in PyVista's add_plane_widget() fixes it for this case. However we don't always want to use this as slicing large data sets in this fashion would have serious performance implications.

ref https://docs.pyvista.org/version/stable/api/plotting/_autosummary/pyvista.Plotter.add_plane_widget.html

I'm wondering if there is another VTK event trigger we could use?

Or is there a way to force update the view?

jourdain commented 1 year ago

I was not providing a fix here. I was just explaining what was happening so we/I could figure out a solution down the road when I get back to it. ;-)

banesullivan-kobold commented 1 month ago

What I don't really understand is why the update calls for the second plotter are being swallowed, but not the update events for the first plotter. For instance, if I change this example to use a single render window (single plotter in PyVista), then we can call ctrl.view_update to push an update on that view just fine (see below).

I'm wondering if anything has progressed here in the last year and if there may be a way to now force an update on any VtkRemoteView?

import pyvista as pv
from trame.app import get_server
from trame.ui.vuetify3 import SinglePageLayout
from trame.widgets import vtk as vtk_widgets
from trame.widgets import vuetify3 as vuetify

pv.OFF_SCREEN = True

server = get_server()
state, ctrl = server.state, server.controller

state.trame__title = "Subplot"

mesh = pv.Wavelet()

pl = pv.Plotter(shape=(1, 2))
pl.add_mesh(mesh.contour())

def my_callback(normal, origin):
    pl.subplot(0, 1)
    pl.add_mesh(mesh.slice(normal, origin), name="slice")
    # pl.render()
    ctrl.view_update()  # <-- this works just fine since its the view being interacted with

pl.add_plane_widget(my_callback)

pl.subplot(0, 1)
pl.add_mesh(mesh.outline(), color='black')

pl.link_views()
pl.view_isometric()

with SinglePageLayout(server) as layout:
    layout.title.set_text("Multi Views")
    layout.icon.click = ctrl.view_reset_camera

    with layout.content:
        with vuetify.VContainer(
            fluid=True,
            classes="pa-0 fill-height",
        ):
            view = vtk_widgets.VtkRemoteView(pl.render_window, ref="view1")
            ctrl.view_update = view.update

server.start()

https://github.com/user-attachments/assets/bd2c1d12-2a98-433e-93fb-71aaed3d90fa

jourdain commented 1 month ago

No update on that end. The issue is the async nature and the fact that 2 updates (when 2 rw instead of 1) are interlace which resolve with one that don't fully trigger an update to the client.