Skip to content

Commit d5a3f77

Browse files
committed
refactor: split view manager into view panel and layout manager
1 parent 765dc37 commit d5a3f77

2 files changed

Lines changed: 198 additions & 188 deletions

File tree

src/e3sm_quickview/view_manager.py

Lines changed: 3 additions & 188 deletions
Original file line numberDiff line numberDiff line change
@@ -2,30 +2,24 @@
22
import math
33
import time
44

5-
# Rendering Factory
6-
import vtkmodules.vtkRenderingOpenGL2 # noqa: F401
75
from paraview.modules.vtkPVVTKExtensionsInteractionStyle import (
86
vtkPVInteractorStyle,
97
vtkPVTrackballZoom,
108
vtkTrackballPan,
119
)
12-
from trame.app import TrameComponent, dataclass
13-
from trame.dataclasses.colormaps import ColormapConfig
10+
from trame.app import TrameComponent
1411
from trame.decorators import controller
1512
from trame.ui.html import DivLayout
16-
from trame.widgets import client, colormaps, html, rca
13+
from trame.widgets import client, colormaps, rca
1714
from trame.widgets import vuetify3 as v3
1815
from vtkmodules.vtkRenderingCore import (
19-
vtkActor,
2016
vtkCamera,
21-
vtkPolyDataMapper,
22-
vtkRenderer,
2317
vtkRenderWindow,
2418
vtkRenderWindowInteractor,
2519
)
2620

27-
from e3sm_quickview.components import view as tview
2821
from e3sm_quickview.utils import perf
22+
from e3sm_quickview.view_panel import VariableView
2923

3024

3125
def auto_size_to_col(size):
@@ -56,185 +50,6 @@ def auto_size_to_col(size):
5650
}
5751

5852

59-
class ViewConfiguration(dataclass.StateDataModel):
60-
# --- View identity ---
61-
variable: str = dataclass.Sync(str)
62-
63-
# --- Layout ---
64-
order: int = dataclass.Sync(int, 0)
65-
size: int = dataclass.Sync(int, 6)
66-
offset: int = dataclass.Sync(int, 0)
67-
break_row: bool = dataclass.Sync(bool, False)
68-
swap_group: list[str] = dataclass.Sync(list[str], list)
69-
70-
71-
class VariableView(TrameComponent):
72-
def __init__(self, server, source, variable_name, variable_type, camera):
73-
super().__init__(server)
74-
self.source = source
75-
self.variable_name = variable_name
76-
self.variable_type = variable_type
77-
self.disable_render = False
78-
self.name = f"view_{self.variable_name}"
79-
self._bounds_key = f"{self.name}_bounds"
80-
self.config = ViewConfiguration(server, variable=variable_name)
81-
self._size = (0, 0)
82-
83-
# VTK
84-
self.renderer = vtkRenderer(
85-
active_camera=camera,
86-
background=(84 / 255, 89 / 255, 109 / 255),
87-
background2=(0, 0, 42 / 255),
88-
gradient_background=1,
89-
)
90-
self._camera = camera
91-
92-
input = source.data_reader.vtk_geometry
93-
self.mapper = vtkPolyDataMapper(input_connection=input.output_port)
94-
self.actor = vtkActor(mapper=self.mapper)
95-
self.renderer.AddActor(self.actor)
96-
97-
# Add annotation to the view (continents, gridlines)
98-
self.renderer.AddActor(source.continent.actor)
99-
self.renderer.AddActor(source.grid_lines.actor)
100-
101-
# colormaps module: creates LUT, wires mapper, manages presets/range/ticks
102-
self.colormap = ColormapConfig(
103-
server,
104-
mapper=self.mapper,
105-
data_array_fn=lambda: self.data_array,
106-
).set_data_array(variable_name, lambda: self.data_array, "cell")
107-
self.colormap.watch(["mapper_change"], lambda *_: self.render())
108-
109-
# GUI
110-
self._build_ui()
111-
112-
@property
113-
def bounds(self):
114-
return self.state[self._bounds_key]
115-
116-
@bounds.setter
117-
def bounds(self, v):
118-
self.renderer.SetViewport(*v)
119-
with self.state as s:
120-
s[self._bounds_key] = v
121-
122-
def reset_camera(self):
123-
self.renderer.ResetCameraScreenSpace(0.9)
124-
125-
def update_size(self, size):
126-
new_size = (int(size["w"] * size["p"]), int(size["h"] * size["p"]))
127-
if self._size != new_size:
128-
self._size = new_size
129-
self.ctrl.size_update()
130-
131-
@property
132-
def size(self):
133-
return self._size
134-
135-
def render(self):
136-
if self.ctx.view:
137-
self.ctx.view.update()
138-
139-
@property
140-
def data_array(self):
141-
self.source.data_reader.vtk_geometry.Update()
142-
ds = self.source.data_reader.vtk_geometry.GetOutput()
143-
return ds.GetCellData().GetArray(self.variable_name)
144-
145-
def _build_ui(self):
146-
with DivLayout(
147-
self.server, template_name=self.name, connect_parent=False, classes="h-100"
148-
) as self.ui:
149-
self.ui.root.classes = "h-100"
150-
with v3.VCard(
151-
variant="tonal",
152-
style=(
153-
"active_layout !== 'auto_layout' ? `height: calc(100% - ${toolbar_size?.size?.height || 0}px)` : 'overflow-hidden'",
154-
),
155-
tile=("active_layout !== 'auto_layout'",),
156-
raw_attrs=[f'data-field-name="{self.variable_name}"'],
157-
):
158-
with v3.VRow(
159-
dense=True,
160-
classes="ma-0 pa-0 bg-black opacity-90 d-flex align-center flex-nowrap",
161-
):
162-
tview.create_size_menu(self.name, self.config)
163-
with html.Div(
164-
self.variable_name,
165-
classes="text-subtitle-2 pr-2 text-truncate",
166-
style="user-select: none;",
167-
title=self.variable_name,
168-
):
169-
with v3.VMenu(activator="parent"):
170-
with v3.VList(density="compact", style="max-height: 40vh;"):
171-
with self.config.provide_as("config"):
172-
v3.VListItem(
173-
subtitle=("name",),
174-
v_for="name, idx in config.swap_group",
175-
key="name",
176-
click=(
177-
self.ctrl.swap_variables,
178-
"[config.variable, name]",
179-
),
180-
)
181-
182-
v3.VIconBtn(
183-
v_tooltip_bottom="'Capture as png'",
184-
icon="mdi-camera-outline",
185-
size="small",
186-
variant="plain",
187-
click=f"utils.quickview.capturePanel('{self.variable_name}')",
188-
style="transform: scale(0.75);",
189-
)
190-
191-
v3.VSpacer()
192-
html.Div(
193-
"t = {{ time_idx }}",
194-
classes="text-caption px-1 text-no-wrap",
195-
v_if="timestamps.length > 1",
196-
)
197-
if self.variable_type == "m":
198-
html.Div(
199-
"[k = {{ midpoint_idx }}]",
200-
classes="text-caption px-1 text-no-wrap",
201-
v_if="midpoints.length > 1",
202-
)
203-
if self.variable_type == "i":
204-
html.Div(
205-
"[k = {{ interface_idx }}]",
206-
classes="text-caption px-1 text-no-wrap",
207-
v_if="interfaces.length > 1",
208-
)
209-
v3.VSpacer()
210-
html.Div(
211-
"avg = {{"
212-
f"fields_avgs['{self.variable_name}']?.toExponential(2) || 'N/A'"
213-
"}}",
214-
classes="text-caption px-1 text-no-wrap",
215-
)
216-
217-
with html.Div(
218-
style=(
219-
"""
220-
{
221-
aspectRatio: active_layout === 'auto_layout' ? (1.0 / aspect_ratio) : null,
222-
height: active_layout !== 'auto_layout' ? 'calc(100% - 2.4rem)' : null,
223-
pointerEvents: 'none',
224-
}
225-
""",
226-
),
227-
):
228-
rca.ImageRegion(
229-
enable_interaction=False,
230-
bounds=(self._bounds_key, (0, 0, 1, 1)),
231-
size=(self.update_size, "[$event]"),
232-
)
233-
234-
with self.colormap.provide_as(self.name):
235-
colormaps.HorizontalScalarBar(self.name, popup_location="top")
236-
237-
23853
class ViewManager(TrameComponent):
23954
def __init__(self, server, source):
24055
super().__init__(server)

0 commit comments

Comments
 (0)