Loading code/episode_7/src/nova_tutorial/models/pyvista.py 0 → 100644 +24 −0 Original line number Diff line number Diff line """Configuration for the PyVista example.""" from pydantic import BaseModel, Field from pyvista import Plotter, examples KNEE_DATA = examples.download_knee_full() class PyVistaConfig(BaseModel): """Configuration class for the PyVista example.""" colormap_options: list[str] = ["viridis", "autumn", "coolwarm", "twilight", "jet"] opacity_options: list[str] = ["linear", "sigmoid"] colormap: str = Field(default="viridis", title="Color Transfer Function") opacity: str = Field(default="linear", title="Opacity Transfer Function") def render(self, plotter: Plotter) -> None: # If re-rendering the volume on changes isn't acceptable, then you may need to switch to using VTK directly due # limitations of the PyVista volume rendering engine. plotter.clear() plotter.add_volume(KNEE_DATA, cmap=self.colormap, opacity=self.opacity, show_scalar_bar=False) plotter.render() plotter.view_isometric() code/episode_7/src/nova_tutorial/view_models/main.py +7 −0 Original line number Diff line number Diff line Loading @@ -3,9 +3,11 @@ from typing import Any, Dict from nova.mvvm.interface import BindingInterface from pyvista import Plotter # just for typing from ..models.main_model import MainModel from ..models.plotly import PlotlyConfig from ..models.pyvista import PyVistaConfig class MainViewModel: Loading @@ -14,11 +16,13 @@ class MainViewModel: def __init__(self, model: MainModel, binding: BindingInterface): self.model = model self.plotly_config = PlotlyConfig() self.pyvista_config = PyVistaConfig() self.plotly_config_bind = binding.new_bind( linked_object=self.plotly_config, callback_after_update=self.update_plotly_figure ) self.plotly_figure_bind = binding.new_bind() self.pyvista_config_bind = binding.new_bind(linked_object=self.pyvista_config) # here we create a bind that connects ViewModel with View. It returns a communicator object, # that allows to update View from ViewModel (by calling update_view). Loading @@ -39,3 +43,6 @@ class MainViewModel: def update_plotly_figure(self, _: Any = None) -> None: self.plotly_config_bind.update_in_view(self.plotly_config) self.plotly_figure_bind.update_in_view(self.plotly_config.get_figure()) def update_pyvista_volume(self, plotter: Plotter) -> None: self.pyvista_config.render(plotter) code/episode_7/src/nova_tutorial/views/pyvista.py 0 → 100644 +44 −0 Original line number Diff line number Diff line """View for the 3d plot using PyVista.""" from typing import Any, Optional import pyvista as pv from nova.trame.view.components import InputField from nova.trame.view.layouts import GridLayout, HBoxLayout from pyvista.trame.ui import plotter_ui from trame.widgets import vuetify3 as vuetify from ..view_models.main import MainViewModel class PyVistaView: """View class for the 3d plot using PyVista.""" def __init__(self, view_model: MainViewModel) -> None: self.view_model = view_model self.view_model.pyvista_config_bind.connect("pyvista_config") self.plotter: Optional[pv.Plotter] = None self.create_plotter() self.create_ui() def create_plotter(self) -> None: self.plotter = pv.Plotter(off_screen=True) def create_ui(self) -> None: vuetify.VCardTitle("PyVista") with GridLayout(columns=5, classes="mb-2", valign="center"): InputField( v_model="pyvista_config.colormap", column_span=2, items="pyvista_config.colormap_options", type="select" ) InputField( v_model="pyvista_config.opacity", column_span=2, items="pyvista_config.opacity_options", type="select" ) vuetify.VBtn("Render", click=self.update) with HBoxLayout(halign="center", height="50vh"): plotter_ui(self.plotter) def update(self, _: Any = None) -> None: if self.plotter: self.view_model.update_pyvista_volume(self.plotter) code/episode_7/src/nova_tutorial/views/tab_content_panel.py 0 → 100644 +28 −0 Original line number Diff line number Diff line """Module for the Tab Content panel.""" from trame.widgets import vuetify3 as vuetify from trame_server import Server from ..view_models.main import MainViewModel from ..views.plotly import PlotlyView from ..views.pyvista import PyVistaView class TabContentPanel: """View class to render content for a selected tab.""" def __init__(self, server: Server, view_model: MainViewModel) -> None: self.view_model = view_model self.server = server self.ctrl = server.controller self.create_ui() def create_ui(self) -> None: with vuetify.VForm(ref="form") as self.f: with vuetify.VContainer(classes="pa-0", fluid=True): with vuetify.VCard(): with vuetify.VWindow(v_model="active_tab"): with vuetify.VWindowItem(value=1): PlotlyView(self.view_model) with vuetify.VWindowItem(value=2): PyVistaView(self.view_model) code/episode_7/src/nova_tutorial/views/tabs_panel.py 0 → 100644 +19 −0 Original line number Diff line number Diff line """Module for the Tab panel.""" from trame.widgets import vuetify3 as vuetify from ..view_models.main import MainViewModel class TabsPanel: """View class to render tabs.""" def __init__(self, view_model: MainViewModel): self.view_model = view_model self.view_model.config_bind.connect("config") self.create_ui() def create_ui(self) -> None: with vuetify.VTabs(v_model=("active_tab", 0), classes="pl-5"): vuetify.VTab("Plotly", value=1) vuetify.VTab("PyVista", value=2) Loading
code/episode_7/src/nova_tutorial/models/pyvista.py 0 → 100644 +24 −0 Original line number Diff line number Diff line """Configuration for the PyVista example.""" from pydantic import BaseModel, Field from pyvista import Plotter, examples KNEE_DATA = examples.download_knee_full() class PyVistaConfig(BaseModel): """Configuration class for the PyVista example.""" colormap_options: list[str] = ["viridis", "autumn", "coolwarm", "twilight", "jet"] opacity_options: list[str] = ["linear", "sigmoid"] colormap: str = Field(default="viridis", title="Color Transfer Function") opacity: str = Field(default="linear", title="Opacity Transfer Function") def render(self, plotter: Plotter) -> None: # If re-rendering the volume on changes isn't acceptable, then you may need to switch to using VTK directly due # limitations of the PyVista volume rendering engine. plotter.clear() plotter.add_volume(KNEE_DATA, cmap=self.colormap, opacity=self.opacity, show_scalar_bar=False) plotter.render() plotter.view_isometric()
code/episode_7/src/nova_tutorial/view_models/main.py +7 −0 Original line number Diff line number Diff line Loading @@ -3,9 +3,11 @@ from typing import Any, Dict from nova.mvvm.interface import BindingInterface from pyvista import Plotter # just for typing from ..models.main_model import MainModel from ..models.plotly import PlotlyConfig from ..models.pyvista import PyVistaConfig class MainViewModel: Loading @@ -14,11 +16,13 @@ class MainViewModel: def __init__(self, model: MainModel, binding: BindingInterface): self.model = model self.plotly_config = PlotlyConfig() self.pyvista_config = PyVistaConfig() self.plotly_config_bind = binding.new_bind( linked_object=self.plotly_config, callback_after_update=self.update_plotly_figure ) self.plotly_figure_bind = binding.new_bind() self.pyvista_config_bind = binding.new_bind(linked_object=self.pyvista_config) # here we create a bind that connects ViewModel with View. It returns a communicator object, # that allows to update View from ViewModel (by calling update_view). Loading @@ -39,3 +43,6 @@ class MainViewModel: def update_plotly_figure(self, _: Any = None) -> None: self.plotly_config_bind.update_in_view(self.plotly_config) self.plotly_figure_bind.update_in_view(self.plotly_config.get_figure()) def update_pyvista_volume(self, plotter: Plotter) -> None: self.pyvista_config.render(plotter)
code/episode_7/src/nova_tutorial/views/pyvista.py 0 → 100644 +44 −0 Original line number Diff line number Diff line """View for the 3d plot using PyVista.""" from typing import Any, Optional import pyvista as pv from nova.trame.view.components import InputField from nova.trame.view.layouts import GridLayout, HBoxLayout from pyvista.trame.ui import plotter_ui from trame.widgets import vuetify3 as vuetify from ..view_models.main import MainViewModel class PyVistaView: """View class for the 3d plot using PyVista.""" def __init__(self, view_model: MainViewModel) -> None: self.view_model = view_model self.view_model.pyvista_config_bind.connect("pyvista_config") self.plotter: Optional[pv.Plotter] = None self.create_plotter() self.create_ui() def create_plotter(self) -> None: self.plotter = pv.Plotter(off_screen=True) def create_ui(self) -> None: vuetify.VCardTitle("PyVista") with GridLayout(columns=5, classes="mb-2", valign="center"): InputField( v_model="pyvista_config.colormap", column_span=2, items="pyvista_config.colormap_options", type="select" ) InputField( v_model="pyvista_config.opacity", column_span=2, items="pyvista_config.opacity_options", type="select" ) vuetify.VBtn("Render", click=self.update) with HBoxLayout(halign="center", height="50vh"): plotter_ui(self.plotter) def update(self, _: Any = None) -> None: if self.plotter: self.view_model.update_pyvista_volume(self.plotter)
code/episode_7/src/nova_tutorial/views/tab_content_panel.py 0 → 100644 +28 −0 Original line number Diff line number Diff line """Module for the Tab Content panel.""" from trame.widgets import vuetify3 as vuetify from trame_server import Server from ..view_models.main import MainViewModel from ..views.plotly import PlotlyView from ..views.pyvista import PyVistaView class TabContentPanel: """View class to render content for a selected tab.""" def __init__(self, server: Server, view_model: MainViewModel) -> None: self.view_model = view_model self.server = server self.ctrl = server.controller self.create_ui() def create_ui(self) -> None: with vuetify.VForm(ref="form") as self.f: with vuetify.VContainer(classes="pa-0", fluid=True): with vuetify.VCard(): with vuetify.VWindow(v_model="active_tab"): with vuetify.VWindowItem(value=1): PlotlyView(self.view_model) with vuetify.VWindowItem(value=2): PyVistaView(self.view_model)
code/episode_7/src/nova_tutorial/views/tabs_panel.py 0 → 100644 +19 −0 Original line number Diff line number Diff line """Module for the Tab panel.""" from trame.widgets import vuetify3 as vuetify from ..view_models.main import MainViewModel class TabsPanel: """View class to render tabs.""" def __init__(self, view_model: MainViewModel): self.view_model = view_model self.view_model.config_bind.connect("config") self.create_ui() def create_ui(self) -> None: with vuetify.VTabs(v_model=("active_tab", 0), classes="pl-5"): vuetify.VTab("Plotly", value=1) vuetify.VTab("PyVista", value=2)