From 9d5379a8e221d6afd9952eb86d7fe2bb16ddbc8d Mon Sep 17 00:00:00 2001 From: afernand Date: Thu, 18 Apr 2024 10:24:47 +0200 Subject: [PATCH 01/17] fix: trame --- src/ansys/visualizer/interfaces/trame_gui.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ansys/visualizer/interfaces/trame_gui.py b/src/ansys/visualizer/interfaces/trame_gui.py index 0a400265..940dc9bf 100644 --- a/src/ansys/visualizer/interfaces/trame_gui.py +++ b/src/ansys/visualizer/interfaces/trame_gui.py @@ -23,7 +23,7 @@ try: from pyvista.trame.ui import plotter_ui from trame.app import get_server - from trame.ui.vuetify import SinglePageLayout + from trame.ui.vuetify3 import SinglePageLayout _HAS_TRAME = True From 86b6026b9d58474012dc705d3803e28e7b128020 Mon Sep 17 00:00:00 2001 From: afernand Date: Mon, 6 May 2024 14:16:36 +0200 Subject: [PATCH 02/17] feat: Add service as in example --- .../README.txt | 0 .../clipping_plane.py | 0 .../picker.py | 2 +- .../plain_usage.py | 0 .../remote_trame_view.py | 67 ++++++++++ .../using_meshobject.py | 3 + .../backends/__init__.py | 2 + .../backends/pyvista/pyvista.py | 2 +- .../pyvista/{trame_gui.py => trame_local.py} | 2 +- .../backends/pyvista/trame_remote.py | 59 +++++++++ .../backends/pyvista/trame_service.py | 123 ++++++++++++++++++ 11 files changed, 257 insertions(+), 3 deletions(-) rename examples/{00-basic-examples => 00-basic-pyvista-examples}/README.txt (100%) rename examples/{00-basic-examples => 00-basic-pyvista-examples}/clipping_plane.py (100%) rename examples/{00-basic-examples => 00-basic-pyvista-examples}/picker.py (99%) rename examples/{00-basic-examples => 00-basic-pyvista-examples}/plain_usage.py (100%) create mode 100644 examples/00-basic-pyvista-examples/remote_trame_view.py rename examples/{00-basic-examples => 00-basic-pyvista-examples}/using_meshobject.py (97%) rename src/ansys/tools/visualization_interface/backends/pyvista/{trame_gui.py => trame_local.py} (98%) create mode 100644 src/ansys/tools/visualization_interface/backends/pyvista/trame_remote.py create mode 100644 src/ansys/tools/visualization_interface/backends/pyvista/trame_service.py diff --git a/examples/00-basic-examples/README.txt b/examples/00-basic-pyvista-examples/README.txt similarity index 100% rename from examples/00-basic-examples/README.txt rename to examples/00-basic-pyvista-examples/README.txt diff --git a/examples/00-basic-examples/clipping_plane.py b/examples/00-basic-pyvista-examples/clipping_plane.py similarity index 100% rename from examples/00-basic-examples/clipping_plane.py rename to examples/00-basic-pyvista-examples/clipping_plane.py diff --git a/examples/00-basic-examples/picker.py b/examples/00-basic-pyvista-examples/picker.py similarity index 99% rename from examples/00-basic-examples/picker.py rename to examples/00-basic-pyvista-examples/picker.py index 99270909..ece5e08a 100644 --- a/examples/00-basic-examples/picker.py +++ b/examples/00-basic-pyvista-examples/picker.py @@ -74,4 +74,4 @@ def name(self): pv_backend = PyVistaBackend(allow_picking=True) pl = Plotter(backend=pv_backend) pl.plot(mesh_object) -pl.show() +pl.show() \ No newline at end of file diff --git a/examples/00-basic-examples/plain_usage.py b/examples/00-basic-pyvista-examples/plain_usage.py similarity index 100% rename from examples/00-basic-examples/plain_usage.py rename to examples/00-basic-pyvista-examples/plain_usage.py diff --git a/examples/00-basic-pyvista-examples/remote_trame_view.py b/examples/00-basic-pyvista-examples/remote_trame_view.py new file mode 100644 index 00000000..f8d611cc --- /dev/null +++ b/examples/00-basic-pyvista-examples/remote_trame_view.py @@ -0,0 +1,67 @@ +# Copyright (C) 2023 - 2024 ANSYS, Inc. and/or its affiliates. +# SPDX-License-Identifier: MIT +# +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +""" +.. _ref_trame_remote + +============================= +Use trame as a remote service +============================= + +This example shows how to launch a trame service and use it as a remote service. +""" + +""" +First, we need to launch the trame service. We can do this by running the following code: +""" + +# import required libraries +from ansys.tools.visualization_interface.backends.pyvista import TrameService + +# create a trame service, in whatever port is available in your system +ts = TrameService(websocket_port=8765) + +# run the service +ts.run() + +""" +Now, we can send meshes and plotter to the trame service. We can do this by running the following code in a separate terminal: +""" + +# import required libraries +import time + +import pyvista as pv + +from ansys.tools.visualization_interface.backends.pyvista.trame_remote import send_mesh, send_pl + +# create an example plotter +plotter = pv.Plotter() +plotter.add_mesh(pv.Cube()) + +# send some example meshes +send_mesh(pv.Sphere()) +send_mesh(pv.Sphere(center=(3,0,0))) +time.sleep(4) + +# if we send a plotter, the previous meshes will be deleted. +send_pl(plotter) diff --git a/examples/00-basic-examples/using_meshobject.py b/examples/00-basic-pyvista-examples/using_meshobject.py similarity index 97% rename from examples/00-basic-examples/using_meshobject.py rename to examples/00-basic-pyvista-examples/using_meshobject.py index 17b3ecb6..0c94e2b5 100644 --- a/examples/00-basic-examples/using_meshobject.py +++ b/examples/00-basic-pyvista-examples/using_meshobject.py @@ -77,3 +77,6 @@ def name(self): pl = Plotter() pl.plot(mesh_object) pl.show() +pl = Plotter(use_trame=True) +pl.add(mesh_object) +pl.plot() diff --git a/src/ansys/tools/visualization_interface/backends/__init__.py b/src/ansys/tools/visualization_interface/backends/__init__.py index a6ba5511..631e84c2 100644 --- a/src/ansys/tools/visualization_interface/backends/__init__.py +++ b/src/ansys/tools/visualization_interface/backends/__init__.py @@ -20,3 +20,5 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. """Provides interfaces.""" +from ansys.tools.visualization_interface.backends.pyvista.trame_remote import send_mesh, send_pl # noqa: F401, E402 +from ansys.tools.visualization_interface.backends.pyvista.trame_service import TrameService # noqa: F401, E402 diff --git a/src/ansys/tools/visualization_interface/backends/pyvista/pyvista.py b/src/ansys/tools/visualization_interface/backends/pyvista/pyvista.py index 16261b6d..29757469 100644 --- a/src/ansys/tools/visualization_interface/backends/pyvista/pyvista.py +++ b/src/ansys/tools/visualization_interface/backends/pyvista/pyvista.py @@ -29,7 +29,7 @@ from ansys.tools.visualization_interface import USE_TRAME from ansys.tools.visualization_interface.backends._base import BaseBackend from ansys.tools.visualization_interface.backends.pyvista.pyvista_interface import PyVistaInterface -from ansys.tools.visualization_interface.backends.pyvista.trame_gui import ( +from ansys.tools.visualization_interface.backends.pyvista.trame_local import ( _HAS_TRAME, TrameVisualizer, ) diff --git a/src/ansys/tools/visualization_interface/backends/pyvista/trame_gui.py b/src/ansys/tools/visualization_interface/backends/pyvista/trame_local.py similarity index 98% rename from src/ansys/tools/visualization_interface/backends/pyvista/trame_gui.py rename to src/ansys/tools/visualization_interface/backends/pyvista/trame_local.py index 940dc9bf..0a400265 100644 --- a/src/ansys/tools/visualization_interface/backends/pyvista/trame_gui.py +++ b/src/ansys/tools/visualization_interface/backends/pyvista/trame_local.py @@ -23,7 +23,7 @@ try: from pyvista.trame.ui import plotter_ui from trame.app import get_server - from trame.ui.vuetify3 import SinglePageLayout + from trame.ui.vuetify import SinglePageLayout _HAS_TRAME = True diff --git a/src/ansys/tools/visualization_interface/backends/pyvista/trame_remote.py b/src/ansys/tools/visualization_interface/backends/pyvista/trame_remote.py new file mode 100644 index 00000000..843877bc --- /dev/null +++ b/src/ansys/tools/visualization_interface/backends/pyvista/trame_remote.py @@ -0,0 +1,59 @@ +# Copyright (C) 2024 ANSYS, Inc. and/or its affiliates. +# SPDX-License-Identifier: MIT +# +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. +"""Module for trame websocket client functions.""" +import pickle + +from beartype.typing import Union +import pyvista as pv +from websockets.sync.client import connect + + +def send_pl(plotter: pv.Plotter, port: int = 8765): + """Send the plotter meshes to a remote trame service. + + Since plotter can't be pickled, we send the meshes list instead. + + Parameters + ---------- + plotter : pv.Plotter + Plotter to send. + port : int, optional + Websocket port to connect to, by default 8765 + """ + with connect("ws://localhost:" + str(port)) as websocket: + # Plotter can't be pickled, so we send the meshes list instead + meshes_list_pk = pickle.dumps(plotter.meshes) + websocket.send(meshes_list_pk) + +def send_mesh(mesh: Union[pv.PolyData, pv.MultiBlock], port: int = 8765): + """Send a mesh to a remote trame service. + + Parameters + ---------- + mesh : Union[pv.PolyData, pv.MultiBlock] + Mesh to send. + port : int, optional + Websocket port to connect to, by default 8765 + """ + with connect("ws://localhost:" + str(port)) as websocket: + mesh_pk = pickle.dumps(mesh) + websocket.send(mesh_pk) diff --git a/src/ansys/tools/visualization_interface/backends/pyvista/trame_service.py b/src/ansys/tools/visualization_interface/backends/pyvista/trame_service.py new file mode 100644 index 00000000..252c9a47 --- /dev/null +++ b/src/ansys/tools/visualization_interface/backends/pyvista/trame_service.py @@ -0,0 +1,123 @@ +# Copyright (C) 2024 ANSYS, Inc. and/or its affiliates. +# SPDX-License-Identifier: MIT +# +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +"""Trame service module.""" +import asyncio +import pickle + +from beartype.typing import TYPE_CHECKING +import pyvista as pv +from pyvista.trame.ui import plotter_ui +from trame.app import get_server +from trame.ui.vuetify3 import SinglePageLayout +from trame.widgets import vuetify3 +from websockets.server import serve + +if TYPE_CHECKING: + from websockets import WebSocketServerProtocol +class TrameService: + """Trame service class. + + Initializes a trame service where you can send meshes to plot in a trame webview plotter. + + Parameters + ---------- + webserver_port : int, optional + Port where the webserver will listen for new plotters and meshes, by default 8765 + """ + def __init__(self, webserver_port: int=8765): + """Initialize the trame service.""" + pv.OFF_SCREEN = True + + self._server = get_server() + self._state, self._ctrl = self._server.state, self._server.controller + self._webserver_port = webserver_port + # pyvista plotter, we treat it as a view i.e. created once but updated as we see fit. + self._pl = pv.Plotter() + + def clear_plotter(self): + """Clears the web view in the service.""" + self._pl.clear_actors() + self._pl.reset_camera() + + + async def _listener(self, websocket: WebSocketServerProtocol): + """Listens for new meshes and plotters. + + Infinite loop where we wait for new messages from the client. + If we get a list of meshes, we assume it's a scene and clear the previous meshes. + + Parameters + ---------- + websocket : websockets.WebSocketServerProtocol + Websocket where to listen. + """ + async for message in websocket: + obj = pickle.loads(message) + + if isinstance(obj, list): + # if we get a list of meshes, assume it's a scene and clear previous meshes + self.clear_plotter() + for mesh in obj: + self._pl.add_mesh(mesh) + print(mesh) + else: + print(obj) + self._pl.add_mesh(obj) + self._pl.reset_camera() + + async def _webserver(self): + """Starts the webserver for the trame service listener.""" + async with serve(self._listener, "localhost", self._webserver_port): + await asyncio.Future() # run forever + + def set_scene(self): + """Sets the web view scene for the trame service.""" + with SinglePageLayout(self._server) as layout: + with layout.toolbar: + with vuetify3.VBtn(icon=True, click=self.clear_plotter): + vuetify3.VIcon("mdi-trash-can") + with vuetify3.VBtn(icon=True, click=self._ctrl.reset_camera): + vuetify3.VIcon("mdi-crop-free") + + # main view container + with layout.content: + with vuetify3.VContainer( + fluid=True, classes="pa-0 fill-height", style="position: relative;" + ): + view = plotter_ui(self._pl) + # bind plotter methods to the controller method, this way we can + # attach UI elements on it. see buttons above + self._ctrl.view_update = view.update + self._ctrl.reset_camera = view.reset_camera + + + async def _launch_trame_service(self): + """Launches the trame service.""" + self.set_scene() + trame_coroutine = self._server.start(exec_mode="coroutine") + webserver_coroutine = asyncio.create_task(self._webserver()) + await asyncio.gather(trame_coroutine, webserver_coroutine) + + def run(self): + """Start the trame web view and the websocket services.""" + asyncio.run(self._launch_trame_service()) From a5fce1f39f134e850c149efd1616423469d8c1a3 Mon Sep 17 00:00:00 2001 From: afernand Date: Mon, 6 May 2024 14:22:50 +0200 Subject: [PATCH 03/17] fix: add final line --- examples/00-basic-pyvista-examples/picker.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/00-basic-pyvista-examples/picker.py b/examples/00-basic-pyvista-examples/picker.py index ece5e08a..99270909 100644 --- a/examples/00-basic-pyvista-examples/picker.py +++ b/examples/00-basic-pyvista-examples/picker.py @@ -74,4 +74,4 @@ def name(self): pv_backend = PyVistaBackend(allow_picking=True) pl = Plotter(backend=pv_backend) pl.plot(mesh_object) -pl.show() \ No newline at end of file +pl.show() From b815f3c709d860a80766687b04c1b1113dd87594 Mon Sep 17 00:00:00 2001 From: afernand Date: Mon, 6 May 2024 14:24:58 +0200 Subject: [PATCH 04/17] fix: Add missing dependency --- pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/pyproject.toml b/pyproject.toml index bf9e0b88..f9cb5b79 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -26,6 +26,7 @@ classifiers = [ dependencies = [ "pyvista >= 0.42.0", "beartype >= 0.17.0", + "websockets >= 12.0", ] [project.optional-dependencies] From c0110a7e65a6a33796e9586bb019a147ff123fe1 Mon Sep 17 00:00:00 2001 From: afernand Date: Mon, 6 May 2024 14:29:59 +0200 Subject: [PATCH 05/17] fix: undo __init__ imports --- examples/00-basic-pyvista-examples/remote_trame_view.py | 2 +- src/ansys/tools/visualization_interface/backends/__init__.py | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/examples/00-basic-pyvista-examples/remote_trame_view.py b/examples/00-basic-pyvista-examples/remote_trame_view.py index f8d611cc..c7966239 100644 --- a/examples/00-basic-pyvista-examples/remote_trame_view.py +++ b/examples/00-basic-pyvista-examples/remote_trame_view.py @@ -35,7 +35,7 @@ """ # import required libraries -from ansys.tools.visualization_interface.backends.pyvista import TrameService +from ansys.tools.visualization_interface.backends.pyvista.trame_service import TrameService # create a trame service, in whatever port is available in your system ts = TrameService(websocket_port=8765) diff --git a/src/ansys/tools/visualization_interface/backends/__init__.py b/src/ansys/tools/visualization_interface/backends/__init__.py index 631e84c2..a6ba5511 100644 --- a/src/ansys/tools/visualization_interface/backends/__init__.py +++ b/src/ansys/tools/visualization_interface/backends/__init__.py @@ -20,5 +20,3 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. """Provides interfaces.""" -from ansys.tools.visualization_interface.backends.pyvista.trame_remote import send_mesh, send_pl # noqa: F401, E402 -from ansys.tools.visualization_interface.backends.pyvista.trame_service import TrameService # noqa: F401, E402 From 9e94e8dbf08e170157eae7cf212c916f9db0517d Mon Sep 17 00:00:00 2001 From: afernand Date: Mon, 6 May 2024 15:30:49 +0200 Subject: [PATCH 06/17] fix: Example --- examples/00-basic-pyvista-examples/using_meshobject.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/examples/00-basic-pyvista-examples/using_meshobject.py b/examples/00-basic-pyvista-examples/using_meshobject.py index 0c94e2b5..40c3b582 100644 --- a/examples/00-basic-pyvista-examples/using_meshobject.py +++ b/examples/00-basic-pyvista-examples/using_meshobject.py @@ -73,10 +73,13 @@ def name(self): # ==================================== from ansys.tools.visualization_interface import Plotter +from ansys.tools.visualization_interface.backends.pyvista import PyVistaBackend pl = Plotter() pl.plot(mesh_object) pl.show() -pl = Plotter(use_trame=True) + +pv_backend = PyVistaBackend(use_trame=True) +pl = Plotter(backend=pv_backend) pl.add(mesh_object) pl.plot() From a69157418dc03e1bc74f356a5133590db1ff43f6 Mon Sep 17 00:00:00 2001 From: afernand Date: Mon, 6 May 2024 15:37:41 +0200 Subject: [PATCH 07/17] fix: Example --- examples/00-basic-pyvista-examples/using_meshobject.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/examples/00-basic-pyvista-examples/using_meshobject.py b/examples/00-basic-pyvista-examples/using_meshobject.py index 40c3b582..17b3ecb6 100644 --- a/examples/00-basic-pyvista-examples/using_meshobject.py +++ b/examples/00-basic-pyvista-examples/using_meshobject.py @@ -73,13 +73,7 @@ def name(self): # ==================================== from ansys.tools.visualization_interface import Plotter -from ansys.tools.visualization_interface.backends.pyvista import PyVistaBackend pl = Plotter() pl.plot(mesh_object) pl.show() - -pv_backend = PyVistaBackend(use_trame=True) -pl = Plotter(backend=pv_backend) -pl.add(mesh_object) -pl.plot() From a2cf8d3cb0395163cedfa56089eafcbbd05ed121 Mon Sep 17 00:00:00 2001 From: afernand Date: Mon, 6 May 2024 15:52:43 +0200 Subject: [PATCH 08/17] fix: Dependency issues --- pyproject.toml | 3 +++ .../backends/pyvista/pyvista.py | 2 +- .../backends/pyvista/trame_service.py | 13 ++++++------- 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 65b8f724..9c6ae641 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -27,6 +27,9 @@ dependencies = [ "pyvista >= 0.42.0", "beartype >= 0.17.0", "websockets >= 12.0", + "trame >= 3.6.0", + "trame-vtk >= 2.8.7", + "trame-vuetify >= 2.4.3", ] [project.optional-dependencies] diff --git a/src/ansys/tools/visualization_interface/backends/pyvista/pyvista.py b/src/ansys/tools/visualization_interface/backends/pyvista/pyvista.py index 29757469..8c775bb4 100644 --- a/src/ansys/tools/visualization_interface/backends/pyvista/pyvista.py +++ b/src/ansys/tools/visualization_interface/backends/pyvista/pyvista.py @@ -280,7 +280,7 @@ def compute_edge_object_map(self) -> Dict[pv.Actor, EdgePlot]: """Compute the mapping between plotter actors and ``EdgePlot`` objects. Returns - -------- + ------- Dict[~pyvista.Actor, EdgePlot] Dictionary defining the mapping between plotter actors and ``EdgePlot`` objects. diff --git a/src/ansys/tools/visualization_interface/backends/pyvista/trame_service.py b/src/ansys/tools/visualization_interface/backends/pyvista/trame_service.py index 252c9a47..58505930 100644 --- a/src/ansys/tools/visualization_interface/backends/pyvista/trame_service.py +++ b/src/ansys/tools/visualization_interface/backends/pyvista/trame_service.py @@ -24,16 +24,15 @@ import asyncio import pickle -from beartype.typing import TYPE_CHECKING import pyvista as pv from pyvista.trame.ui import plotter_ui from trame.app import get_server from trame.ui.vuetify3 import SinglePageLayout from trame.widgets import vuetify3 +from websockets import WebSocketServerProtocol from websockets.server import serve -if TYPE_CHECKING: - from websockets import WebSocketServerProtocol + class TrameService: """Trame service class. @@ -41,16 +40,16 @@ class TrameService: Parameters ---------- - webserver_port : int, optional + websocket_port : int, optional Port where the webserver will listen for new plotters and meshes, by default 8765 """ - def __init__(self, webserver_port: int=8765): + def __init__(self, websocket_port: int=8765): """Initialize the trame service.""" pv.OFF_SCREEN = True self._server = get_server() self._state, self._ctrl = self._server.state, self._server.controller - self._webserver_port = webserver_port + self._websocket_port = websocket_port # pyvista plotter, we treat it as a view i.e. created once but updated as we see fit. self._pl = pv.Plotter() @@ -87,7 +86,7 @@ async def _listener(self, websocket: WebSocketServerProtocol): async def _webserver(self): """Starts the webserver for the trame service listener.""" - async with serve(self._listener, "localhost", self._webserver_port): + async with serve(self._listener, "localhost", self._websocket_port): await asyncio.Future() # run forever def set_scene(self): From 4128bf3dc78138c74cccbb63aa433f158442d3b0 Mon Sep 17 00:00:00 2001 From: afernand Date: Mon, 6 May 2024 16:00:13 +0200 Subject: [PATCH 09/17] fix: Example as pure documentation --- .../remote_trame_view.py | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/examples/00-basic-pyvista-examples/remote_trame_view.py b/examples/00-basic-pyvista-examples/remote_trame_view.py index c7966239..c3f4dad5 100644 --- a/examples/00-basic-pyvista-examples/remote_trame_view.py +++ b/examples/00-basic-pyvista-examples/remote_trame_view.py @@ -32,27 +32,32 @@ """ First, we need to launch the trame service. We can do this by running the following code: -""" +```python # import required libraries -from ansys.tools.visualization_interface.backends.pyvista.trame_service import TrameService +from ansys.tools.visualization_interface.backends.pyvista.trame_service import ( + TrameService, +) # create a trame service, in whatever port is available in your system ts = TrameService(websocket_port=8765) # run the service ts.run() +``` -""" Now, we can send meshes and plotter to the trame service. We can do this by running the following code in a separate terminal: -""" +```python # import required libraries import time import pyvista as pv -from ansys.tools.visualization_interface.backends.pyvista.trame_remote import send_mesh, send_pl +from ansys.tools.visualization_interface.backends.pyvista.trame_remote import ( + send_mesh, + send_pl, +) # create an example plotter plotter = pv.Plotter() @@ -60,8 +65,10 @@ # send some example meshes send_mesh(pv.Sphere()) -send_mesh(pv.Sphere(center=(3,0,0))) +send_mesh(pv.Sphere(center=(3, 0, 0))) time.sleep(4) # if we send a plotter, the previous meshes will be deleted. send_pl(plotter) +``` +""" \ No newline at end of file From 3d6ba439b6102fc7489b08f30564cb206d25416b Mon Sep 17 00:00:00 2001 From: afernand Date: Mon, 6 May 2024 16:04:26 +0200 Subject: [PATCH 10/17] fix: Example reference --- examples/00-basic-pyvista-examples/remote_trame_view.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/00-basic-pyvista-examples/remote_trame_view.py b/examples/00-basic-pyvista-examples/remote_trame_view.py index c3f4dad5..0dbabc1e 100644 --- a/examples/00-basic-pyvista-examples/remote_trame_view.py +++ b/examples/00-basic-pyvista-examples/remote_trame_view.py @@ -21,7 +21,7 @@ # SOFTWARE. """ -.. _ref_trame_remote +.. _ref_remote_trame_view ============================= Use trame as a remote service From a245e28c658e060b135a079463a5b8b85d91265f Mon Sep 17 00:00:00 2001 From: afernand Date: Mon, 6 May 2024 16:09:04 +0200 Subject: [PATCH 11/17] fix: Example --- examples/00-basic-pyvista-examples/remote_trame_view.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/examples/00-basic-pyvista-examples/remote_trame_view.py b/examples/00-basic-pyvista-examples/remote_trame_view.py index 0dbabc1e..e914a46c 100644 --- a/examples/00-basic-pyvista-examples/remote_trame_view.py +++ b/examples/00-basic-pyvista-examples/remote_trame_view.py @@ -28,9 +28,7 @@ ============================= This example shows how to launch a trame service and use it as a remote service. -""" -""" First, we need to launch the trame service. We can do this by running the following code: ```python From 86f2f2a26a3ab8050023ab32f01c12227c1c08dc Mon Sep 17 00:00:00 2001 From: afernand Date: Mon, 6 May 2024 16:20:51 +0200 Subject: [PATCH 12/17] fix: Use rst literals --- .../remote_trame_view.py | 60 +++++++++---------- 1 file changed, 29 insertions(+), 31 deletions(-) diff --git a/examples/00-basic-pyvista-examples/remote_trame_view.py b/examples/00-basic-pyvista-examples/remote_trame_view.py index e914a46c..b2d3c482 100644 --- a/examples/00-basic-pyvista-examples/remote_trame_view.py +++ b/examples/00-basic-pyvista-examples/remote_trame_view.py @@ -21,7 +21,7 @@ # SOFTWARE. """ -.. _ref_remote_trame_view +.. _ref_remote_trame_view: ============================= Use trame as a remote service @@ -29,44 +29,42 @@ This example shows how to launch a trame service and use it as a remote service. -First, we need to launch the trame service. We can do this by running the following code: +First, we need to launch the trame service. We can do this by running the following code:: -```python -# import required libraries -from ansys.tools.visualization_interface.backends.pyvista.trame_service import ( - TrameService, -) + # import required libraries + from ansys.tools.visualization_interface.backends.pyvista.trame_service import ( + TrameService, + ) -# create a trame service, in whatever port is available in your system -ts = TrameService(websocket_port=8765) + # create a trame service, in whatever port is available in your system + ts = TrameService(websocket_port=8765) -# run the service -ts.run() -``` + # run the service + ts.run() -Now, we can send meshes and plotter to the trame service. We can do this by running the following code in a separate terminal: -```python -# import required libraries -import time +Now, we can send meshes and plotter to the trame service. We can do this by running the following code in a separate terminal:: -import pyvista as pv + # import required libraries + import time -from ansys.tools.visualization_interface.backends.pyvista.trame_remote import ( - send_mesh, - send_pl, -) + import pyvista as pv -# create an example plotter -plotter = pv.Plotter() -plotter.add_mesh(pv.Cube()) + from ansys.tools.visualization_interface.backends.pyvista.trame_remote import ( + send_mesh, + send_pl, + ) -# send some example meshes -send_mesh(pv.Sphere()) -send_mesh(pv.Sphere(center=(3, 0, 0))) -time.sleep(4) + # create an example plotter + plotter = pv.Plotter() + plotter.add_mesh(pv.Cube()) + + # send some example meshes + send_mesh(pv.Sphere()) + send_mesh(pv.Sphere(center=(3, 0, 0))) + time.sleep(4) + + # if we send a plotter, the previous meshes will be deleted. + send_pl(plotter) -# if we send a plotter, the previous meshes will be deleted. -send_pl(plotter) -``` """ \ No newline at end of file From f698153cc4eaff4deb3c73ca10cb4ee66f47642e Mon Sep 17 00:00:00 2001 From: Alex Fernandez Date: Tue, 7 May 2024 09:36:38 +0200 Subject: [PATCH 13/17] Apply suggestions from code review Co-authored-by: Roberto Pastor Muela <37798125+RobPasMue@users.noreply.github.com> --- .../visualization_interface/backends/pyvista/trame_remote.py | 4 ++-- .../visualization_interface/backends/pyvista/trame_service.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/ansys/tools/visualization_interface/backends/pyvista/trame_remote.py b/src/ansys/tools/visualization_interface/backends/pyvista/trame_remote.py index 843877bc..63078f0a 100644 --- a/src/ansys/tools/visualization_interface/backends/pyvista/trame_remote.py +++ b/src/ansys/tools/visualization_interface/backends/pyvista/trame_remote.py @@ -37,7 +37,7 @@ def send_pl(plotter: pv.Plotter, port: int = 8765): plotter : pv.Plotter Plotter to send. port : int, optional - Websocket port to connect to, by default 8765 + Websocket port to connect to, by default 8765. """ with connect("ws://localhost:" + str(port)) as websocket: # Plotter can't be pickled, so we send the meshes list instead @@ -52,7 +52,7 @@ def send_mesh(mesh: Union[pv.PolyData, pv.MultiBlock], port: int = 8765): mesh : Union[pv.PolyData, pv.MultiBlock] Mesh to send. port : int, optional - Websocket port to connect to, by default 8765 + Websocket port to connect to, by default 8765. """ with connect("ws://localhost:" + str(port)) as websocket: mesh_pk = pickle.dumps(mesh) diff --git a/src/ansys/tools/visualization_interface/backends/pyvista/trame_service.py b/src/ansys/tools/visualization_interface/backends/pyvista/trame_service.py index 58505930..f3bc2e80 100644 --- a/src/ansys/tools/visualization_interface/backends/pyvista/trame_service.py +++ b/src/ansys/tools/visualization_interface/backends/pyvista/trame_service.py @@ -41,7 +41,7 @@ class TrameService: Parameters ---------- websocket_port : int, optional - Port where the webserver will listen for new plotters and meshes, by default 8765 + Port where the webserver will listen for new plotters and meshes, by default 8765. """ def __init__(self, websocket_port: int=8765): """Initialize the trame service.""" From f027c7d8c1e5001b02b5c669f155eba3a629524d Mon Sep 17 00:00:00 2001 From: afernand Date: Tue, 7 May 2024 09:51:27 +0200 Subject: [PATCH 14/17] fix> Review comments --- pyproject.toml | 8 ++++---- .../backends/pyvista/trame_remote.py | 8 ++++---- .../backends/pyvista/trame_service.py | 5 +++-- 3 files changed, 11 insertions(+), 10 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 9c6ae641..5df0036e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -26,10 +26,10 @@ classifiers = [ dependencies = [ "pyvista >= 0.42.0", "beartype >= 0.17.0", - "websockets >= 12.0", - "trame >= 3.6.0", - "trame-vtk >= 2.8.7", - "trame-vuetify >= 2.4.3", + "websockets >= 12.0,<13", + "trame >= 3.6.0,<4", + "trame-vtk >= 2.8.7,<3", + "trame-vuetify >= 2.4.3,<3", ] [project.optional-dependencies] diff --git a/src/ansys/tools/visualization_interface/backends/pyvista/trame_remote.py b/src/ansys/tools/visualization_interface/backends/pyvista/trame_remote.py index 63078f0a..d7850877 100644 --- a/src/ansys/tools/visualization_interface/backends/pyvista/trame_remote.py +++ b/src/ansys/tools/visualization_interface/backends/pyvista/trame_remote.py @@ -27,7 +27,7 @@ from websockets.sync.client import connect -def send_pl(plotter: pv.Plotter, port: int = 8765): +def send_pl(plotter: pv.Plotter, url: str = "localhost", port: int = 8765): """Send the plotter meshes to a remote trame service. Since plotter can't be pickled, we send the meshes list instead. @@ -39,12 +39,12 @@ def send_pl(plotter: pv.Plotter, port: int = 8765): port : int, optional Websocket port to connect to, by default 8765. """ - with connect("ws://localhost:" + str(port)) as websocket: + with connect("ws://" + url + ":" + str(port)) as websocket: # Plotter can't be pickled, so we send the meshes list instead meshes_list_pk = pickle.dumps(plotter.meshes) websocket.send(meshes_list_pk) -def send_mesh(mesh: Union[pv.PolyData, pv.MultiBlock], port: int = 8765): +def send_mesh(mesh: Union[pv.PolyData, pv.MultiBlock], url: str = "localhost", port: int = 8765): """Send a mesh to a remote trame service. Parameters @@ -54,6 +54,6 @@ def send_mesh(mesh: Union[pv.PolyData, pv.MultiBlock], port: int = 8765): port : int, optional Websocket port to connect to, by default 8765. """ - with connect("ws://localhost:" + str(port)) as websocket: + with connect("ws://" + url + ":" + str(port)) as websocket: mesh_pk = pickle.dumps(mesh) websocket.send(mesh_pk) diff --git a/src/ansys/tools/visualization_interface/backends/pyvista/trame_service.py b/src/ansys/tools/visualization_interface/backends/pyvista/trame_service.py index f3bc2e80..d9cd7139 100644 --- a/src/ansys/tools/visualization_interface/backends/pyvista/trame_service.py +++ b/src/ansys/tools/visualization_interface/backends/pyvista/trame_service.py @@ -43,13 +43,14 @@ class TrameService: websocket_port : int, optional Port where the webserver will listen for new plotters and meshes, by default 8765. """ - def __init__(self, websocket_port: int=8765): + def __init__(self, websocket_url: str = "localhost", websocket_port: int=8765): """Initialize the trame service.""" pv.OFF_SCREEN = True self._server = get_server() self._state, self._ctrl = self._server.state, self._server.controller self._websocket_port = websocket_port + self._websocket_url = websocket_url # pyvista plotter, we treat it as a view i.e. created once but updated as we see fit. self._pl = pv.Plotter() @@ -86,7 +87,7 @@ async def _listener(self, websocket: WebSocketServerProtocol): async def _webserver(self): """Starts the webserver for the trame service listener.""" - async with serve(self._listener, "localhost", self._websocket_port): + async with serve(self._listener, self._websocket_url, self._websocket_port): await asyncio.Future() # run forever def set_scene(self): From f60d69c01cbc2e19a3b2fb75c1f27504c30fe872 Mon Sep 17 00:00:00 2001 From: afernand Date: Tue, 7 May 2024 09:52:35 +0200 Subject: [PATCH 15/17] fix: Missing upper limits --- pyproject.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 5df0036e..17912a8c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -24,8 +24,8 @@ classifiers = [ ] dependencies = [ - "pyvista >= 0.42.0", - "beartype >= 0.17.0", + "pyvista >= 0.42.0,<1", + "beartype >= 0.17.0,<1", "websockets >= 12.0,<13", "trame >= 3.6.0,<4", "trame-vtk >= 2.8.7,<3", From 4cb33a68c9e3bb903e71102c8b15e8f7a3b291df Mon Sep 17 00:00:00 2001 From: afernand Date: Tue, 7 May 2024 10:05:10 +0200 Subject: [PATCH 16/17] fix: Add docstring for new parameters --- .../visualization_interface/backends/pyvista/trame_remote.py | 4 ++++ .../visualization_interface/backends/pyvista/trame_service.py | 2 ++ 2 files changed, 6 insertions(+) diff --git a/src/ansys/tools/visualization_interface/backends/pyvista/trame_remote.py b/src/ansys/tools/visualization_interface/backends/pyvista/trame_remote.py index d7850877..ea3f0d71 100644 --- a/src/ansys/tools/visualization_interface/backends/pyvista/trame_remote.py +++ b/src/ansys/tools/visualization_interface/backends/pyvista/trame_remote.py @@ -36,6 +36,8 @@ def send_pl(plotter: pv.Plotter, url: str = "localhost", port: int = 8765): ---------- plotter : pv.Plotter Plotter to send. + url : str, optional + Websocket url to connect to, by default "localhost". port : int, optional Websocket port to connect to, by default 8765. """ @@ -51,6 +53,8 @@ def send_mesh(mesh: Union[pv.PolyData, pv.MultiBlock], url: str = "localhost", p ---------- mesh : Union[pv.PolyData, pv.MultiBlock] Mesh to send. + url : str, optional + Websocket url to connect to, by default "localhost". port : int, optional Websocket port to connect to, by default 8765. """ diff --git a/src/ansys/tools/visualization_interface/backends/pyvista/trame_service.py b/src/ansys/tools/visualization_interface/backends/pyvista/trame_service.py index d9cd7139..39fdd9b8 100644 --- a/src/ansys/tools/visualization_interface/backends/pyvista/trame_service.py +++ b/src/ansys/tools/visualization_interface/backends/pyvista/trame_service.py @@ -40,6 +40,8 @@ class TrameService: Parameters ---------- + websocket_url : str, optional + URL where the webserver will listen for new plotters and meshes, by default "localhost". websocket_port : int, optional Port where the webserver will listen for new plotters and meshes, by default 8765. """ From aeefc17d368e7491dca8cff3a1154f82a995b6af Mon Sep 17 00:00:00 2001 From: afernand Date: Tue, 7 May 2024 10:36:57 +0200 Subject: [PATCH 17/17] fix: Change URL for host --- .../backends/pyvista/trame_remote.py | 16 ++++++++-------- .../backends/pyvista/trame_service.py | 10 +++++----- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/ansys/tools/visualization_interface/backends/pyvista/trame_remote.py b/src/ansys/tools/visualization_interface/backends/pyvista/trame_remote.py index ea3f0d71..69438367 100644 --- a/src/ansys/tools/visualization_interface/backends/pyvista/trame_remote.py +++ b/src/ansys/tools/visualization_interface/backends/pyvista/trame_remote.py @@ -27,7 +27,7 @@ from websockets.sync.client import connect -def send_pl(plotter: pv.Plotter, url: str = "localhost", port: int = 8765): +def send_pl(plotter: pv.Plotter, host: str = "localhost", port: int = 8765): """Send the plotter meshes to a remote trame service. Since plotter can't be pickled, we send the meshes list instead. @@ -36,28 +36,28 @@ def send_pl(plotter: pv.Plotter, url: str = "localhost", port: int = 8765): ---------- plotter : pv.Plotter Plotter to send. - url : str, optional - Websocket url to connect to, by default "localhost". + host : str, optional + Websocket host to connect to, by default "localhost". port : int, optional Websocket port to connect to, by default 8765. """ - with connect("ws://" + url + ":" + str(port)) as websocket: + with connect("ws://" + host + ":" + str(port)) as websocket: # Plotter can't be pickled, so we send the meshes list instead meshes_list_pk = pickle.dumps(plotter.meshes) websocket.send(meshes_list_pk) -def send_mesh(mesh: Union[pv.PolyData, pv.MultiBlock], url: str = "localhost", port: int = 8765): +def send_mesh(mesh: Union[pv.PolyData, pv.MultiBlock], host: str = "localhost", port: int = 8765): """Send a mesh to a remote trame service. Parameters ---------- mesh : Union[pv.PolyData, pv.MultiBlock] Mesh to send. - url : str, optional - Websocket url to connect to, by default "localhost". + host : str, optional + Websocket host to connect to, by default "localhost". port : int, optional Websocket port to connect to, by default 8765. """ - with connect("ws://" + url + ":" + str(port)) as websocket: + with connect("ws://" + host + ":" + str(port)) as websocket: mesh_pk = pickle.dumps(mesh) websocket.send(mesh_pk) diff --git a/src/ansys/tools/visualization_interface/backends/pyvista/trame_service.py b/src/ansys/tools/visualization_interface/backends/pyvista/trame_service.py index 39fdd9b8..3af1fb1a 100644 --- a/src/ansys/tools/visualization_interface/backends/pyvista/trame_service.py +++ b/src/ansys/tools/visualization_interface/backends/pyvista/trame_service.py @@ -40,19 +40,19 @@ class TrameService: Parameters ---------- - websocket_url : str, optional - URL where the webserver will listen for new plotters and meshes, by default "localhost". + websocket_host : str, optional + Host where the webserver will listen for new plotters and meshes, by default "localhost". websocket_port : int, optional Port where the webserver will listen for new plotters and meshes, by default 8765. """ - def __init__(self, websocket_url: str = "localhost", websocket_port: int=8765): + def __init__(self, websocket_host: str = "localhost", websocket_port: int=8765): """Initialize the trame service.""" pv.OFF_SCREEN = True self._server = get_server() self._state, self._ctrl = self._server.state, self._server.controller self._websocket_port = websocket_port - self._websocket_url = websocket_url + self._websocket_host = websocket_host # pyvista plotter, we treat it as a view i.e. created once but updated as we see fit. self._pl = pv.Plotter() @@ -89,7 +89,7 @@ async def _listener(self, websocket: WebSocketServerProtocol): async def _webserver(self): """Starts the webserver for the trame service listener.""" - async with serve(self._listener, self._websocket_url, self._websocket_port): + async with serve(self._listener, self._websocket_host, self._websocket_port): await asyncio.Future() # run forever def set_scene(self):