Giter VIP home page Giter VIP logo

trame's Introduction

trame: simple, powerful, innovative

Test and Release PyPI PyPI - Downloads

trame - a web framework that weaves together open source components into customized visual analytics easily.

trame is French for

  • the core that ties things together
  • a guide providing the essence of a task

Welcome to trame and 3D visualization

With trame, create stunning, interactive web applications compactly and intuitively.

3D Visualization

With best-in-class VTK and ParaView platforms at its core, trame provides complete control of 3D visualizations and data movements. Developers benefit from a write-once environment while trame simply exposes both local and remote rendering through a single method.

Rich Features

trame leverages existing libraries and tools such as Vuetify, Altair, Vega, deck.gl, VTK, ParaView, and more, to create vivid content for visual analytics applications.

Problem Focused

By relying simply on Python, trame focuses on one's data and associated analysis and visualizations while hiding the complications of web app development.

Desktop to cloud

The resulting trame applications can act as local desktop applications or remote cloud applications both accessed through a browser.

Installing

trame can be installed with pip:

pip install --upgrade trame

Usage

The Trame Tutorial is the place to go to learn how to use the library and start building your own application.

The API Reference documentation provides API-level documentation.

License

trame is made available under the Apache License, Version 2.0. For more details, see LICENSE

Community

Trame | Discussions | Issues | RoadMap| Contact Us

DOI

Enjoying trame?

Share your experience with a testimonial or with a brand approval.

Optional dependencies

When installing trame using pip (pip install trame) you will get the core infrastructure for any trame application to work but more advanced usage may require additional dependencies. The list below captures what may need to add depending on your usage:

  • pywebview : Needed for desktop usage (--app)
  • jupyterlab : Needed to run inside jupyter-lab
  • notebook : Needed to run inside jupyter-notebook
  • requests : Needed when using remote assets such as GDrive files

Environments variables

  • TRAME_LOG_NETWORK : Path to log file for capturing network exchange. (default: None)
  • TRAME_WS_MAX_MSG_SIZE : Maximum size in bytes of any ws message. (default: 10MB)
  • TRAME_WS_HEART_BEAT : Time in second before assuming the server is non-responsive. (default: 30s)

Life cycle callbacks

Life cycle events are directly managed on the application controller and are prefixed with on_*.

  • on_server_ready : All protocols initialized and available for client to connect

  • on_client_connected : Connection established to server

  • on_client_exited : Linked to browser "beforeunload" event

  • on_server_exited : Trame is exiting its event loop

  • on_server_reload : If callback registered it is used for reloading server side modules

Reserved state entries

The shared state allow us to synchronize the server with the client. Rather than creating another mechanism to handle similar needs throughout the application we purposely reuse that state internally. To prevent any conflict with any user we are prefixing our internal variable with trame__*. In general those state values should not be use or changed by the user except for the one listed below:

Read/Write:

  • trame__favicon: Update it to replace the displayed favicon in your browser. The content needs to be an image encoded url.
  • trame__title: Update it to replace your page title (tab name / window name).

Read-only:

  • trame__busy: Provide information if we have pending requests waiting for the server to respond.
  • tts: Template Time Stamp to regenerate sub elements when a template gets updated. Usually used as :key="tts" to force some component rebuild.

trame's People

Contributors

actions-user avatar bigfooted avatar bourdaisj avatar charrisit avatar cjh1 avatar dependabot[bot] avatar jcfr avatar jourdain avatar mntikor avatar psavery avatar purg avatar richardscottoz avatar willdunklin avatar wschroed avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

trame's Issues

Advanced topic documentation

  • How do I make a new module, as a web developer?
  • How do I deploy so that multiple users have their own app? How do I deploy so that multiple users see the same app?

Using trame with jupyterhub

Hi,

I tried to use trame through jupyterhub + batchspawner running the notebook on a SLURM cluster by following the example: https://kitware.github.io/trame/guide/deployment/jupyter.html

It does not work because jupyterhub proxy the machine where the notebook runs, and this machine is not directly accessible.

With the jupyter-server-proxy extension, it is possible to proxy arbitrary ports through juypter (with url /proxy/{port}, and this could be used to solve this issue.
For instance, the bokeh documentation provides a way to proxy the bokeh server in jupyer.

Currently, the trame.app.jupyter.show function cannot be configured to use a .../proxy/{port}/index.html-like url:

src = f"{kwargs.get('protocol', 'http')}://{kwargs.get('host', 'localhost')}:{_server.port}/index.html{params}"

I modified trame.app.jupyter.show to make it work through jupyterhub, by adding an extra url_function argument to pass a function to generate the url from the port number (as in bokeh)

Code
def show(_server, ui=None, url_function=None, **kwargs):
    ...
    def on_ready(**_):
        params = f"?ui={ui}" if ui else ""
        if url_function is None:
            base_url = f"{kwargs.get('protocol', 'http')}://{kwargs.get('host', 'localhost')}:{_server.port}"
        else:
            base_url = url_function(_server.port)
        src = f"{base_url}/index.html{params}"
        ...

For jupyterhub, this can then be used as:

from urllib.parse import urljoin

base_url = urljoin(os.environ.get('JUPYTERHUB_BASE_URL', ''), os.environ.get('JUPYTERHUB_SERVICE_PREFIX', ''))

show(cone.server, url_function=lambda port: f"{base_url}/proxy/{port}")

environment variables set by jupyterhub.

It's also possible to make a show function that is aware of jupyterhub:

Code
def jupyterhub_show(_server, ui=None, **kwargs):
    ...
    def on_ready(**_):
        params = f"?ui={ui}" if ui else ""
        # This requires jupyter-server-proxy extension to be enabled
        base_url = urljoin(
            os.environ.get('JUPYTERHUB_BASE_URL', ''),
            os.environ.get('JUPYTERHUB_SERVICE_PREFIX', ''),
        )
        src = f"{base_url}/proxy/{_server.port}/index.html{params}"
        ...

It would be great to have a simpler way to run trame through jupyterhub than using a patched copy show!
I can propose a PR if you are interested.

"Exception: no view provided: -1" on trame 1.19.1

Describe the bug

On the latest version of trame (1.19.1), the VTK view does not work as on version 1.18.0. The viewer doesn't show up in the UI and I am getting an error from vtkmodules saying:

ERROR:root:Exception raised
ERROR:root:Exception('no view provided: -1')
ERROR:root:Traceback (most recent call last):
  File ".../lib/python3.8/site-packages/wslink/backends/aiohttp/__init__.py", line 371, in onMessage
    results = await asyncio.coroutine(func)(*args, **kwargs)
  File "/usr/lib/python3.8/asyncio/coroutines.py", line 124, in coro
    res = func(*args, **kw)
  File ".../lib/python3.8/site-packages/vtkmodules/web/protocols.py", line 425, in imagePush
    sView = self.getView(options["view"])
  File ".../lib/python3.8/site-packages/vtkmodules/web/protocols.py", line 80, in getView
    raise Exception("no view provided: %s" % vid)
Exception: no view provided: -1

I think it may be because of this commit? ad23c4d

To Reproduce

Minimal code to reproduce:

import vtk
from trame import change, state
from trame.layouts import SinglePageWithDrawer
from trame.html import vuetify
from trame.html.vtk import VtkRemoteLocalView

# -----------------------------------------------------------------------------
# VTK pipeline
# -----------------------------------------------------------------------------

renderer = vtk.vtkRenderer()
renderWindow = vtk.vtkRenderWindow()
renderWindow.AddRenderer(renderer)

renderWindowInteractor = vtk.vtkRenderWindowInteractor()
renderWindowInteractor.SetRenderWindow(renderWindow)
renderWindowInteractor.GetInteractorStyle().SetCurrentStyleToTrackballCamera()

# -----------------------------------------------------------------------------
# Callbacks
# -----------------------------------------------------------------------------
@change("viewMode")
def update_view(viewMode, flush=True, **kwargs):
    html_view.update_image()
    if viewMode == "local":
        html_view.update_geometry()
        if flush:
            # can only flush once protocol is initialized (publish)
            state.flush("viewScene")

# -----------------------------------------------------------------------------
# GUI
# -----------------------------------------------------------------------------

html_view = VtkRemoteLocalView(renderWindow, namespace="view")

def on_ready(**kwargs):
    update_view("local", flush=False)
    html_view.update()
    renderer.ResetCamera()

layout = SinglePageWithDrawer("Trame App", on_ready=on_ready)

with layout.content:
    vuetify.VContainer(
        fluid=True,
        classes="pa-0 fill-height",
        children=[html_view],
    )

if __name__ == "__main__":
    layout.start()

Memory leak on Ubuntu

When I move the view on a simple trame+pyvista view, the memory fills up. Moving the view quickly for long enough makes my whole system freeze. Same happens if I have an animation with the colors changing with time for example.

Steps to reproduce the behavior:

  1. Launch the first example : https://tutorial.pyvista.org/tutorial/09_trame/a_trame_simple.html
  2. Move the view around while inspecting the machine's RAM

Code

import pyvista as pv
from pyvista import examples
from pyvista.trame.ui import plotter_ui
from trame.app import get_server
from trame.ui.vuetify import SinglePageLayout

pv.OFF_SCREEN = True

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

mesh = examples.load_random_hills()

pl = pv.Plotter()
pl.add_mesh(mesh)

with SinglePageLayout(server) as layout:
    with layout.content:
        view = plotter_ui(pl)

server.start()

Expected behavior

The Ram should not increase

Platform: Linux MINT

Device:

  • Desktop
  • [ X ] Mobile

OS:

  • Windows
  • MacOS
  • [ X ] Linux
  • Android
  • iOS

Browsers Affected:

  • [ X ] Chrome
  • [ X ] Firefox
  • Microsoft Edge
  • Safari
  • Opera
  • Brave
  • IE 11

vtkOrientationMarkerWidget fails to render properly or update

Describe the bug

Using vtkOrientationMarkerWidget with trame, the orientation widget does not display properly or update.

I think vtkCameraOrientationWidget is similarly broken (it fails to display at all) but I haven't investigated fully.

To Reproduce

See the code below, which produces the following screenshot:

Screenshot from 2024-01-12 15-46-12

If I uncomment out the lines indicated (this doesn't make sense, just for debugging this), then I first get an interactive window, with the axes displaying properly. After closing this, the trame app appears in a browser window, looking like this:

Screenshot from 2024-01-12 15-55-30

So the axes look correct here, but they still don't move when you change the camera position with the mouse.

Code
The code here is based on a minimal trame app (from https://github.com/Kitware/trame-tutorial/blob/master/01_vtk/solution_cone.py ) combined with https://examples.vtk.org/site/Python/Visualization/DisplayCoordinateAxes/

from trame.app import get_server
from trame.ui.vuetify import SinglePageLayout
from trame.widgets import vtk, vuetify

from vtkmodules.vtkFiltersSources import vtkConeSource
from vtkmodules.vtkRenderingCore import (
    vtkActor,
    vtkPolyDataMapper,
    vtkRenderer,
    vtkRenderWindow,
    vtkRenderWindowInteractor,
)

# Required for interactor initialization
from vtkmodules.vtkInteractionStyle import vtkInteractorStyleSwitch  # noqa

# Required for rendering initialization, not necessary for
# local rendering, but doesn't hurt to include it
import vtkmodules.vtkRenderingOpenGL2  # noqa

from vtkmodules.vtkInteractionWidgets import vtkOrientationMarkerWidget
from vtkmodules.vtkRenderingAnnotation import vtkAxesActor

renderer = vtkRenderer()
renderWindow = vtkRenderWindow()
renderWindow.AddRenderer(renderer)

renderWindowInteractor = vtkRenderWindowInteractor()
renderWindowInteractor.SetRenderWindow(renderWindow)
renderWindowInteractor.GetInteractorStyle().SetCurrentStyleToTrackballCamera()

cone_source = vtkConeSource()
mapper = vtkPolyDataMapper()
mapper.SetInputConnection(cone_source.GetOutputPort())
actor = vtkActor()
actor.SetMapper(mapper)

renderer.AddActor(actor)

axes = vtkAxesActor()

widget = vtkOrientationMarkerWidget()
widget.SetOrientationMarker(axes)
widget.SetInteractor(renderWindowInteractor)
widget.SetViewport(0.0, 0.0, 0.4, 0.4)
widget.SetEnabled(1)
widget.InteractiveOn()

renderer.GetActiveCamera().Azimuth(50)
renderer.GetActiveCamera().Elevation(-30)

renderer.ResetCamera()

# Un-comment these to show a normal VTK app with desired behavior:
# renderWindow.Render()
# renderWindowInteractor.Start()


server = get_server(client_type="vue2")
ctrl = server.controller

with SinglePageLayout(server) as layout:
    layout.title.set_text("Hello trame")

    with layout.content:
        with vuetify.VContainer(
            fluid=True,
            classes="pa-0 fill-height",
        ):
            view = vtk.VtkLocalView(renderWindow)

if __name__ == "__main__":
    server.start()

Expected behavior

It should display an axes that moves when you move the camera around.

Screenshots

If applicable, add screenshots to help explain your problem (drag and drop the image).

Platform:

I have ticked only those that I have checked, but based on Chrome+Firefox on both Windows+Linux being affected and the symptoms, I expect all platforms/devices are affected.

Device:

  • Desktop
  • Mobile

OS:

  • Windows
  • MacOS
  • Linux
  • Android
  • iOS

Browsers Affected:

  • Chrome
  • Firefox
  • Microsoft Edge
  • Safari
  • Opera
  • Brave
  • IE 11

Plotly figure resets unexpectedly

Describe the bug

Upon closing or opening a drawer in an app containing a plotly figure, the figure is reset. For example, if the user zooms into a plotly plot, upon closing or opening a drawer, the axes ranges reset and the plot "unzooms." This behavior is unexpected, as a user may want to zoom into a region of interest in, for example, a scatter plot to better resolve the individual points when making selections. Closing or opening the drawer should not reset the figure, as this requires the user to repeatedly re-zoom into the region of interest โ€” a cumbersome act.

To Reproduce

Steps to reproduce the behavior:

  1. Go to the example here in the Trame repo
  2. Change the following lines
  • Change line 15 from from trame.ui.vuetify import SinglePageLayout to from trame.ui.vuetify import SinglePageWithDrawerLayout
  • Change line 320 from with SinglePageLayout(server) as layout: to with SinglePageWithDrawerLayout(server) as layout:
  • Remove line 322 (layout.icon.click = ctrl.view_reset_camera)
  1. Zoom into the scatter plot on the right hand side
  2. Click the layout icon in the top left hand corner to open or close the drawer
  3. Observe error

The code changes discussed above are implemented below:
Code

r"""
Version for trame 1.x - https://github.com/Kitware/trame/blob/release-v1/examples/VTK/Applications/RemoteSelection/app.py
Delta v1..v2          - https://github.com/Kitware/trame/commit/03f28bb0084490acabf218264b96a1dbb3a17f19
"""

import pandas as pd

# Plotly/chart imports
import plotly.graph_objects as go
import plotly.express as px

# Trame imports
from trame.app import get_server
from trame.assets.remote import HttpFile
from trame.ui.vuetify import SinglePageWithDrawerLayout
from trame.widgets import vuetify, plotly, trame, vtk as vtk_widgets

# VTK imports
from vtkmodules.vtkIOXML import vtkXMLUnstructuredGridReader
from vtkmodules.numpy_interface import dataset_adapter as dsa
from vtkmodules.vtkCommonDataModel import vtkSelection, vtkSelectionNode, vtkDataObject
from vtkmodules.vtkCommonCore import vtkIdTypeArray
from vtkmodules.vtkFiltersExtraction import vtkExtractSelection
from vtkmodules.vtkFiltersGeometry import vtkGeometryFilter
from vtkmodules.vtkRenderingCore import (
    vtkActor,
    vtkDataSetMapper,
    vtkRenderer,
    vtkRenderWindow,
    vtkRenderWindowInteractor,
    vtkHardwareSelector,
    vtkRenderedAreaPicker,
)

from vtkmodules.vtkInteractionStyle import (
    vtkInteractorStyleRubberBandPick,
    vtkInteractorStyleSwitch,
)  # noqa
import vtkmodules.vtkRenderingOpenGL2  # noqa

from vtkmodules.vtkInteractionStyle import vtkInteractorStyleRubberBandPick

# -----------------------------------------------------------------------------
# Data file information
# -----------------------------------------------------------------------------

dataset_file = HttpFile(
    "./data/disk_out_ref.vtu",
    "https://github.com/Kitware/trame/raw/master/examples/data/disk_out_ref.vtu",
    __file__,
)

# -----------------------------------------------------------------------------
# Trame setup
# -----------------------------------------------------------------------------

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


# -----------------------------------------------------------------------------
# VTK
# -----------------------------------------------------------------------------

reader = vtkXMLUnstructuredGridReader()
reader.SetFileName(dataset_file.path)
reader.Update()
dataset = reader.GetOutput()

renderer = vtkRenderer()
renderer.SetBackground(1, 1, 1)
render_window = vtkRenderWindow()
render_window.AddRenderer(renderer)

rw_interactor = vtkRenderWindowInteractor()
rw_interactor.SetRenderWindow(render_window)
rw_interactor.GetInteractorStyle().SetCurrentStyleToTrackballCamera()

interactor_trackball = rw_interactor.GetInteractorStyle()
interactor_selection = vtkInteractorStyleRubberBandPick()
area_picker = vtkRenderedAreaPicker()
rw_interactor.SetPicker(area_picker)

surface_filter = vtkGeometryFilter()
surface_filter.SetInputConnection(reader.GetOutputPort())
surface_filter.SetPassThroughPointIds(True)

mapper = vtkDataSetMapper()
mapper.SetInputConnection(surface_filter.GetOutputPort())
actor = vtkActor()
actor.GetProperty().SetOpacity(0.5)
actor.SetMapper(mapper)

# Selection
selection_extract = vtkExtractSelection()
selection_mapper = vtkDataSetMapper()
selection_mapper.SetInputConnection(selection_extract.GetOutputPort())
selection_actor = vtkActor()
selection_actor.GetProperty().SetColor(1, 0, 1)
selection_actor.GetProperty().SetPointSize(5)
selection_actor.SetMapper(selection_mapper)
selection_actor.SetVisibility(0)

renderer.AddActor(actor)
renderer.AddActor(selection_actor)
renderer.ResetCamera()

selector = vtkHardwareSelector()
selector.SetRenderer(renderer)
selector.SetFieldAssociation(vtkDataObject.FIELD_ASSOCIATION_POINTS)

# vtkDataSet to DataFrame
py_ds = dsa.WrapDataObject(dataset)
pt_data = py_ds.PointData
cols = {}
for name in pt_data.keys():
    array = pt_data[name]
    shp = array.shape
    if len(shp) == 1:
        cols[name] = array
    else:
        for i in range(shp[1]):
            cols[name + "_%d" % i] = array[:, i]
DATAFRAME = pd.DataFrame(cols)
FIELD_NAMES = list(cols.keys())
SELECTED_IDX = []

# -----------------------------------------------------------------------------
# Callbacks
# -----------------------------------------------------------------------------


@state.change("figure_size", "scatter_x", "scatter_y")
def update_figure(figure_size, scatter_x, scatter_y, **kwargs):
    if figure_size is None:
        return

    # Generate figure
    bounds = figure_size.get("size", {})
    fig = px.scatter(
        DATAFRAME,
        x=scatter_x,
        y=scatter_y,
        width=bounds.get("width", 200),
        height=bounds.get("height", 200),
    )

    # Update selection settings
    fig.data[0].update(
        selectedpoints=SELECTED_IDX,
        selected={"marker": {"color": "red"}},
        unselected={"marker": {"opacity": 0.5}},
    )

    # Update chart
    ctrl.update_figure(fig)


# -----------------------------------------------------------------------------


@state.change("vtk_selection")
def update_interactor(vtk_selection, **kwargs):
    if vtk_selection:
        # remote view
        rw_interactor.SetInteractorStyle(interactor_selection)
        interactor_selection.StartSelect()
        # local view
        state.interactorSettings = VIEW_SELECT
    else:
        # remote view
        rw_interactor.SetInteractorStyle(interactor_trackball)
        # local view
        state.interactorSettings = VIEW_INTERACT


# -----------------------------------------------------------------------------


def on_chart_selection(selected_point_idxs):
    global SELECTED_IDX
    SELECTED_IDX = selected_point_idxs if selected_point_idxs else []
    npts = len(SELECTED_IDX)

    ids = vtkIdTypeArray()
    ids.SetNumberOfTuples(npts)
    for idx, p_id in enumerate(SELECTED_IDX):
        ids.SetTuple1(idx, p_id)
        idx += 1

    sel_node = vtkSelectionNode()
    sel_node.GetProperties().Set(
        vtkSelectionNode.CONTENT_TYPE(), vtkSelectionNode.INDICES
    )
    sel_node.GetProperties().Set(vtkSelectionNode.FIELD_TYPE(), vtkSelectionNode.POINT)
    sel_node.SetSelectionList(ids)
    sel = vtkSelection()
    sel.AddNode(sel_node)

    selection_extract.SetInputDataObject(0, py_ds.VTKObject)
    selection_extract.SetInputDataObject(1, sel)
    selection_extract.Update()
    selection_actor.SetVisibility(1)

    # Update 3D view
    ctrl.view_update()


def on_box_selection_change(selection):
    global SELECTED_IDX
    if selection.get("mode") == "remote":
        actor.GetProperty().SetOpacity(1)
        selector.SetArea(
            int(renderer.GetPickX1()),
            int(renderer.GetPickY1()),
            int(renderer.GetPickX2()),
            int(renderer.GetPickY2()),
        )
    elif selection.get("mode") == "local":
        camera = renderer.GetActiveCamera()
        camera_props = selection.get("camera")

        # Sync client view to server one
        camera.SetPosition(camera_props.get("position"))
        camera.SetFocalPoint(camera_props.get("focalPoint"))
        camera.SetViewUp(camera_props.get("viewUp"))
        camera.SetParallelProjection(camera_props.get("parallelProjection"))
        camera.SetParallelScale(camera_props.get("parallelScale"))
        camera.SetViewAngle(camera_props.get("viewAngle"))
        render_window.SetSize(selection.get("size"))

        actor.GetProperty().SetOpacity(1)
        render_window.Render()

        area = selection.get("selection")
        selector.SetArea(
            int(area[0]),
            int(area[2]),
            int(area[1]),
            int(area[3]),
        )

    # Common server selection
    s = selector.Select()
    n = s.GetNode(0)
    ids = dsa.vtkDataArrayToVTKArray(n.GetSelectionData().GetArray("SelectedIds"))
    surface = dsa.WrapDataObject(surface_filter.GetOutput())
    SELECTED_IDX = surface.PointData["vtkOriginalPointIds"][ids].tolist()

    selection_extract.SetInputConnection(surface_filter.GetOutputPort())
    selection_extract.SetInputDataObject(1, s)
    selection_extract.Update()
    selection_actor.SetVisibility(1)
    actor.GetProperty().SetOpacity(0.5)

    # Update scatter plot with selection
    update_figure(**state.to_dict())

    # Update 3D view
    ctrl.view_update()

    # disable selection mode
    state.vtk_selection = False


# -----------------------------------------------------------------------------
# Settings
# -----------------------------------------------------------------------------

DROPDOWN_STYLES = {
    "dense": True,
    "hide_details": True,
    "classes": "px-2",
    "style": "max-width: calc(25vw - 10px);",
}

CHART_STYLE = {
    "style": "position: absolute; left: 50%; transform: translateX(-50%);",
    "display_mode_bar": ("true",),
    "mode_bar_buttons_to_remove": (
        "chart_buttons",
        [
            "toImage",
            "resetScale2d",
            "zoomIn2d",
            "zoomOut2d",
            "toggleSpikelines",
            "hoverClosestCartesian",
            "hoverCompareCartesian",
        ],
    ),
    "display_logo": ("false",),
}

VTK_VIEW_SETTINGS = {
    "interactive_ratio": 1,
    "interactive_quality": 80,
}

VIEW_INTERACT = [
    {"button": 1, "action": "Rotate"},
    {"button": 2, "action": "Pan"},
    {"button": 3, "action": "Zoom", "scrollEnabled": True},
    {"button": 1, "action": "Pan", "alt": True},
    {"button": 1, "action": "Zoom", "control": True},
    {"button": 1, "action": "Pan", "shift": True},
    {"button": 1, "action": "Roll", "alt": True, "shift": True},
]

VIEW_SELECT = [{"button": 1, "action": "Select"}]


# -----------------------------------------------------------------------------
# UI
# -----------------------------------------------------------------------------

state.trame__title = "VTK selection"
ctrl.on_server_ready.add(ctrl.view_update)

with SinglePageWithDrawerLayout(server) as layout:
    layout.title.set_text("VTK & plotly")

    with layout.toolbar as tb:
        tb.dense = True
        vuetify.VSpacer()
        vuetify.VSelect(
            v_model=("scatter_y", FIELD_NAMES[1]),
            items=("fields", FIELD_NAMES),
            **DROPDOWN_STYLES,
        )
        vuetify.VSelect(
            v_model=("scatter_x", FIELD_NAMES[0]),
            items=("fields", FIELD_NAMES),
            **DROPDOWN_STYLES,
        )

    with layout.content:
        with vuetify.VContainer(fluid=True, classes="fill-height pa-0 ma-0"):
            with vuetify.VRow(dense=True, style="height: 100%;"):
                with vuetify.VCol(
                    classes="pa-0",
                    style="border-right: 1px solid #ccc; position: relative;",
                ):
                    view = vtk_widgets.VtkRemoteView(
                        # view = vtk_widgets.VtkLocalView(
                        render_window,
                        box_selection=("vtk_selection",),
                        box_selection_change=(on_box_selection_change, "[$event]"),
                        # For VtkRemoteView
                        **VTK_VIEW_SETTINGS,
                        # For VtkLocalView
                        interactor_settings=("interactorSettings", VIEW_SELECT),
                    )
                    ctrl.view_update = view.update
                    ctrl.view_reset_camera = view.reset_camera
                    vuetify.VCheckbox(
                        small=True,
                        on_icon="mdi-selection-drag",
                        off_icon="mdi-rotate-3d",
                        v_model=("vtk_selection", False),
                        style="position: absolute; top: 0; right: 0; z-index: 1;",
                        dense=True,
                        hide_details=True,
                    )
                with vuetify.VCol(classes="pa-0"):
                    with trame.SizeObserver("figure_size"):
                        html_plot = plotly.Figure(
                            selected=(
                                on_chart_selection,
                                "[$event?.points.map(({pointIndex}) => pointIndex)]",
                            ),
                            **CHART_STYLE,
                        )
                        ctrl.update_figure = html_plot.update

# -----------------------------------------------------------------------------
# Main
# -----------------------------------------------------------------------------

if __name__ == "__main__":
    server.start()

Expected behavior

I expect that the plotly figure should not be reset upon opening or closing the drawer.

Platform:

Device:

  • Desktop
  • Mobile

OS:

  • Windows
  • MacOS
  • Linux
  • Android
  • iOS

Browsers Affected:

  • Chrome
  • Firefox
  • Microsoft Edge
  • Safari
  • Opera
  • Brave
  • IE 11

Altered rendering behavior in client mode

Hello,

I have some python/pyvista code that uses spheres and also makes a point cloud, rendering the points as spheres. The issues I have seen are:

  1. pl.add_mesh seems not to handle specular at all, so this call:
    pl.add_mesh(pv.Sphere(), color='white', point_size=4, smooth_shading=True, specular=0.3, metallic=1, roughness=0, line_width=1.5)
    gives a blinding white sphere.
  2. the points in the point cloud are not rendered as spheres, instead as flat squares.

To Reproduce

Run the code below, in a Jupyter notebook.
I just reinstalled my entire environment so things should be up to date.

Code

import pyvista as pv
# use backend 'trame' not 'client' if you want the possibility of remote remdering, with the dataset left on the server.
pv.set_jupyter_backend('trame')
# pv.set_jupyter_backend('client')
pl = pv.Plotter()
# trame client rendering does not handle specularity. Must use "specular=0" or no specular parameter.
pl.add_mesh(pv.Sphere(), color='white', point_size=4, smooth_shading=True, specular=0.3, metallic=1, roughness=0, line_width=1.5)
point_list_cart = [[1.1,0,0],[0,1.1,0],[0,0,1.1], [0,0,0]]
num_points = len(point_list_cart)
point_cloud_list = [[] for i in range(num_points)]
point_cloud_list = pv.PolyData(point_list_cart)
pl.add_mesh(point_cloud_list, color='red', point_size=200, render_points_as_spheres=True)
# trame client rendering does not handle points as spheres. Defaults to flat squares.
# pl.add_mesh(point_cloud_list, color='red', point_size=200, render_points_as_spheres=False)
pl.show()

Expected behavior

I expect to see a white sphere and three smaller red points rendered as spheres. This works in remote mode on my standalone machine (although the resolution is nasty and gives a fuzzy effect). This does not work in client mode. The code I've attached just uses trame, and switching back and forth between modes in the widget shows the issue.

Screenshots

Doesn't upload unfortunately ...

Platform:

Device:

  • Desktop
  • XX Mobile

OS:

  • Windows
  • XX MacOS
  • Linux
  • Android
  • iOS

Browsers Affected:

  • Chrome
  • XX Firefox
  • Microsoft Edge
  • Safari
  • Opera
  • Brave
  • IE 11

Template slot extension issue

Describe the bug

I am trying to reproduce a simple vuetify code to extends the tool bar with tabs. However using trame produce different result. The app bar gets override by the template slot extension. I suppose the issue is with "v_slot = 'extension'".

To Reproduce
I found this code on internet to add tabs to the App bar in vuetify:

<template>
  <v-app>
    <v-app-bar app color="primary" dark>
      <v-app-bar-nav-icon> </v-app-bar-nav-icon>
      <v-toolbar-title>Coding Beauty</v-toolbar-title>
      <v-spacer></v-spacer>
      <v-btn icon>
        <v-icon>mdi-magnify</v-icon>
      </v-btn>
      //[...]
      <template v-slot:extension>
        <v-tabs align-with-title>
          <v-tab>Tab 1</v-tab>
          <v-tab>Tab 2</v-tab>
          <v-tab>Tab 3</v-tab>
        </v-tabs>
      </template>
    </v-app-bar>
  </v-app>
</template>

<script>
export default {
  name: 'App',
};
</script>

I translated it in python with Trame:
Code

from trame.app import get_server
from trame.ui.vuetify import VAppLayout
from trame.widgets import vuetify

# -----------------------------------------------------------------------------
# Trame setup
# -----------------------------------------------------------------------------

server = get_server()

# -----------------------------------------------------------------------------
# GUI
# -----------------------------------------------------------------------------

# Main page content
with VAppLayout(server) as layout:
    with vuetify.VAppBar(app=True, color='primary', dark=True) as toolbar:
        vuetify.VAppBarNavIcon()
        vuetify.VToolbarTitle('Test')
        vuetify.VSpacer()
        with vuetify.VBtn(icon=True):
            vuetify.VIcon("mdi-magnify")
        # [...]
        with vuetify.Template(v_slot='extension'):
            with vuetify.VTabs(align_with_title=True):
                vuetify.VTab("Tab 1")
                vuetify.VTab("Tab 2")
                vuetify.VTab("Tab 3")

Expected behavior

Screenshot from 2023-01-03 13-36-03

Obtained result
Screenshot from 2023-01-03 13-37-58

Platform:

Trame:

  • Version 1.x
  • Version 2.x

Device:

  • Desktop
  • Mobile

OS:

  • Windows
  • MacOS
  • Linux
  • Android
  • iOS

Browsers Affected:

  • Chrome
  • Firefox
  • Microsoft Edge
  • Safari
  • Opera
  • Brave
  • IE 11

py-web-vue version:
vMAJOR.MINOR.PATCH

App freezes on disconnect when template_name is used

Describe the bug

Trame apps freeze on disconnect whenever a template_name is used. If using the ?reconnect URL option, the button will never appear and the page becomes unresponsive. The issue is most notable with VTK apps but also happens for more simple apps like examples/02_vuetify/01_menu.py

To Reproduce

Code

Take the follow diff for examples/02_vuetify/01_menu.py

diff --git a/examples/02_vuetify/01_menu.py b/examples/02_vuetify/01_menu.py
index e935488..22d7d10 100644
--- a/examples/02_vuetify/01_menu.py
+++ b/examples/02_vuetify/01_menu.py
@@ -27,7 +27,7 @@ def print_item(item):

 state.trame__title = "Menu example"

-with SinglePageLayout(server) as layout:
+with SinglePageLayout(server, template_name="myapp") as layout:
     with layout.toolbar:
         vuetify.VSpacer()
         with vuetify.VMenu():
python examples/02_vuetify/01_menu.py --server

Then go to the URL:

http://localhost:8080/index.html?reconnect&ui=myapp

Expected behavior

On disconnect (closing/reopening my laptop does the trick), I expect to see the reconnection button due to the ?reconnect URL option

Screenshots

If applicable, add screenshots to help explain your problem (drag and drop the image).

Platform:

Trame:

$ pip list | grep trame
trame                         2.2.5
trame-client                  2.4.0
trame-components              2.0.4
trame-deckgl                  2.0.1
trame-markdown                2.0.2
trame-matplotlib              2.0.1
trame-plotly                  2.0.1
trame-rca                     0.3.0
trame-router                  2.0.1
trame-server                  2.7.0
trame-simput                  2.1.0
trame-vega                    2.0.2
trame-vtk                     2.0.13
trame-vuetify                 2.0.2

Device:

  • Desktop
  • Mobile

OS:

  • Windows
  • MacOS
  • Linux
  • Android
  • iOS

Browsers Affected:

  • Chrome
  • Firefox
  • Microsoft Edge
  • Safari
  • Opera
  • Brave
  • IE 11

py-web-vue version:
vMAJOR.MINOR.PATCH

app_local_view.py---------the `nodes_bytes` and `elems_bytes` are `list`s, not `bytes`

Describe the bug
In lines 86-87 of the 'https://github.com/Kitware/trame/blob/releasev1/examples/VTK/Applications/FiniteElementAnalysis/app_local_view.py'
the nodes_bytes and elems_bytes are lists, not bytes

and I insert some code after line 88 to make it runable

To Reproduce

Steps to reproduce the behavior:

  1. Go to 'https://github.com/Kitware/trame/blob/release-v1/examples/VTK/Applications/FiniteElementAnalysis/app_local_view.py',
  2. select data in 'https://github.com/shkiefer/dash_vtk_unstructured/tree/main/data' on Trame
    image
  3. See error
    image

Code
the inserted code in line 88, which joint the bytes in the lists

     if type(nodes_bytes) ==list:
        a = b''
        for i in range(len(nodes_bytes)):
            a += nodes_bytes[i]
        nodes_bytes = a

    if type(elems_bytes) ==list:
        b = b''
        for i in range(len(elems_bytes)):
            b += elems_bytes[i]
        elems_bytes = b

Expected behavior

A clear and concise description of what you expected to happen.

Screenshots

If applicable, add screenshots to help explain your problem (drag and drop the image).

Platform:

Trame:

  • Version 1.x
  • Version 2.x

Device:

  • Desktop
  • Mobile

OS:

  • Windows
  • MacOS
  • Linux
  • Android
  • iOS

Browsers Affected:

  • Chrome
  • Firefox
  • Microsoft Edge
  • Safari
  • Opera
  • Brave
  • IE 11

py-web-vue version:
vMAJOR.MINOR.PATCH

Segmentation fault after WebSocket connection

Continuation of #145!

I am currently trying to run trame/pv-visualizer on our JupyterLab instance using a JupyterServerProxy
After starting the proxy, the webpage is loading properly for a few seconds. But after that, the UI can only be seen for a frame before the connection is lost.

[I 2022-11-24 23:40:44.960 SingleUserNotebookApp handlers:446] Trying to establish websocket connection to ws://localhost:54753/ws
[I 2022-11-24 23:40:44.963 SingleUserNotebookApp handlers:453] Websocket connection established to ws://localhost:54753/ws

Loguru caught a signal: SIGSEGV
Stack trace:
85            0x4006ce _start + 46
84      0x14dd31dbbcf3 __libc_start_main + 243
83      0x14dd3363fc29 Py_BytesMain + 41
82      0x14dd33660166 Py_RunMain + 326
81      0x14dd3364cb0c PyRun_SimpleStringFlags + 60
80      0x14dd335cd424 PyRun_StringFlags + 164
79      0x14dd3364fc57 /p/software/juwels/stages/2022/software/Python/3.9.6-GCCcore-11.2.0/lib/libpython3.9.so.1.0(+0x1b9c57) [0x14dd3364fc57]
78      0x14dd33651974 /p/software/juwels/stages/2022/software/Python/3.9.6-GCCcore-11.2.0/lib/libpython3.9.so.1.0(+0x1bb974) [0x14dd33651974]
77      0x14dd335b129b PyEval_EvalCode + 27
76      0x14dd335b12e3 PyEval_EvalCodeEx + 67
75      0x14dd335b1341 _PyEval_EvalCodeWithName + 81
74      0x14dd335b1534 /p/software/juwels/stages/2022/software/Python/3.9.6-GCCcore-11.2.0/lib/libpython3.9.so.1.0(+0x11b534) [0x14dd335b1534]
73      0x14dd335b4302 _PyEval_EvalFrameDefault + 7394
72      0x14dd335c168d _PyFunction_Vectorcall + 221
71      0x14dd335b1534 /p/software/juwels/stages/2022/software/Python/3.9.6-GCCcore-11.2.0/lib/libpython3.9.so.1.0(+0x11b534) [0x14dd335b1534]
70      0x14dd335b70fd _PyEval_EvalFrameDefault + 19165
69      0x14dd335cb2aa PyVectorcall_Call + 170
68      0x14dd335cac59 /p/software/juwels/stages/2022/software/Python/3.9.6-GCCcore-11.2.0/lib/libpython3.9.so.1.0(+0x134c59) [0x14dd335cac59]
67      0x14dd335c168d _PyFunction_Vectorcall + 221
66      0x14dd335b1534 /p/software/juwels/stages/2022/software/Python/3.9.6-GCCcore-11.2.0/lib/libpython3.9.so.1.0(+0x11b534) [0x14dd335b1534]
65      0x14dd335b70fd _PyEval_EvalFrameDefault + 19165
64      0x14dd335cb2aa PyVectorcall_Call + 170
63      0x14dd335c168d _PyFunction_Vectorcall + 221
62      0x14dd335b1534 /p/software/juwels/stages/2022/software/Python/3.9.6-GCCcore-11.2.0/lib/libpython3.9.so.1.0(+0x11b534) [0x14dd335b1534]
61      0x14dd335b70fd _PyEval_EvalFrameDefault + 19165
60      0x14dd335cb2aa PyVectorcall_Call + 170
59      0x14dd335c168d _PyFunction_Vectorcall + 221
58      0x14dd335b1534 /p/software/juwels/stages/2022/software/Python/3.9.6-GCCcore-11.2.0/lib/libpython3.9.so.1.0(+0x11b534) [0x14dd335b1534]
57      0x14dd335b2b7f _PyEval_EvalFrameDefault + 1375
56      0x14dd335c168d _PyFunction_Vectorcall + 221
55      0x14dd335b1534 /p/software/juwels/stages/2022/software/Python/3.9.6-GCCcore-11.2.0/lib/libpython3.9.so.1.0(+0x11b534) [0x14dd335b1534]
54      0x14dd335b2d29 _PyEval_EvalFrameDefault + 1801
53      0x14dd335c1923 /p/software/juwels/stages/2022/software/Python/3.9.6-GCCcore-11.2.0/lib/libpython3.9.so.1.0(+0x12b923) [0x14dd335c1923]
52      0x14dd335b2d29 _PyEval_EvalFrameDefault + 1801
51      0x14dd335c1923 /p/software/juwels/stages/2022/software/Python/3.9.6-GCCcore-11.2.0/lib/libpython3.9.so.1.0(+0x12b923) [0x14dd335c1923]
50      0x14dd335b2d29 _PyEval_EvalFrameDefault + 1801
49      0x14dd335c1923 /p/software/juwels/stages/2022/software/Python/3.9.6-GCCcore-11.2.0/lib/libpython3.9.so.1.0(+0x12b923) [0x14dd335c1923]
48      0x14dd335b2d29 _PyEval_EvalFrameDefault + 1801
47      0x14dd335c1923 /p/software/juwels/stages/2022/software/Python/3.9.6-GCCcore-11.2.0/lib/libpython3.9.so.1.0(+0x12b923) [0x14dd335c1923]
46      0x14dd335b85b3 _PyEval_EvalFrameDefault + 24467
45      0x14dd335ba75a /p/software/juwels/stages/2022/software/Python/3.9.6-GCCcore-11.2.0/lib/libpython3.9.so.1.0(+0x12475a) [0x14dd335ba75a]
44      0x14dd336385c6 /p/software/juwels/stages/2022/software/Python/3.9.6-GCCcore-11.2.0/lib/libpython3.9.so.1.0(+0x1a25c6) [0x14dd336385c6]
43      0x14dd335bb8c3 _PyObject_MakeTpCall + 131
42      0x14dd229ae378 /p/software/juwels/stages/2022/software/Python/3.9.6-GCCcore-11.2.0/lib/python3.9/lib-dynload/_asyncio.cpython-39-x86_64-linux-gnu.so(+0x8378) [0x14dd229ae378]
41      0x14dd229ac8f4 /p/software/juwels/stages/2022/software/Python/3.9.6-GCCcore-11.2.0/lib/python3.9/lib-dynload/_asyncio.cpython-39-x86_64-linux-gnu.so(+0x68f4) [0x14dd229ac8f4]
40      0x14dd335e6032 /p/software/juwels/stages/2022/software/Python/3.9.6-GCCcore-11.2.0/lib/libpython3.9.so.1.0(+0x150032) [0x14dd335e6032]
39      0x14dd335b4d6c _PyEval_EvalFrameDefault + 10060
38      0x14dd335e6032 /p/software/juwels/stages/2022/software/Python/3.9.6-GCCcore-11.2.0/lib/libpython3.9.so.1.0(+0x150032) [0x14dd335e6032]
37      0x14dd335b4d6c _PyEval_EvalFrameDefault + 10060
36      0x14dd335e6032 /p/software/juwels/stages/2022/software/Python/3.9.6-GCCcore-11.2.0/lib/libpython3.9.so.1.0(+0x150032) [0x14dd335e6032]
35      0x14dd335b4d6c _PyEval_EvalFrameDefault + 10060
34      0x14dd335e6032 /p/software/juwels/stages/2022/software/Python/3.9.6-GCCcore-11.2.0/lib/libpython3.9.so.1.0(+0x150032) [0x14dd335e6032]
33      0x14dd335b70fd _PyEval_EvalFrameDefault + 19165
32      0x14dd335c168d _PyFunction_Vectorcall + 221
31      0x14dd335b1534 /p/software/juwels/stages/2022/software/Python/3.9.6-GCCcore-11.2.0/lib/libpython3.9.so.1.0(+0x11b534) [0x14dd335b1534]
30      0x14dd335b2d29 _PyEval_EvalFrameDefault + 1801
29      0x14dd335c168d _PyFunction_Vectorcall + 221
28      0x14dd335b1534 /p/software/juwels/stages/2022/software/Python/3.9.6-GCCcore-11.2.0/lib/libpython3.9.so.1.0(+0x11b534) [0x14dd335b1534]
27      0x14dd335b2d29 _PyEval_EvalFrameDefault + 1801
26      0x14dd335c1923 /p/software/juwels/stages/2022/software/Python/3.9.6-GCCcore-11.2.0/lib/libpython3.9.so.1.0(+0x12b923) [0x14dd335c1923]
25      0x14dd335b48a4 _PyEval_EvalFrameDefault + 8836
24      0x14dd335bb8c3 _PyObject_MakeTpCall + 131
23      0x14dd335cc4a0 /p/software/juwels/stages/2022/software/Python/3.9.6-GCCcore-11.2.0/lib/libpython3.9.so.1.0(+0x1364a0) [0x14dd335cc4a0]
22      0x14dcd16fa3cd /p/usersoftware/swmanage/goebbert1/stage2022/ParaView/easybuild/juwels/software/ParaView/5.11.0-gpsmkl-2021b/lib64/python3.9/site-packages/paraview/modules/vtkPVClientWeb.so(+0x43cd) [0x14dcd16fa3cd]
21      0x14dcd16ee191 vtkPVWebApplication::StillRenderToBuffer(vtkSMViewProxy*, unsigned long, int) + 17
20      0x14dcd16eddc2 vtkPVWebApplication::StillRender(vtkSMViewProxy*, int) + 370
19      0x14dd0050c011 vtkSMViewProxy::CaptureWindow(int, int) + 81
18      0x14dd0050ba76 /p/usersoftware/swmanage/goebbert1/stage2022/ParaView/easybuild/juwels/software/ParaView/5.11.0-gpsmkl-2021b/lib/libvtkRemotingViews-pv5.11.so.1(+0x2eca76) [0x14dd0050ba76]
17      0x14dd0050b675 /p/usersoftware/swmanage/goebbert1/stage2022/ParaView/easybuild/juwels/software/ParaView/5.11.0-gpsmkl-2021b/lib/libvtkRemotingViews-pv5.11.so.1(+0x2ec675) [0x14dd0050b675]
16      0x14dd0050ae24 vtkSMViewProxy::CaptureWindowSingle(int, int) + 52
15      0x14dd0050a7a2 vtkSMViewProxy::CaptureWindowInternal(int, int) + 770
14      0x14dd0050a21a vtkSMViewProxy::StillRender() + 362
13      0x14dd04e03764 vtkPVSessionBase::ExecuteStream(unsigned int, vtkClientServerStream const&, bool) + 52
12      0x14dd04e0585b vtkPVSessionCore::ExecuteStreamInternal(vtkClientServerStream const&, bool) + 251
11      0x14dd2237257d vtkClientServerInterpreter::ProcessStream(vtkClientServerStream const&) + 29
10      0x14dd223720ed vtkClientServerInterpreter::ProcessOneMessage(vtkClientServerStream const&, int) + 221
9       0x14dd22371fae vtkClientServerInterpreter::ProcessCommandInvoke(vtkClientServerStream const&, int) + 1214
8       0x14dcfd8a3950 vtkPVRenderViewCommand(vtkClientServerInterpreter*, vtkObjectBase*, char const*, vtkClientServerStream const&, vtkClientServerStream&, void*) + 6608
7       0x14dd0043c977 vtkPVRenderView::StillRender() + 103
6       0x14dd00449fc5 vtkPVRenderView::Render(bool, bool) + 2373
5       0x14dd0477b920 vtkXOpenGLRenderWindow::Render() + 32
4       0x14dd046ceb99 vtkOpenGLRenderWindow::Render() + 169
3       0x14dd0572ed68 vtkRenderWindow::Render() + 584
2       0x14dd0436e779 vtkXRenderWindowInteractor::Initialize() + 217
1       0x14dd07cf689b XSync + 11
0       0x14dd32ad9ce0 /usr/lib64/libpthread.so.0(+0x12ce0) [0x14dd32ad9ce0]
(  16.615s) [paraview        ]                       :0     FATL| Signal: SIGSEGV
/p/home/jusers/windgassen1/juwels/Nek/proxy-venv/lib/python3.9/site-packages/jupyter_trame_proxy/share/launch_trame.sh: line 20: 16088 Segmentation fault      python -c "from pv_visualizer.app.main import main; main(port=$1)"
2022-11-24 23:40:47,646 - SingleUserNotebookApp - ERROR - Uncaught exception GET /user/j.windgassen_at_fz-juelich.de/vf0a98375c2247efb8baa17a2ed3d3ce/trame/ws (79.232.197.221)
HTTPServerRequest(protocol='https', host='jupyter-jsc.fz-juelich.de', method='GET', uri='/user/j.windgassen_at_fz-juelich.de/vf0a98375c2247efb8baa17a2ed3d3ce/trame/ws', version='HTTP/1.1', remote_ip='79.232.197.221')
Traceback (most recent call last):
  File "/p/software/juwels/stages/2022/software/Jupyter/2022.3.4-gcccoremkl-11.2.0-2021.4.0/lib/python3.9/site-packages/tornado/websocket.py", line 1086, in write_message
    fut = self._write_frame(True, opcode, message, flags=flags)
  File "/p/software/juwels/stages/2022/software/Jupyter/2022.3.4-gcccoremkl-11.2.0-2021.4.0/lib/python3.9/site-packages/tornado/websocket.py", line 1061, in _write_frame
    return self.stream.write(frame)
  File "/p/software/juwels/stages/2022/software/Jupyter/2022.3.4-gcccoremkl-11.2.0-2021.4.0/lib/python3.9/site-packages/tornado/iostream.py", line 530, in write
    self._check_closed()
  File "/p/software/juwels/stages/2022/software/Jupyter/2022.3.4-gcccoremkl-11.2.0-2021.4.0/lib/python3.9/site-packages/tornado/iostream.py", line 1019, in _check_closed
    raise StreamClosedError(real_error=self.error)
tornado.iostream.StreamClosedError: Stream is closed

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/p/software/juwels/stages/2022/software/Jupyter/2022.3.4-gcccoremkl-11.2.0-2021.4.0/lib/python3.9/site-packages/tornado/websocket.py", line 635, in _run_callback
    result = callback(*args, **kwargs)
  File "/p/software/juwels/stages/2022/software/Jupyter/2022.3.4-gcccoremkl-11.2.0-2021.4.0/lib/python3.9/site-packages/jupyter_server_proxy/handlers.py", line 160, in on_message
    self.ws.write_message(message, binary=isinstance(message, bytes))
  File "/p/software/juwels/stages/2022/software/Jupyter/2022.3.4-gcccoremkl-11.2.0-2021.4.0/lib/python3.9/site-packages/tornado/websocket.py", line 1500, in write_message
    return self.protocol.write_message(message, binary=binary)
  File "/p/software/juwels/stages/2022/software/Jupyter/2022.3.4-gcccoremkl-11.2.0-2021.4.0/lib/python3.9/site-packages/tornado/websocket.py", line 1088, in write_message
    raise WebSocketClosedError()
tornado.websocket.WebSocketClosedError

Screenshot 2022-11-24 at 23 50 47

I tried digging around in the code for an afternoon and finding out, where it goes south, but couldn't find anything. I was previously using ParaView 5.9 and Python 3.8 (i modified the code a bit to run there) and blamed it on those versions, but we just installed ParaView 5.11 and this error still occurs there with Python 3.9

Add `__version__` attribute to Trame packages

>>> import trame
>>> trame.__version__
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: module 'trame' has no attribute '__version__'
>>>

None of the Trame packages have a version attribute which makes it a bit difficult to dynamically tell which version of trame is installed.

This can be implemented in a handful of ways as not to have to hardcode the version string. For example:

from pkg_resources import DistributionNotFound, get_distribution

try:
    __version__ = get_distribution("trame").version
except DistributionNotFound:  # pragma: no cover
    # package is not installed
    __version__ = None

Or as a user, I could:

>>> from pkg_resources import get_distribution
>>> get_distribution('trame')
trame 2.2.5 (/opt/miniconda3/envs/pyvista-dev/lib/python3.9/site-packages)
>>> get_distribution('trame').version
'2.2.5'
>>>

But having a __version__ attribute is pretty standard and expected in Python packaging

Can not forward events without a current renderer on the interactor

Describe the bug
Hi, I found a bug, but it doesn't have to happen on every machine any times,
Sometimes it works, sometimes throw this: Can not forward events without a current renderer on the interactor.
I has test many times many way by variable of control, but still can not find a pattern.
As I know, one colleagues of mine run the program in terminal, never throw this bug.
But another colleague of mine need run the program by java thread, this bug happend by this way, but sometimes it works.
So we try to run the program in the same machine in terminal, as my first colleagues, he never got this bug, but it still throw in our machine!

I google this message, found this.
fa7141a1db04347c33a97ac57b08c58

But I can not imagine why getCurrentRenderer() dont work well.
So, I came here, hoping for some possible leads, maybe can help.

sorry for my english, I come from china. I hope I've described it correctly

Code

# just same as examples

Screenshots
e0e42c575f19fb9f465e1e3359bab77

Platform:
Windows 10

Trame:

  • [*] Version 2.x

Device:

  • [*] Desktop
  • Mobile

OS:

  • [*] Windows

Browsers Affected:

  • [*] Chrome

Setting actor `UserMatrix` transformation renders nothing in local view

Describe the bug

When using the UserMatrix property of an Actor to transform the actor upon rendering, the local view of the actor disappears.

To Reproduce

I took the trame-tutorial/04_application/solution.py to exemplify this and added a zoom matrix to set as mesh_actor.SetUserMatrix(matrix) right before renderer.AddActor(mesh_actor)

Code

import os
import numpy as np

from trame.app import get_server
from trame.ui.vuetify import SinglePageWithDrawerLayout
from trame.widgets import vtk, vuetify

from vtkmodules.vtkCommonMath import vtkMatrix4x4
from vtkmodules.vtkCommonDataModel import vtkDataObject
from vtkmodules.vtkFiltersCore import vtkContourFilter
from vtkmodules.vtkIOXML import vtkXMLUnstructuredGridReader
from vtkmodules.vtkRenderingAnnotation import vtkCubeAxesActor

from vtkmodules.vtkRenderingCore import (
    vtkActor,
    vtkDataSetMapper,
    vtkRenderer,
    vtkRenderWindow,
    vtkRenderWindowInteractor,
)

# Required for interactor initialization
from vtkmodules.vtkInteractionStyle import vtkInteractorStyleSwitch  # noqa

# Required for rendering initialization, not necessary for
# local rendering, but doesn't hurt to include it
import vtkmodules.vtkRenderingOpenGL2  # noqa

CURRENT_DIRECTORY = os.path.abspath(os.path.dirname(__file__))

# -----------------------------------------------------------------------------
# Constants
# -----------------------------------------------------------------------------


class Representation:
    Points = 0
    Wireframe = 1
    Surface = 2
    SurfaceWithEdges = 3


class LookupTable:
    Rainbow = 0
    Inverted_Rainbow = 1
    Greyscale = 2
    Inverted_Greyscale = 3


# -----------------------------------------------------------------------------
# VTK pipeline
# -----------------------------------------------------------------------------

renderer = vtkRenderer()
renderWindow = vtkRenderWindow()
renderWindow.AddRenderer(renderer)

renderWindowInteractor = vtkRenderWindowInteractor()
renderWindowInteractor.SetRenderWindow(renderWindow)
renderWindowInteractor.GetInteractorStyle().SetCurrentStyleToTrackballCamera()

# Read Data
reader = vtkXMLUnstructuredGridReader()
reader.SetFileName(os.path.join(CURRENT_DIRECTORY, "../data/disk_out_ref.vtu"))
reader.Update()

# Extract Array/Field information
dataset_arrays = []
fields = [
    (reader.GetOutput().GetPointData(), vtkDataObject.FIELD_ASSOCIATION_POINTS),
    (reader.GetOutput().GetCellData(), vtkDataObject.FIELD_ASSOCIATION_CELLS),
]
for field in fields:
    field_arrays, association = field
    for i in range(field_arrays.GetNumberOfArrays()):
        array = field_arrays.GetArray(i)
        array_range = array.GetRange()
        dataset_arrays.append(
            {
                "text": array.GetName(),
                "value": i,
                "range": list(array_range),
                "type": association,
            }
        )
default_array = dataset_arrays[0]
default_min, default_max = default_array.get("range")

# Mesh
mesh_mapper = vtkDataSetMapper()
mesh_mapper.SetInputConnection(reader.GetOutputPort())
mesh_actor = vtkActor()
mesh_actor.SetMapper(mesh_mapper)

matrix = vtkMatrix4x4()
z = np.eye(4)
np.fill_diagonal(z[:3,:3], 1e3)

m, n = z.shape
for i in range(m):
    for j in range(n):
        matrix.SetElement(i, j, z[i, j])
mesh_actor.SetUserMatrix(matrix)

renderer.AddActor(mesh_actor)

# Mesh: Setup default representation to surface
mesh_actor.GetProperty().SetRepresentationToSurface()
mesh_actor.GetProperty().SetPointSize(1)
mesh_actor.GetProperty().EdgeVisibilityOff()

# Mesh: Apply rainbow color map
mesh_lut = mesh_mapper.GetLookupTable()
mesh_lut.SetHueRange(0.666, 0.0)
mesh_lut.SetSaturationRange(1.0, 1.0)
mesh_lut.SetValueRange(1.0, 1.0)
mesh_lut.Build()

# Mesh: Color by default array
mesh_mapper.SelectColorArray(default_array.get("text"))
mesh_mapper.GetLookupTable().SetRange(default_min, default_max)
if default_array.get("type") == vtkDataObject.FIELD_ASSOCIATION_POINTS:
    mesh_mapper.SetScalarModeToUsePointFieldData()
else:
    mesh_mapper.SetScalarModeToUseCellFieldData()
mesh_mapper.SetScalarVisibility(True)
mesh_mapper.SetUseLookupTableScalarRange(True)

# Contour
contour = vtkContourFilter()
contour.SetInputConnection(reader.GetOutputPort())
contour_mapper = vtkDataSetMapper()
contour_mapper.SetInputConnection(contour.GetOutputPort())
contour_actor = vtkActor()
contour_actor.SetMapper(contour_mapper)
renderer.AddActor(contour_actor)

# Contour: ContourBy default array
contour_value = 0.5 * (default_max + default_min)
contour.SetInputArrayToProcess(
    0, 0, 0, default_array.get("type"), default_array.get("text")
)
contour.SetValue(0, contour_value)

# Contour: Setup default representation to surface
contour_actor.GetProperty().SetRepresentationToSurface()
contour_actor.GetProperty().SetPointSize(1)
contour_actor.GetProperty().EdgeVisibilityOff()

# Contour: Apply rainbow color map
contour_lut = contour_mapper.GetLookupTable()
contour_lut.SetHueRange(0.666, 0.0)
contour_lut.SetSaturationRange(1.0, 1.0)
contour_lut.SetValueRange(1.0, 1.0)
contour_lut.Build()

# Contour: Color by default array
contour_mapper.SelectColorArray(default_array.get("text"))
contour_mapper.GetLookupTable().SetRange(default_min, default_max)
if default_array.get("type") == vtkDataObject.FIELD_ASSOCIATION_POINTS:
    contour_mapper.SetScalarModeToUsePointFieldData()
else:
    contour_mapper.SetScalarModeToUseCellFieldData()
contour_mapper.SetScalarVisibility(True)
contour_mapper.SetUseLookupTableScalarRange(True)

# Cube Axes
cube_axes = vtkCubeAxesActor()
renderer.AddActor(cube_axes)

# Cube Axes: Boundaries, camera, and styling
cube_axes.SetBounds(mesh_actor.GetBounds())
cube_axes.SetCamera(renderer.GetActiveCamera())
cube_axes.SetXLabelFormat("%6.1f")
cube_axes.SetYLabelFormat("%6.1f")
cube_axes.SetZLabelFormat("%6.1f")
cube_axes.SetFlyModeToOuterEdges()

renderer.ResetCamera()

# -----------------------------------------------------------------------------
# Trame setup
# -----------------------------------------------------------------------------

server = get_server()
server.client_type = "vue2"
state, ctrl = server.state, server.controller

state.setdefault("active_ui", None)

# -----------------------------------------------------------------------------
# Callbacks
# -----------------------------------------------------------------------------


@state.change("cube_axes_visibility")
def update_cube_axes_visibility(cube_axes_visibility, **kwargs):
    cube_axes.SetVisibility(cube_axes_visibility)
    ctrl.view_update()


# Selection Change
def actives_change(ids):
    _id = ids[0]
    if _id == "1":  # Mesh
        state.active_ui = "mesh"
    elif _id == "2":  # Contour
        state.active_ui = "contour"
    else:
        state.active_ui = "nothing"


# Visibility Change
def visibility_change(event):
    _id = event["id"]
    _visibility = event["visible"]

    if _id == "1":  # Mesh
        mesh_actor.SetVisibility(_visibility)
    elif _id == "2":  # Contour
        contour_actor.SetVisibility(_visibility)
    ctrl.view_update()


# Representation Callbacks
def update_representation(actor, mode):
    property = actor.GetProperty()
    if mode == Representation.Points:
        property.SetRepresentationToPoints()
        property.SetPointSize(5)
        property.EdgeVisibilityOff()
    elif mode == Representation.Wireframe:
        property.SetRepresentationToWireframe()
        property.SetPointSize(1)
        property.EdgeVisibilityOff()
    elif mode == Representation.Surface:
        property.SetRepresentationToSurface()
        property.SetPointSize(1)
        property.EdgeVisibilityOff()
    elif mode == Representation.SurfaceWithEdges:
        property.SetRepresentationToSurface()
        property.SetPointSize(1)
        property.EdgeVisibilityOn()


@state.change("mesh_representation")
def update_mesh_representation(mesh_representation, **kwargs):
    update_representation(mesh_actor, mesh_representation)
    ctrl.view_update()


@state.change("contour_representation")
def update_contour_representation(contour_representation, **kwargs):
    update_representation(contour_actor, contour_representation)
    ctrl.view_update()


# Color By Callbacks
def color_by_array(actor, array):
    _min, _max = array.get("range")
    mapper = actor.GetMapper()
    mapper.SelectColorArray(array.get("text"))
    mapper.GetLookupTable().SetRange(_min, _max)
    if array.get("type") == vtkDataObject.FIELD_ASSOCIATION_POINTS:
        mesh_mapper.SetScalarModeToUsePointFieldData()
    else:
        mesh_mapper.SetScalarModeToUseCellFieldData()
    mapper.SetScalarVisibility(True)
    mapper.SetUseLookupTableScalarRange(True)


@state.change("mesh_color_array_idx")
def update_mesh_color_by_name(mesh_color_array_idx, **kwargs):
    array = dataset_arrays[mesh_color_array_idx]
    color_by_array(mesh_actor, array)
    ctrl.view_update()


@state.change("contour_color_array_idx")
def update_contour_color_by_name(contour_color_array_idx, **kwargs):
    array = dataset_arrays[contour_color_array_idx]
    color_by_array(contour_actor, array)
    ctrl.view_update()


# Color Map Callbacks
def use_preset(actor, preset):
    lut = actor.GetMapper().GetLookupTable()
    if preset == LookupTable.Rainbow:
        lut.SetHueRange(0.666, 0.0)
        lut.SetSaturationRange(1.0, 1.0)
        lut.SetValueRange(1.0, 1.0)
    elif preset == LookupTable.Inverted_Rainbow:
        lut.SetHueRange(0.0, 0.666)
        lut.SetSaturationRange(1.0, 1.0)
        lut.SetValueRange(1.0, 1.0)
    elif preset == LookupTable.Greyscale:
        lut.SetHueRange(0.0, 0.0)
        lut.SetSaturationRange(0.0, 0.0)
        lut.SetValueRange(0.0, 1.0)
    elif preset == LookupTable.Inverted_Greyscale:
        lut.SetHueRange(0.0, 0.666)
        lut.SetSaturationRange(0.0, 0.0)
        lut.SetValueRange(1.0, 0.0)
    lut.Build()


@state.change("mesh_color_preset")
def update_mesh_color_preset(mesh_color_preset, **kwargs):
    use_preset(mesh_actor, mesh_color_preset)
    ctrl.view_update()


@state.change("contour_color_preset")
def update_contour_color_preset(contour_color_preset, **kwargs):
    use_preset(contour_actor, contour_color_preset)
    ctrl.view_update()


# Opacity Callbacks
@state.change("mesh_opacity")
def update_mesh_opacity(mesh_opacity, **kwargs):
    mesh_actor.GetProperty().SetOpacity(mesh_opacity)
    ctrl.view_update()


@state.change("contour_opacity")
def update_contour_opacity(contour_opacity, **kwargs):
    contour_actor.GetProperty().SetOpacity(contour_opacity)
    ctrl.view_update()


# Contour Callbacks
@state.change("contour_by_array_idx")
def update_contour_by(contour_by_array_idx, **kwargs):
    array = dataset_arrays[contour_by_array_idx]
    contour_min, contour_max = array.get("range")
    contour_step = 0.01 * (contour_max - contour_min)
    contour_value = 0.5 * (contour_max + contour_min)
    contour.SetInputArrayToProcess(0, 0, 0, array.get("type"), array.get("text"))
    contour.SetValue(0, contour_value)

    # Update UI
    state.contour_min = contour_min
    state.contour_max = contour_max
    state.contour_value = contour_value
    state.contour_step = contour_step

    # Update View
    ctrl.view_update()


@state.change("contour_value")
def update_contour_value(contour_value, **kwargs):
    contour.SetValue(0, float(contour_value))
    ctrl.view_update()


# -----------------------------------------------------------------------------
# GUI elements
# -----------------------------------------------------------------------------


def standard_buttons():
    vuetify.VCheckbox(
        v_model=("cube_axes_visibility", True),
        on_icon="mdi-cube-outline",
        off_icon="mdi-cube-off-outline",
        classes="mx-1",
        hide_details=True,
        dense=True,
    )
    vuetify.VCheckbox(
        v_model="$vuetify.theme.dark",
        on_icon="mdi-lightbulb-off-outline",
        off_icon="mdi-lightbulb-outline",
        classes="mx-1",
        hide_details=True,
        dense=True,
    )
    vuetify.VCheckbox(
        v_model=("viewMode", "local"),
        on_icon="mdi-lan-disconnect",
        off_icon="mdi-lan-connect",
        true_value="local",
        false_value="remote",
        classes="mx-1",
        hide_details=True,
        dense=True,
    )
    with vuetify.VBtn(icon=True, click="$refs.view.resetCamera()"):
        vuetify.VIcon("mdi-crop-free")


# def pipeline_widget():
    # trame.GitTree(
    #     sources=(
    #         "pipeline",
    #         [
    #             {"id": "1", "parent": "0", "visible": 1, "name": "Mesh"},
    #             {"id": "2", "parent": "1", "visible": 1, "name": "Contour"},
    #         ],
    #     ),
    #     actives_change=(actives_change, "[$event]"),
    #     visibility_change=(visibility_change, "[$event]"),
    # )


def ui_card(title, ui_name):
    with vuetify.VCard(v_show=f"active_ui == '{ui_name}'"):
        vuetify.VCardTitle(
            title,
            classes="grey lighten-1 py-1 grey--text text--darken-3",
            style="user-select: none; cursor: pointer",
            hide_details=True,
            dense=True,
        )
        content = vuetify.VCardText(classes="py-2")
    return content


def mesh_card():
    with ui_card(title="Mesh", ui_name="mesh"):
        vuetify.VSelect(
            # Representation
            v_model=("mesh_representation", Representation.Surface),
            items=(
                "representations",
                [
                    {"text": "Points", "value": 0},
                    {"text": "Wireframe", "value": 1},
                    {"text": "Surface", "value": 2},
                    {"text": "SurfaceWithEdges", "value": 3},
                ],
            ),
            label="Representation",
            hide_details=True,
            dense=True,
            outlined=True,
            classes="pt-1",
        )
        with vuetify.VRow(classes="pt-2", dense=True):
            with vuetify.VCol(cols="6"):
                vuetify.VSelect(
                    # Color By
                    label="Color by",
                    v_model=("mesh_color_array_idx", 0),
                    items=("array_list", dataset_arrays),
                    hide_details=True,
                    dense=True,
                    outlined=True,
                    classes="pt-1",
                )
            with vuetify.VCol(cols="6"):
                vuetify.VSelect(
                    # Color Map
                    label="Colormap",
                    v_model=("mesh_color_preset", LookupTable.Rainbow),
                    items=(
                        "colormaps",
                        [
                            {"text": "Rainbow", "value": 0},
                            {"text": "Inv Rainbow", "value": 1},
                            {"text": "Greyscale", "value": 2},
                            {"text": "Inv Greyscale", "value": 3},
                        ],
                    ),
                    hide_details=True,
                    dense=True,
                    outlined=True,
                    classes="pt-1",
                )
        vuetify.VSlider(
            # Opacity
            v_model=("mesh_opacity", 1.0),
            min=0,
            max=1,
            step=0.1,
            label="Opacity",
            classes="mt-1",
            hide_details=True,
            dense=True,
        )


def contour_card():
    with ui_card(title="Contour", ui_name="contour"):
        vuetify.VSelect(
            # Contour By
            label="Contour by",
            v_model=("contour_by_array_idx", 0),
            items=("array_list", dataset_arrays),
            hide_details=True,
            dense=True,
            outlined=True,
            classes="pt-1",
        )
        vuetify.VSlider(
            # Contour Value
            v_model=("contour_value", contour_value),
            min=("contour_min", default_min),
            max=("contour_max", default_max),
            step=("contour_step", 0.01 * (default_max - default_min)),
            label="Value",
            classes="my-1",
            hide_details=True,
            dense=True,
        )
        vuetify.VSelect(
            # Representation
            v_model=("contour_representation", Representation.Surface),
            items=(
                "representations",
                [
                    {"text": "Points", "value": 0},
                    {"text": "Wireframe", "value": 1},
                    {"text": "Surface", "value": 2},
                    {"text": "SurfaceWithEdges", "value": 3},
                ],
            ),
            label="Representation",
            hide_details=True,
            dense=True,
            outlined=True,
            classes="pt-1",
        )
        with vuetify.VRow(classes="pt-2", dense=True):
            with vuetify.VCol(cols="6"):
                vuetify.VSelect(
                    # Color By
                    label="Color by",
                    v_model=("contour_color_array_idx", 0),
                    items=("array_list", dataset_arrays),
                    hide_details=True,
                    dense=True,
                    outlined=True,
                    classes="pt-1",
                )
            with vuetify.VCol(cols="6"):
                vuetify.VSelect(
                    # Color Map
                    label="Colormap",
                    v_model=("contour_color_preset", LookupTable.Rainbow),
                    items=(
                        "colormaps",
                        [
                            {"text": "Rainbow", "value": 0},
                            {"text": "Inv Rainbow", "value": 1},
                            {"text": "Greyscale", "value": 2},
                            {"text": "Inv Greyscale", "value": 3},
                        ],
                    ),
                    hide_details=True,
                    dense=True,
                    outlined=True,
                    classes="pt-1",
                )
        vuetify.VSlider(
            # Opacity
            v_model=("contour_opacity", 1.0),
            min=0,
            max=1,
            step=0.1,
            label="Opacity",
            classes="mt-1",
            hide_details=True,
            dense=True,
        )


# -----------------------------------------------------------------------------
# GUI
# -----------------------------------------------------------------------------

with SinglePageWithDrawerLayout(server) as layout:
    layout.title.set_text("Viewer")

    with layout.toolbar:
        # toolbar components
        vuetify.VSpacer()
        vuetify.VDivider(vertical=True, classes="mx-2")
        standard_buttons()

    with layout.drawer as drawer:
        # drawer components
        drawer.width = 325
        # pipeline_widget()
        vuetify.VDivider(classes="mb-2")
        mesh_card()
        contour_card()

    with layout.content:
        # content components
        with vuetify.VContainer(
            fluid=True,
            classes="pa-0 fill-height",
        ):
            # view = vtk.VtkRemoteView(renderWindow, interactive_ratio=1)
            # view = vtk.VtkLocalView(renderWindow)
            view = vtk.VtkRemoteLocalView(
                renderWindow, namespace="view", mode="local", interactive_ratio=1
            )
            ctrl.view_update = view.update
            ctrl.view_reset_camera = view.reset_camera

# -----------------------------------------------------------------------------
# Main
# -----------------------------------------------------------------------------

if __name__ == "__main__":
    server.start()

Expected behavior

I would expect to be able to transform the actor in either view.

Screenshots

In LocalView
image

In RemoteView
image

Platform:

Device:

  • Desktop
  • Mobile

OS:

  • Windows
  • MacOS
  • Linux
  • Android
  • iOS

Browsers Affected:

  • Chrome
  • Firefox
  • Microsoft Edge
  • Safari
  • Opera
  • Brave
  • IE 11

I am using:

trame==3.2.4
trame-client==2.11.3
trame-server==2.11.7
trame-vtk==2.5.8
trame-vuetify==2.3.1

User testimonial

The goal of this issue is to capture positive feedback from the community so we can feature them on our web site.

  • It is an opportunity for us to learn what you are doing with Trame and what made trame special for you.
  • It is an opportunity for you to say thank you to the dev team. They love hearing what you have been up to and what you enjoyed about trame. That help motivate them.

Feel free to share comments, links, pictures, video, social media posts.

And thank you again for sharing your experience!

Contributing.md improvements

We might help potential contributors by specifying:

  • The types of contributions weโ€™re looking for
  • The roadmap

For example, is the core considered finished? Are we looking for more modules?

Failing event binding with vtkRemoteView

Describe the bug
Under the following conditions, the error message JS Error => RangeError: Maximum call stack size exceeded is produced.
1.) Use of vtkRemoteView() (this error is not produced for vtkLocalView())
2.) an arbitrary function is bound to an event, such as LeftButtonDown
3.) the contents of the event are passed as an input to the function
4.) the event is triggered

To Reproduce

Steps to reproduce the behavior:

  1. Run the script below (an MRE using a two-segment polyline passed to a tube filter as an actor)
  2. Click anywhere in the display
  3. See error

Code

import vtk
from trame.app import get_server
from trame.widgets import vuetify, vtk as vtk_widgets
from trame.ui.vuetify import SinglePageLayout

from vtkmodules.vtkRenderingCore import (
    vtkActor,
    vtkPolyDataMapper,
    vtkRenderer,
    vtkRenderWindow,
    vtkRenderWindowInteractor, 
)
from vtkmodules.vtkFiltersCore import vtkTubeFilter
from vtkmodules.vtkInteractionStyle import (
    vtkInteractorStyleTrackballCamera) 

import vtkmodules.vtkRenderingOpenGL2  # noqa (needed for vtkHardwareSelector)

# -----------------------------------------------------------------------------
# Generate dataset
# -----------------------------------------------------------------------------

points = vtk.vtkPoints()
points.SetNumberOfPoints(4)
line = vtk.vtkLine()
lines = vtk.vtkCellArray()

# create first line segment
points.SetPoint(0, 0, 0, 0)
line.GetPointIds().SetId(0, 0)

points.SetPoint(1, 1, 1, 1)
line.GetPointIds().SetId(1, 1)

lines.InsertNextCell(line)

# create second line segment
points.SetPoint(2, 1, 1, 1)
line.GetPointIds().SetId(0, 2)

points.SetPoint(3, 2, 2, 2)
line.GetPointIds().SetId(1, 3)

lines.InsertNextCell(line)

linesPolyData = vtk.vtkPolyData()
linesPolyData.SetPoints(points)
linesPolyData.SetLines(lines)

# -----------------------------------------------------------------------------
# Trame initialization
# -----------------------------------------------------------------------------

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

# -----------------------------------------------------------------------------
# Interactions
# -----------------------------------------------------------------------------

def foo(pickData): 
    print("hello")

# -----------------------------------------------------------------------------
# VTK pipeline
# -----------------------------------------------------------------------------

tubes_filter = vtkTubeFilter()
tubes_filter.SetInputData(linesPolyData)
tubes_filter.SetRadius(2)
tubes_filter.SetNumberOfSides(3)
tubes_filter.Update()

mapper = vtkPolyDataMapper()
mapper.SetInputConnection(tubes_filter.GetOutputPort())
actor = vtkActor()
actor.SetMapper(mapper)

renderer = vtkRenderer()
renderer.SetBackground(1, 1, 1)
renderer.AddActor(actor)

render_window = vtkRenderWindow()
render_window.AddRenderer(renderer)

rw_interactor = vtkRenderWindowInteractor()
rw_interactor.SetRenderWindow(render_window)
rw_interactor.GetInteractorStyle().SetCurrentStyleToTrackballCamera()

renderer.ResetCamera()

# -----------------------------------------------------------------------------
# GUI layout
# -----------------------------------------------------------------------------

with SinglePageLayout(server) as layout:
    with layout.content:
        with vuetify.VContainer(fluid=True, classes="fill-height pa-0 ma-0"):
                view = vtk_widgets.VtkRemoteView(
                    render_window,
                    interactor_events=("events", ["LeftButtonPress"]), 
                    LeftButtonPress=(foo, "[$event]")
                )
                view.update
                view.reset_camera

# -----------------------------------------------------------------------------
# Main
# -----------------------------------------------------------------------------

if __name__ == "__main__":
    server.start(port=0)

Expected behavior

I expect that, upon clicking anywhere in the display, "hello" will be printed.
Screenshots

If applicable, add screenshots to help explain your problem (drag and drop the image).

Platform:

Trame:

  • Version 1.x
  • Version 2.x

Device:

  • Desktop
  • Mobile

OS:

  • Windows
  • MacOS
  • Linux
  • Android
  • iOS

Browsers Affected:

  • Chrome
  • Firefox
  • Microsoft Edge
  • Safari
  • Opera
  • Brave
  • IE 11

Connection lost page should attempt a retry

Is your feature request related to a problem? Please describe.

The connection lost screen after killing/relaunching the server appears as though it is trying to reconnect with the spinning icon:

2022-09-16 16 28 10

But it actually does nothing (as far as I can tell). So I have to refresh the page.

Describe the solution you'd like

I'd like this to attempt to reconnect to my app and load when it's ready or not using this sort of misleading graphic

render unstructuredGrid data

I want to render my .vtk files, it's unstructuredGrid data. but I got none on web.
my .vtk files is lv_fiber.vtk
Code

import os
from trame.app import get_server
from trame.ui.vuetify import SinglePageLayout
from trame.widgets import vtk as vtk2, vuetify
import vtk

from vtkmodules.vtkCommonColor import vtkNamedColors
from vtkmodules.vtkCommonCore import vtkLookupTable
from vtkmodules.vtkFiltersCore import (
    vtkContourFilter,
    vtkGlyph3D,
    vtkMaskPoints,
    vtkThresholdPoints,
)
from vtkmodules.vtkFiltersModeling import vtkOutlineFilter
from vtkmodules.vtkFiltersSources import vtkConeSource
from vtkmodules.vtkIOLegacy import vtkStructuredPointsReader
from vtkmodules.vtkIOLegacy import vtkUnstructuredGridReader
from vtkmodules.vtkRenderingCore import (
    vtkActor,
    vtkPolyDataMapper,
    vtkRenderer,
    vtkRenderWindow,
    vtkRenderWindowInteractor,
)

# Required for interactor initialization
from vtkmodules.vtkInteractionStyle import vtkInteractorStyleSwitch  # noqa

# Required for rendering initialization, not necessary for
# local rendering, but doesn't hurt to include it
import vtkmodules.vtkRenderingOpenGL2  # noqa

CURRENT_DIRECTORY = os.path.abspath(os.path.dirname(__file__))

# -----------------------------------------------------------------------------
# VTK pipeline
# -----------------------------------------------------------------------------

renderer = vtkRenderer()
renderWindow = vtkRenderWindow()
renderWindow.AddRenderer(renderer)

renderWindowInteractor = vtkRenderWindowInteractor()
renderWindowInteractor.SetRenderWindow(renderWindow)
renderWindowInteractor.GetInteractorStyle().SetCurrentStyleToTrackballCamera()

# Read the data

# reader = vtkStructuredPointsReader()
reader = vtkUnstructuredGridReader()

reader.SetFileName(os.path.join(CURRENT_DIRECTORY, "../data/01_strocchi_LV.vtk"))
reader.Update()
# 2. ๅˆ›ๅปบ็ฎญๅคดๆบ
arrow_source = vtk.vtkArrowSource()
arrow_source.Update()
# 3. ๅˆ›ๅปบ็ฎญๅคดๆ˜ ๅฐ„ๅ™จ
arrow_mapper = vtk.vtkGlyph3DMapper()
arrow_mapper.SetInputConnection(reader.GetOutputPort())
arrow_mapper.SetSourceConnection(arrow_source.GetOutputPort())
arrow_mapper.SetScaleFactor(1.0)  # ่ฐƒๆ•ด็ฎญๅคดๅคงๅฐ
arrow_mapper.ScalingOn()
arrow_mapper.Update()
# 4. ๅˆ›ๅปบ็ฎญๅคดๆผ”ๅ‘˜
arrow_actor = vtk.vtkActor()
arrow_actor.SetMapper(arrow_mapper)
# 6. ๅˆ›ๅปบๆธฒๆŸ“็ช—ๅฃไบคไบ’ๅ™จ
render_interactor = vtk.vtkRenderWindowInteractor()
render_interactor.SetRenderWindow(renderWindow)
# 7. ๆทปๅŠ ็ฎญๅคดๆผ”ๅ‘˜ๅˆฐๆธฒๆŸ“ๅ™จ
renderer.AddActor(arrow_actor)
# 8. ่ฎพ็ฝฎๆธฒๆŸ“ๅ™จ่ƒŒๆ™ฏ้ขœ่‰ฒๅ’Œ็›ธๆœบไฝ็ฝฎ
renderer.SetBackground(1.0, 1.0, 1.0) 
# 9. ่ฎพ็ฝฎ็ฎญๅคดๅŠ่ฝด็š„ๅฐบๅฏธ
arrow_source.SetTipLength(0.1)  # ็ฎญๅคดๅฐ–้ƒจ็š„้•ฟๅบฆ
arrow_source.SetTipRadius(0.05)  # ็ฎญๅคดๅฐ–้ƒจ็š„ๅŠๅพ„
arrow_source.SetShaftRadius(0.02)  # ็ฎญๅคดๆ†็š„ๅŠๅพ„

renderer.ResetCamera()
renderWindow.Render()

# -----------------------------------------------------------------------------
# GUI
# -----------------------------------------------------------------------------

server = get_server()
server.client_type = "vue2"
ctrl = server.controller

with SinglePageLayout(server) as layout:
    layout.title.set_text("Hello1 trame")

    with layout.content:
        with vuetify.VContainer(
            fluid=True,
            classes="pa-0 fill-height",
        ):
            view = vtk2.VtkLocalView(renderWindow)


# -----------------------------------------------------------------------------
# Main
# -----------------------------------------------------------------------------

if __name__ == "__main__":
    server.start()

Expected behavior

Display the following image in your browser

Screenshots

image

Platform:

Device:

  • Desktop
  • Mobile

OS:

  • Windows
  • MacOS
  • Linux
  • Android
  • iOS

Browsers Affected:

  • Chrome : 106.0.5249.61
  • Firefox
  • Microsoft Edge
  • Safari
  • Opera
  • Brave
  • IE 11

Trame unexpectedly affects the render window and vtkWindowToImageFilter

Describe the bug

Trame is doing something to the render window that makes vtkWindowToImageFilter produce incorrect images with repeated patches.

To Reproduce

Slightly modify the examples/validation/core/24_vtk_download_image.py example to use vtkWindowToImageFilter.SetScale to magnify the saved screenshot.

I can only reproduce this when using trame... otherwise vtkWindowToImageFilter works perfectly fine for me. This makes me think that the mechanism for how trame captures a screenshot is altering the render window in an unexpected way.

I ran into this while working on pyvista/pyvista#3897. It seems that when a render window is paired with vtkRemoteView (or vtkRemoteLocalView) these repeated patches occur.

diff --git a/examples/validation/core/24_vtk_download_image.py b/examples/validation/core/24_vtk_download_image.py
index 7ec4605..d55bdbf 100644
--- a/examples/validation/core/24_vtk_download_image.py
+++ b/examples/validation/core/24_vtk_download_image.py
@@ -56,6 +56,7 @@ def update_resolution(resolution, **kwargs):
 def download_screenshot():
     w2if = vtkWindowToImageFilter()
     w2if.SetInput(renderWindow)
+    w2if.SetScale(3)
     w2if.SetInputBufferTypeToRGB()
     w2if.ReadFrontBufferOff()
     #

Expected behavior

The screenshots shouldn't have these repeated patches and don't when using vtkWindowToImageFilter in any other context

Screenshots

vtk

Platform:

Device:

  • Desktop
  • Mobile

OS:

  • Windows
  • MacOS
  • Linux
  • Android
  • iOS

Browsers Affected:

  • Chrome
  • Firefox
  • Microsoft Edge
  • Safari
  • Opera
  • Brave
  • IE 11

Different Color in Local and Remote Rendering

Describe the bug

I tried the application example in the tutorial. But I found a very strange result, the color of the local rendering and remote rendering is different and the remoting rendering is correct obviously.

To Reproduce

Simply run python ./04_application/solution.py --port 1234, and I got the following 2 different pictures:
With the same parameters, but the color is different. I don't know whether it's a bug or some problem on my computer with local rendering.

Code

# code goes here

Expected behavior

Both local and remote rendering should have the same results.

Screenshots

For local rendering, check the picture below:
local_rendering

For remote rendering, check below:
remote_rendering

Platform:

Device:

  • Desktop
  • Mobile

OS:

  • Windows
  • MacOS
  • Linux
  • Android
  • iOS

Browsers Affected:

  • Chrome
  • Firefox
  • Microsoft Edge
  • Safari
  • Opera
  • Brave
  • IE 11

How do I...?

  • Observe the shared state while debugging?

I could log the value of a particular key, or I could use the browser network tab to see the diffs, but is there a way to debug Trame apps that lends itself to the "Shared state" mindset?

One idea would be to have a "debug" flag in the "start()" function. If true, this flag would pretty print the state whenever it settles changes. This lets me not worry about the differences between changes from the server or from the client. Perhaps an optional argument would also be a list of keys to not print, since their output would be very large. I think a deny list would be better than an allow list here because it would change less as I debug a growing app.

Failure when picking cells

Describe the bug

Picking cells in a simple tube filter, applied to a two-line-segment polyline, sometimes fails inexplicably.
To Reproduce

Steps to reproduce the behavior:

  1. Run the script that follows
  2. Click on the actor to "pick" its cells
  3. Inspect the viewer and command line output; when pick is successful, a tooltip appears with the data value corresponding to the cell and the last output on the command line shows a value > -1. when the pick is unsuccessful, no tooltip appears and the last output on the command line shows a value = -1.

Code

from trame.app import get_server
from trame.ui.vuetify import SinglePageLayout
from trame.widgets import vuetify, html, trame, vtk as vtk_widgets
from vtkmodules.vtkFiltersCore import vtkTubeFilter
from vtk import reference, vtkGenericCell
import vtk.util.numpy_support as vtknumpy

import vtk

import numpy as np
# -----------------------------------------------------------------------------
# Trame
# -----------------------------------------------------------------------------

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

# -----------------------------------------------------------------------------
# Constants
# -----------------------------------------------------------------------------

VIEW_INTERACT = [
    {"button": 1, "action": "Rotate"},
    {"button": 2, "action": "Pan"},
    {"button": 3, "action": "Zoom", "scrollEnabled": True},
    {"button": 1, "action": "Pan", "alt": True},
    {"button": 1, "action": "Zoom", "control": True},
    {"button": 1, "action": "Pan", "shift": True},
    {"button": 1, "action": "Roll", "alt": True, "shift": True},
]

# -----------------------------------------------------------------------------
# Generate dataset
# -----------------------------------------------------------------------------

points = vtk.vtkPoints()
points.SetNumberOfPoints(4)
line = vtk.vtkLine()
lines = vtk.vtkCellArray()

# create first line segment
points.SetPoint(0, 0, 0, 0)
line.GetPointIds().SetId(0, 0)

points.SetPoint(1, 1, 1, 1)
line.GetPointIds().SetId(1, 1)

lines.InsertNextCell(line)

# create second line segment
points.SetPoint(2, 1, 1, 1)
line.GetPointIds().SetId(0, 2)

points.SetPoint(3, 2, 2, 2)
line.GetPointIds().SetId(1, 3)

lines.InsertNextCell(line)

linesPolyData = vtk.vtkPolyData()
linesPolyData.SetPoints(points)
linesPolyData.SetLines(lines)

vtkfields= vtknumpy.numpy_to_vtk([0,1])
vtkfields.SetName("data")
vtkfields.SetNumberOfComponents(1)

linesPolyData.GetCellData().AddArray(vtkfields)

# -----------------------------------------------------------------------------
# VTK pipeline
# -----------------------------------------------------------------------------

lines_tubes = vtkTubeFilter()
lines_tubes.SetInputData(linesPolyData)
radius = 2
lines_tubes.SetRadius(radius)
n_of_sides = 6
lines_tubes.SetNumberOfSides(n_of_sides)
lines_tubes.Update()
lines_polydata = lines_tubes.GetOutput()
state.lines = None

# Extract fieldParameters
fieldParameters = {"data": {"range": [0, 1]}}
cd = linesPolyData.GetCellData()
nb_arrays = cd.GetNumberOfArrays()

# -----------------------------------------------------------------------------
# Web App setup
# -----------------------------------------------------------------------------

state.update(
    {
        # Fields available
        "fieldParameters": fieldParameters,
        # picking controls
        "pickingMode": "click",
        "modes": [
            {"value": "click", "icon": "mdi-cursor-default-click-outline"},
        ],
        "field": "data",
        # Picking feedback
        "pickData": None,
        "tooltip": "",
        "pixel_ratio": 2,
        # Meshes
        "linesVisible": True,
        
    }
)

# -----------------------------------------------------------------------------
# Callbacks
# -----------------------------------------------------------------------------

@state.change("pickData")
def update_tooltip(pickData, pixel_ratio, **kwargs):
    state.tooltip = ""
    state.tooltipStyle = {"display": "none"}
    data = pickData
    if data and data["representationId"] == "lines":
        xyx = data["worldPosition"]
        
        gen_cell = vtkGenericCell()
        sub_id = reference(0)
        pc = [0, 0, 0]
        weights = [0,0,0,0,0,0,0,0,0,0,0,0]
        face_idx = lines_polydata.FindCell(xyx, gen_cell, -1, 1, sub_id, pc, weights)
        idx = int(np.floor(face_idx / n_of_sides))
        print(" ")
        print("New Click:")
        print(f"xyx={xyx}, gen_cell={gen_cell}, sub_id={sub_id}, pc={pc}, weights={weights}")
        print(f"index={idx}")
        if idx > -1:

            messages = []

            for i in range(nb_arrays):
                array = cd.GetAbstractArray(i)
                if array.IsNumeric(): 
                    name = array.GetName()
                    if name == state.field: 
                        value_str = f"{array.GetValue(idx):.2f}"
                        norm_str = ""

                        messages.append(f"{name}: {value_str} {norm_str}")

            if len(messages):
                x, y, z = data["displayPosition"]
                state.tooltip = "\n".join(messages)
                state.tooltipStyle = {
                    "position": "absolute",
                    "left": f"{(x / pixel_ratio )+ 10}px",
                    "bottom": f"{(y / pixel_ratio ) + 10}px",
                    "zIndex": 10,
                    "pointerEvents": "none",
                }

# -----------------------------------------------------------------------------
# UI
# -----------------------------------------------------------------------------

with SinglePageLayout(server) as layout:
    # Let the server know the browser pixel ratio
    trame.ClientTriggers(mounted="pixel_ratio = window.devicePixelRatio")

    with layout.content:
        with vuetify.VContainer(
            fluid=True, classes="pa-0 fill-height", style="position: relative;"
        ): 

            with vuetify.VCard(
                style=("tooltipStyle", {"display": "none"}), elevation=2, outlined=True
            ):
                with vuetify.VCardText():
                    html.Pre("{{ tooltip }}")

            with vtk_widgets.VtkView(
                ref="view",
                picking_modes=("[pickingMode]",),
                interactor_settings=("interactorSettings", VIEW_INTERACT),
                click="pickData = $event"
            ):

                with vtk_widgets.VtkGeometryRepresentation(
                    id="lines",
                    v_if="lines",
                    actor=("{ visibility: linesVisible }",),
                ):
                    vtk_widgets.VtkMesh("lines", dataset=lines_polydata, cell_arrays=["data"])

# -----------------------------------------------------------------------------
# Jupyter
# -----------------------------------------------------------------------------

def show(**kwargs):
    from trame.app import jupyter

    jupyter.show(server, **kwargs)

# -----------------------------------------------------------------------------
# CLI
# -----------------------------------------------------------------------------

if __name__ == "__main__":
    server.start(port=8080)

Expected behavior

I expect that, upon clicking the tube filter actor, a cell within the actor will be picked. Inexplicably, the cell that is clicked will occasionally not be registered as picked. Furthermore, this "bad behavior" gets worse as the number of clicks continues (i.e., initially the proportion of clicks that result in a failed pick is low, but that increases with the cumulative number of clicks). The behavior described is exhibited in the movie below:

Screenshots

MRE_pick_failure_vtk.mov

I realize that this might be an issue with something other than Trame, but I thought I'd start here!

Platform:

Trame:

  • Version 1.x
  • Version 2.x

Device:

  • Desktop
  • Mobile

OS:

  • Windows
  • MacOS
  • Linux
  • Android
  • iOS

Browsers Affected:

  • Chrome
  • Firefox
  • Microsoft Edge
  • Safari
  • Opera
  • Brave
  • IE 11

py-web-vue version:
vMAJOR.MINOR.PATCH

`from trame.widgets import vtk` is not user friendly and should not be in the examples

Is your feature request related to a problem? Please describe.

The vtk widgets module should be named something else as only serious VTK power users import vtk-python the proper way like from vtkmodules.vtkRenderingCore import vtkRenderWindow.

Most (like 99% of) users simply use import vtk and that's not going anywhere. Having from trame.widgets import vtk in the examples where you want users to also use VTK-python is setting new users up for failure as the two namespaces will conflict.

Further, VTK is an overwhelming library for new users, so seeing from trame.widgets import vtk, they will likely think that vtk is the actual vtk where they can get classes like vtkPolyData (actually even I thought that when first trying trame).

Describe the solution you'd like

The vtk widgets module should be named something other than vtk, like vtk_widgets

from trame.widgets import vtk should be something else, perhaps from trame.widgets import vtk_widgets

Describe alternatives you've considered

An alternative is to have all the examples use an alias: from trame.widgets import vtk as vtk_widgets

Additional context

I'm having to carefully modify this import statement in every trame app I create from the examples (I use the examples as templates which I expect most others do as well)

SetInteractorStyle fails to do anything

Describe the bug

Trying to use SetInteractorStyle always seems to fail, I always get vtkInteractorStyleTrackballCamera. Specifically I can't use a custom subclass.

To Reproduce

The script below was taken from https://examples.vtk.org/site/Python/Picking/HighlightWithSilhouette/ with minimal adaptation for trame.

When running, the custom code in MouseInteractorHighLightActor.onLeftButtonDown never executes, so the functionality does not work and there are no debug prints.

Code

#!/usr/bin/env python

# noinspection PyUnresolvedReferences
import vtkmodules.vtkRenderingOpenGL2
from vtkmodules.vtkCommonColor import vtkNamedColors
from vtkmodules.vtkCommonCore import vtkMinimalStandardRandomSequence
from vtkmodules.vtkFiltersHybrid import vtkPolyDataSilhouette
from vtkmodules.vtkFiltersSources import vtkSphereSource
from vtkmodules.vtkInteractionStyle import vtkInteractorStyleTrackballCamera
from vtkmodules.vtkRenderingCore import (
    vtkActor,
    vtkPolyDataMapper,
    vtkPropPicker,
    vtkRenderWindow,
    vtkRenderWindowInteractor,
    vtkRenderer
)
from trame.app import get_server
from trame.ui.vuetify import SinglePageLayout
from trame.widgets import vtk, vuetify


def get_program_parameters():
    import argparse
    description = 'Highlighting a selected object with a silhouette.'
    epilogue = '''
Click on the object to highlight it.
The selected object is highlighted with a silhouette.
    '''
    parser = argparse.ArgumentParser(description=description, epilog=epilogue,
                                     formatter_class=argparse.RawTextHelpFormatter)
    parser.add_argument('numberOfSpheres', nargs='?', type=int, default=10,
                        help='The number of spheres, default is 10.')
    args = parser.parse_args()
    return args.numberOfSpheres


class MouseInteractorHighLightActor(vtkInteractorStyleTrackballCamera):

    def __init__(self, silhouette=None, silhouetteActor=None):
        self.AddObserver("LeftButtonPressEvent", self.onLeftButtonDown)
        self.LastPickedActor = None
        self.Silhouette = silhouette
        self.SilhouetteActor = silhouetteActor

    def onLeftButtonDown(self, obj, event):
        print("In onLeftButtonDown")
        clickPos = self.GetInteractor().GetEventPosition()

        #  Pick from this location.
        picker = vtkPropPicker()
        picker.Pick(clickPos[0], clickPos[1], 0, self.GetDefaultRenderer())
        self.LastPickedActor = picker.GetActor()

        # If we picked something before, remove the silhouette actor and
        # generate a new one.
        if self.LastPickedActor:
            print(f"You picked: {self.LastPickedActor}")
            self.GetDefaultRenderer().RemoveActor(self.SilhouetteActor)

            # Highlight the picked actor by generating a silhouette
            self.Silhouette.SetInputData(self.LastPickedActor.GetMapper().GetInput())
            self.GetDefaultRenderer().AddActor(self.SilhouetteActor)
        else:
            print("Nothing picked")

        #  Forward events
        self.OnLeftButtonDown()
        return

    def SetSilhouette(self, silhouette):
        self.Silhouette = silhouette

    def SetSilhouetteActor(self, silhouetteActor):
        self.SilhouetteActor = silhouetteActor


def main():
    numberOfSpheres = get_program_parameters()
    colors = vtkNamedColors()

    # A renderer and render window
    renderer = vtkRenderer()
    renderer.SetBackground(colors.GetColor3d('SteelBlue'))

    renderWindow = vtkRenderWindow()
    renderWindow.SetSize(640, 480)
    renderWindow.AddRenderer(renderer)

    # An interactor
    interactor = vtkRenderWindowInteractor()
    interactor.SetRenderWindow(renderWindow)

    randomSequence = vtkMinimalStandardRandomSequence()
    # randomSequence.SetSeed(1043618065)
    # randomSequence.SetSeed(5170)
    randomSequence.SetSeed(8775070)
    # Add spheres to play with
    for i in range(numberOfSpheres):
        source = vtkSphereSource()

        # random position and radius
        x = randomSequence.GetRangeValue(-5.0, 5.0)
        randomSequence.Next()
        y = randomSequence.GetRangeValue(-5.0, 5.0)
        randomSequence.Next()
        z = randomSequence.GetRangeValue(-5.0, 5.0)
        randomSequence.Next()
        radius = randomSequence.GetRangeValue(0.5, 1.0)
        randomSequence.Next()

        source.SetRadius(radius)
        source.SetCenter(x, y, z)
        source.SetPhiResolution(11)
        source.SetThetaResolution(21)

        mapper = vtkPolyDataMapper()
        mapper.SetInputConnection(source.GetOutputPort())
        actor = vtkActor()
        actor.SetMapper(mapper)

        r = randomSequence.GetRangeValue(0.4, 1.0)
        randomSequence.Next()
        g = randomSequence.GetRangeValue(0.4, 1.0)
        randomSequence.Next()
        b = randomSequence.GetRangeValue(0.4, 1.0)
        randomSequence.Next()

        actor.GetProperty().SetDiffuseColor(r, g, b)
        actor.GetProperty().SetDiffuse(0.8)
        actor.GetProperty().SetSpecular(0.5)
        actor.GetProperty().SetSpecularColor(colors.GetColor3d('White'))
        actor.GetProperty().SetSpecularPower(30.0)

        renderer.AddActor(actor)
    renderer.ResetCamera()

    # Create the silhouette pipeline, the input data will be set in the
    # interactor
    silhouette = vtkPolyDataSilhouette()
    silhouette.SetCamera(renderer.GetActiveCamera())

    # Create mapper and actor for silhouette
    silhouetteMapper = vtkPolyDataMapper()
    silhouetteMapper.SetInputConnection(silhouette.GetOutputPort())

    silhouetteActor = vtkActor()
    silhouetteActor.SetMapper(silhouetteMapper)
    silhouetteActor.GetProperty().SetColor(colors.GetColor3d("Tomato"))
    silhouetteActor.GetProperty().SetLineWidth(5)

    # Set the custom type to use for interaction.
    style = MouseInteractorHighLightActor(silhouette, silhouetteActor)
    style.SetDefaultRenderer(renderer)

    interactor.SetInteractorStyle(style)

    server = get_server(client_type="vue2")
    ctrl = server.controller

    with SinglePageLayout(server) as layout:
        layout.title.set_text("Hello trame")

        with layout.content:
            with vuetify.VContainer(
                fluid=True,
                classes="pa-0 fill-height",
            ):
                view = vtk.VtkLocalView(renderWindow)

    server.start()
    


if __name__ == "__main__":
    main()

Expected behavior

The MouseInteractorHighLightActor sublcass should work like it does in the VTK example linked.

Platform:

I've checked only the following, but I imagine the bug is on all platforms.

Device:

  • Desktop
  • Mobile

OS:

  • Windows
  • MacOS
  • Linux
  • Android
  • iOS

Browsers Affected:

  • Chrome
  • Firefox
  • Microsoft Edge
  • Safari
  • Opera
  • Brave
  • IE 11

`trame-client` not generating `index.html`

Describe the bug

Following the cookiecutter, the file index.html is not output when building the docker image, resulting in a failure of the Apache server to serve files.

One can see this in the build_image.sh output:

[...]
usage: app.py [-h] [--name NAME] [--input INPUT]

HTML app file generator for trame applications

optional arguments:
  -h, --help     show this help message and exit
  --name NAME    Application name to encode inside HTML {name}.html
  --input INPUT  Input file to use as template
usage: app.py [-h] [--name NAME] [--input INPUT]

HTML app file generator for trame applications

optional arguments:
  -h, --help     show this help message and exit
  --name NAME    Application name to encode inside HTML {name}.html
  --input INPUT  Input file to use as template
  [...]

Indicating trame.tools.www is failing.

If one force pins trame-client to 2.5.1, it starts to work. See trame client v2.5.1 to trame client v2.6.0, the switch for vue2 and vue3 was added. Maybe the issue is me and I need to set server.client_type to either vue2 or vue3?

client_type does appear to be set when I load a server:

>>> from trame.app import get_server
>>> server = get_server()
>>> print(server.client_type)
vue2

To Reproduce

Run trame cookiecutter.

  1. cookiecutter gh:kitware/trame-cookiecutter # Just App, no component
  2. cd deploy/docker
  3. ./scripts/build_image.sh
  4. ./scripts/run_image.sh
  5. go to localhost:8080 and see the Apache error page

Code

N/A

Expected behavior

Hitting the port loads the webpage.

Screenshots

Screenshot 2023-02-13 at 9 21 51 PM

Platform:

Device:

  • Desktop
  • Mobile

OS:

  • Windows
  • MacOS
  • Linux
  • Android
  • iOS

Browsers Affected:

  • Chrome
  • Firefox
  • Microsoft Edge
  • Safari
  • Opera
  • Brave
  • IE 11

Visible status of widget GitTree cannot be changed

Describe the bug

After the server started, if I change the visibility of GiftTree through UI, the server state of the visibility cannot be accessed and changed by code.

To Reproduce

Steps to reproduce the behavior:

If I have 2 elements, both are visible at the initial state.

  1. I changed the 1st element to be not visible.
  2. I change from local view to remote view, then the visibility of the 1st element will be visible again.
  3. I tried to write the visibility of each element when they are changed to the state of the server, but I found that it is impossible.

For vuetify widget, this is possible. So I think the trame widget GitTree may have a bug. Please check.

Code

Here I give a sample code modified from the git_tree example.

from trame.app import get_server
from trame.ui.vuetify import VAppLayout
from trame.widgets import trame

selection = ["2"]
tree = [
    {"id": "1", "parent": "0", "visible": 0, "name": "Wavelet"},
    {"id": "2", "parent": "1", "visible": 0, "name": "Clip"},
    {"id": "3", "parent": "1", "visible": 1, "name": "Slice"},
    {"id": "4", "parent": "2", "visible": 1, "name": "Slice 2"},
]
server = get_server()
state = server.state

with VAppLayout(server):
    trame.GitTree(
        sources=("tree", tree),
        actives=("selection", selection),
    )

if __name__ == "__main__":
    server.start()
    state.tree[0]['visible'] = 1

Expected behavior

The first element should be visible while it's not.

Screenshots

image

Platform:

Trame:

  • Version 1.x
  • Version 2.x

Device:

  • Desktop
  • Mobile

OS:

  • Windows
  • MacOS
  • Linux
  • Android
  • iOS

Browsers Affected:

  • Chrome
  • Firefox
  • Microsoft Edge
  • Safari
  • Opera
  • Brave
  • IE 11

py-web-vue version:
vMAJOR.MINOR.PATCH

How to delete the actor in the VtkLocalView?

image

After excuting the click_test2() function, I can still see the actor in the browser.
Refreshing the web page could erase the actor and browser Cache might be the reason.
How to solve the problem?

Thanks

Will trame consider front-end and back-end separation in future development?

The trame backend is now developed in python, while the front-end vue code is defined together in a python project.

For modern web development, flexibility and extensibility are necessary. In the case that vtk.js does not support all C++ functions and also does not support unstructured grid, separating trame front-end and back-end is the most convenient and perfect solution.

So I'm looking forward to the version that trame separates code of client and server.

VtkRemoteLocalView local view not syncing

Describe the bug

examples/06_vtk/02_ContourGeometry/DynamicLocalRemoteRendering.py is not updating the local view when the isovalue slider is changed

To Reproduce

Steps to reproduce the behavior:

  1. Go to '...'
  2. Click on '....'
  3. See error

Code

python examples/06_vtk/02_ContourGeometry/DynamicLocalRemoteRendering.py

Expected behavior

I expect the local view to sync when the contour/isovalue is updated

Screenshots

Screen.Recording.2023-01-13.at.2.00.37.PM.mov

Platform:

Trame:

  • Version 1.x
  • Version 2.x

Device:

  • Desktop
  • Mobile

OS:

  • Windows
  • MacOS
  • Linux
  • Android
  • iOS

Browsers Affected:

  • Chrome
  • Firefox
  • Microsoft Edge
  • Safari
  • Opera
  • Brave
  • IE 11

py-web-vue version:
vMAJOR.MINOR.PATCH

Used by ...

The goal of this issue is to capture companies using trame that are willing to have their logo featured on our website.

Please provide:

  • Short sentence on what you are using trame for.
  • Which logo to use from your company (links).
  • Sentence granting Kitware to feature your logo on our website as a user of trame.

Thanks for posting here, your approval and your will to link your brand with trame means the world to us.

Should "pip install trame" install less packages?

As of now pip install trame installs the following packages:

trame-client
trame-components
trame-deckgl
trame-markdown
trame-matplotlib
trame-plotly
trame-router
trame-server
trame-simput
trame-vega
trame-vtk
trame-vuetify
trame-rca

Installing trame-client, trame-server makes totally sense as they are a fundamental requirement of trame, but maybe we can have the others in and extras_require. Especially trame-vtk is pretty hefty since it installs vtk with >300MB.

I understand that for showing the tutorials and demos it is pretty nice to have "just do pip install trame and it works", but maybe having a "just do pip install trame[vtk] and it works" isn't that bad.

trame CLI

Trame should have a CLI for more easily running scripts and for documenting the available options when running. I highly recommend gaining inspiration from streamlit's CLI so that trame scripts do not have to have __main__ catch and can have better support and auto refresh when running.

This would likely help with #119

I don't think this would be a big ask as there is already args parsing happening.

GitTree Bug when change the active state

Describe the bug

When I tried to use trame widget GitTree, I found that if I refresh the web browser, the state of the GitTree will change to the initial state. So I tried to store the status of the GitTree to the state of the server, then I found it seems there is a bug when changing the active state of the GitTree.

To Reproduce

Steps to reproduce the behavior:

  1. Change the visibility of the 1st element.
  2. Click on the 3rd element. Actually, any element except the currently active element).
  3. The visibility of the 1st element will be changed. Only the last changed visibility will be changed.
  4. Refresh the web browser, the visibility of the 1st element will change to the correct status.

Code
Here is a sample code modified from the GitTree example.

from trame.app import get_server
from trame.ui.vuetify import VAppLayout
from trame.widgets import trame

selection_t = ["2"]
tree = [
    {"id": "1", "parent": "0", "visible": 0, "name": "Wavelet"},
    {"id": "2", "parent": "1", "visible": 0, "name": "Clip"},
    {"id": "3", "parent": "1", "visible": 1, "name": "Slice"},
    {"id": "4", "parent": "2", "visible": 1, "name": "Slice 2"},
]
server = get_server()
state = server.state

def visibility_change(event):
    _id = event["id"]
    _visibility = event["visible"]
    if _id == "1":  
        state.tree[0]['visible'] = int(_visibility)
    elif _id == "2":  
        state.tree[1]['visible'] = int(_visibility)
    elif _id == "3":
        state.tree[2]['visible'] = int(_visibility)
    elif _id == "4":
        state.tree[3]['visible'] = int(_visibility)
        
def actives_change(ids):
    _id = ids[0]
    if _id == "1":
        state.selection = [_id]
    elif _id == "2":  
        state.selection = [_id]
    elif _id == "3":  
        state.selection = [_id]
    elif _id == "4":
        state.selection = [_id]

with VAppLayout(server):
    trame.GitTree(
        sources=("tree", tree),
        actives=("selection", selection_t),
        visibility_change=(visibility_change, "[$event]"),
        actives_change=(actives_change, "[$event]"),
    )

if __name__ == "__main__":
    server.start()

Expected behavior

When I change the active of the GitTree, the last changed visibility of the element will not be changed.

Screenshots
Initial state:
image
Change the visibility of the 1st element:
image
Click on the 3rd element:
image

Platform:

Trame:

  • Version 1.x
  • Version 2.x

Device:

  • Desktop
  • Mobile

OS:

  • Windows
  • MacOS
  • Linux
  • Android
  • iOS

Browsers Affected:

  • Chrome
  • Firefox
  • Microsoft Edge
  • Safari
  • Opera
  • Brave
  • IE 11

py-web-vue version:
vMAJOR.MINOR.PATCH

`${HOST}:${PORT}/favicon.ico` should return `server.stat.trame__favicon`

Is your feature request related to a problem? Please describe.

Would it be possible for /favicon.ico to return the value from server.stat.trame__favicon? Seems pedantic, but I believe this is standard in web development?

Describe the solution you'd like

Going to /favicon.ico returns the favicon set with server.stat.trame__favicon.

This probably requires trame-router in the application by default. I guess the simplest solution would be to provide another template in trame-vuetify similar to VAppLayout that routes /favicon.ico.

Describe alternatives you've considered

Just not implementing this feature.

Additional context

None.

Add `git`, `git-lfs`, and `openssh-client` to the `trame` docker container

Is your feature request related to a problem? Please describe.

I have many private python dependencies I specify through the remote git interface as such:

requirements.txt

django @ git+https://github.com/django/django@80d38de52bb2721a7b44fce4057bcff571afc23a

I currently cannot use them since the trame docker container does not contain git.
I get the following output from build_server.sh:

[...]
Processing /local-app
  Installing build dependencies: started
  Installing build dependencies: finished with status 'done'
  Getting requirements to build wheel: started
  Getting requirements to build wheel: finished with status 'done'
  Preparing metadata (pyproject.toml): started
  Preparing metadata (pyproject.toml): finished with status 'done'
Collecting *@ git+ssh://[email protected]/****/*[email protected]
  Cloning ssh://****@bitbucket.org/****/*.git (to revision v0.2.0) to /tmp/pip-install-86inj873/*_8ceacc16a7ac4ae9989ad1b36e837e76
  ERROR: Error [Errno 2] No such file or directory: 'git' while executing command git version
ERROR: Cannot find command 'git' - do you have 'git' installed and in your PATH?
Ignoring cffi: markers 'python_version >= "3.9" and python_version < "3.11" and sys_platform == "win32"' don't match your environment
Ignoring clr-loader: markers 'python_version >= "3.9" and python_version < "3.11" and sys_platform == "win32"' don't match your environment
Ignoring colorama: markers 'python_version >= "3.9" and python_version < "3.11" and platform_system == "Windows"' don't match your environment
Collecting ***@ git+ssh://[email protected]/****/***[email protected]
ERROR: Can't verify hashes for these requirements because we don't have a way to hash version control repositories:
    ***@ git+ssh://[email protected]/****/***[email protected] from git+ssh://****@bitbucket.org/****/***[email protected] (from -r /deploy/setup/requirements.txt (line 426))
/deploy/server/venv/bin/python: Error while finding module specification for 'trame.tools.www' (ModuleNotFoundError: No module named 'trame')
/deploy/server/venv/bin/python: Error while finding module specification for 'trame.tools.app' (ModuleNotFoundError: No module named 'trame')
/deploy/server/venv/bin/python: Error while finding module specification for 'trame.tools.app' (ModuleNotFoundError: No module named 'trame')
[...]

Describe the solution you'd like

Inclusion of git, git-lfs, and openssh-client in the docker container.

Inclusion of openssh-client and a poetry target would also be sufficient.

Describe alternatives you've considered

  1. poetry (instead of pip or conda) comes with dulwich as a python-only git client. This gets around git and git-lfs and can be installed in initialize.sh, but does not include ssh.

initialize.sh

# Install app from local directory
wget https://install.python-poetry.org -O - | POETRY_HOME=~/.poetry python && \
    export PATH="~/.poetry/bin:$PATH" && \
    poetry config virtualenvs.create false

pushd /local-app
poetry install
popd
  1. Maybe I could wget binaries of all these packages, drop them in a bin somewhere, and modify my path?

  2. Just write my own Dockerfile from scratch, bastardizing the nice setup.

Additional context

N/A

RemoveActor does not remove last actor from a scene

Describe the bug

If I have a set of actors added to a renderer, and I attempt to remove them all, all but the last of them is removed. This is different from normal VTK behaviour, which allows all to be removed.

To Reproduce

Using the RemoveActor.py script below, do:

python RemoveActor.py

You can use a and r to add and remove actors. With the normal VTK rendering, note how you can remove the last one.

Then, do

python RemoveActor.py --use-trame

Use the "Add" and "Remove" buttons. Here, when you attempt to remove the last actor, it does not. Interestingly, the display also seems to freeze at this point - dragging with the mouse does not cause rotation, almost as if it thinks there is nothing to update. However, on adding another actor again, you will now have two items than can be rotated as normal.

Code

#!/usr/bin/env python

import random
import argparse
from trame.app import get_server
from trame.ui.vuetify import SinglePageLayout
from trame.widgets import vtk, vuetify
from vtkmodules.vtkCommonTransforms import vtkTransform

from vtkmodules.vtkFiltersSources import vtkConeSource
from vtkmodules.vtkRenderingCore import (
    vtkActor,
    vtkPolyDataMapper,
    vtkRenderer,
    vtkRenderWindow,
    vtkRenderWindowInteractor,
)

# Required for interactor initialization
from vtkmodules.vtkInteractionStyle import vtkInteractorStyleSwitch, vtkInteractorStyleTrackballCamera  # noqa

# Required for rendering initialization, not necessary for
# local rendering, but doesn't hurt to include it
import vtkmodules.vtkRenderingOpenGL2  # noqa


# -----------------------------------------------------------------------------
# VTK pipeline
# -----------------------------------------------------------------------------

renderer = vtkRenderer()
renderWindow = vtkRenderWindow()
renderWindow.AddRenderer(renderer)

renderWindowInteractor = vtkRenderWindowInteractor()
renderWindowInteractor.SetRenderWindow(renderWindow)

cone_source = vtkConeSource()
mapper = vtkPolyDataMapper()
mapper.SetInputConnection(cone_source.GetOutputPort())


actors = []

def add_actor():
    actor = vtkActor()
    actor.SetMapper(mapper)
    transform = vtkTransform()
    transform.PostMultiply()
    transform.RotateZ(random.randint(0, 180))
    transform.RotateX(random.randint(0, 180))
    actor.SetUserTransform(transform)
    actors.append(actor)
    renderer.AddActor(actor)


add_actor()
renderer.ResetCamera()


def run_normal():

    class MyKeyPressInteractorStyle(vtkInteractorStyleTrackballCamera):
        def __init__(self):
            super().__init__()
            self.AddObserver("KeyPressEvent", self.my_KeyPressEvent_observer)

        def my_KeyPressEvent_observer(self , obj, event):
            keysym = self.GetInteractor().GetKeySym()
            print(len(actors))
            if keysym == 'r':
                print("r pressed")
                if actors:
                    renderer.RemoveActor(actors.pop())
                    renderer.Modified()
                    renderWindow.Render()
            elif keysym == 'a':
                print("a pressed")
                add_actor()
                renderer.Modified()
                renderWindow.Render()

    renderWindowInteractor.SetInteractorStyle(MyKeyPressInteractorStyle())
    renderWindow.Render()
    renderWindowInteractor.Start()

def run_trame():
    server = get_server(client_type = "vue2")
    ctrl = server.controller

    def add_clicked(**kwargs):
        print("add clicked")
        add_actor()
        ctrl.view_update()

    def remove_clicked(**kwargs):
        print("remove clicked")
        if actors:
            renderer.RemoveActor(actors.pop())
        ctrl.view_update()

    with SinglePageLayout(server) as layout:
        with layout.toolbar:
            vuetify.VSpacer()
            vuetify.VBtn("Add", click=add_clicked)
            vuetify.VBtn("Remove", click=remove_clicked)

        with layout.content:
            with vuetify.VContainer(
                fluid=True,
                classes="pa-0 fill-height",
            ):
                view = vtk.VtkLocalView(renderWindow)
                ctrl.view_update = view.update

    server.start()

# -----------------------------------------------------------------------------
# Main
# -----------------------------------------------------------------------------

parser = argparse.ArgumentParser()
parser.add_argument('--use-trame', action='store_true')

if __name__ == "__main__":

    if parser.parse_args().use_trame:
        run_trame()
    else:
        run_normal()

Expected behavior

The code should behave the same under trame as normally.

Platform:

I assuming all platforms are affected.

Device:

  • Desktop
  • Mobile

OS:

  • Windows
  • MacOS
  • Linux
  • Android
  • iOS

Browsers Affected:

  • Chrome
  • Firefox
  • Microsoft Edge
  • Safari
  • Opera
  • Brave
  • IE 11

trame cli doesn't find sys.path modules when imported in main script

I import a module in my main script which is in the same directory as my app.py. Using python app.py this works fine, but using trame app.py the module is not found.

The import looks like this:

from ArgumentValidator import ArgumentValidator

And my attempts to run my app look like this:

$ ls
app.py  ArgumentValidator.py  dev_server.sh  FileDatabase.py  model  ParflowWrapper.py  __pycache__  SimputLoader.py
$ trame app.py
Traceback (most recent call last):
  File "/home/drew/Desktop/simulation-modeler/.venv/bin/trame", line 8, in <module>
    sys.exit(main())
  File "/home/drew/Desktop/simulation-modeler/.venv/lib/python3.8/site-packages/trame/__init__.py", line 553, in main
    spec.loader.exec_module(app)
  File "<frozen importlib._bootstrap_external>", line 783, in exec_module
  File "<frozen importlib._bootstrap>", line 219, in _call_with_frames_removed
  File "app.py", line 10, in <module>
    from ArgumentValidator import ArgumentValidator
ModuleNotFoundError: No module named 'ArgumentValidator'
$ python app.py # succeeds

Grid / Bounds plotting incorrectly for parallel projection

Describe the bug

Hello, when using PyVista with pv.set_jupyter_backend('trame').
When you toggle local rendering, parallel project and turn on the rulers/bounds, the grid appears infront of the mesh instead of behind.
I raised a bug report for Pyvista pyvista/pyvista#3991 but was recommend opening a bug report with vtk noting:

 "Fly Mode" of the vtkCubeAxesActor is incorrectly set

To Reproduce

import pyvista as pv
pv.set_jupyter_backend('trame')  
sp = pv.Sphere()
sp.points[:,-1] *= 2

pl = pv.Plotter(shape=(1,2))
pl.enable_parallel_projection()

pl.add_mesh(sp)
pl.subplot(0,1)
pl.add_mesh(pv.Cone())
pl.link_views()
pl.show()

Then toggle OFF server rendering and turn ON rulers/bounds

Detailed behavior

image

Expected behavior

image

Platform:

Device:

  • Desktop
  • Mobile

OS:

  • Windows
  • MacOS
  • Linux
  • Android
  • iOS

Browsers Affected:

  • Chrome
  • Firefox
  • Microsoft Edge
  • Safari
  • Opera
  • Brave
  • IE 11

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.