Skip to content

Commit 1eee4b7

Browse files
Merge pull request #31 from Kitware/fix-register-rca
fix: use root server controller to call rc_area_register
2 parents b1aebe1 + 7dc963d commit 1eee4b7

File tree

10 files changed

+59
-89
lines changed

10 files changed

+59
-89
lines changed

.github/workflows/test_and_release.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,8 @@ jobs:
7575
run: |
7676
pip install ".[turbo]"
7777
pip install -r tests/requirements.txt
78+
# Install requirements for playwright
79+
playwright install
7880
pytest -s ./tests
7981
env:
8082
CI: true

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ dist
99
.env.*.local
1010

1111
# Log files
12+
*.log
1213
npm-debug.log*
1314
yarn-debug.log*
1415
yarn-error.log*

examples/00_vanilla/vanilla_example.py

Lines changed: 4 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
from numpy import asarray
22
from pathlib import Path
33
from PIL import Image
4+
from trame.app import TrameApp
45
from trame.app.testing import enable_testing
5-
from trame.decorators import TrameApp, change
6+
from trame.decorators import change
67
from trame_rca.widgets import rca
7-
from trame.app import get_server
88
from trame.ui.vuetify3 import SinglePageLayout
99
from trame.widgets import vuetify3 as v3
1010
from trame_client.module.vue3 import www
@@ -50,22 +50,13 @@ def process_interaction_event(self, event):
5050
self._image_angle += spin * self.rotation_step
5151

5252

53-
@TrameApp()
54-
class VanillaApp:
53+
class VanillaApp(TrameApp):
5554
def __init__(self, server=None):
56-
self.server = get_server(server, client_type="vue3")
55+
super().__init__(server)
5756
image_path = Path(www) / "logo.png"
5857
self.window = RotatableImageWindow(image_path)
5958
self._build_ui()
6059

61-
@property
62-
def state(self):
63-
return self.server.state
64-
65-
@property
66-
def ctrl(self):
67-
return self.server.controller
68-
6960
@change("rotation_step")
7061
def update_rotation_step(self, rotation_step, **kwargs):
7162
self.window.rotation_step = rotation_step
@@ -100,7 +91,6 @@ def _build_ui(self):
10091
classes="pa-0 fill-height position-relative",
10192
):
10293
view = rca.RemoteControlledArea(
103-
name="view",
10494
display="image",
10595
image_style=({},), # restore default style with width: 100%
10696
)

examples/01_vtk/vtk_cone_bare_jpg.py

Lines changed: 8 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@
44
import json
55
import asyncio
66

7-
from trame.app import get_server, asynchronous
7+
from trame.app import TrameApp, asynchronous
88
from trame.app.testing import enable_testing
9-
from trame.decorators import TrameApp, change, life_cycle
9+
from trame.decorators import change
1010
from trame.ui.vuetify3 import SinglePageLayout
1111
from trame.widgets import vuetify3 as v3
1212

@@ -155,23 +155,13 @@ def on_interaction(self, origin, event):
155155
vtkRemoteInteractionAdapter.ProcessEvent(self._iren, event_str)
156156

157157

158-
@TrameApp()
159-
class ConeApp:
158+
class ConeApp(TrameApp):
160159
def __init__(self, server=None):
161-
self.server = get_server(server, client_type="vue3")
160+
super().__init__(server)
162161

163162
self.render_window, self.cone_source = self.setup_vtk()
164-
self.view_handler = ViewAdapter(self.render_window, "view")
165163
self.build_ui()
166164

167-
@property
168-
def state(self):
169-
return self.server.state
170-
171-
@property
172-
def ctrl(self):
173-
return self.server.controller
174-
175165
def setup_vtk(self):
176166
renderer = vtkRenderer()
177167
renderWindow = vtkRenderWindow()
@@ -215,13 +205,14 @@ def build_ui(self):
215205
fluid=True,
216206
classes="pa-0 fill-height position-relative",
217207
):
218-
rca.RemoteControlledArea(
219-
name="view",
208+
view = rca.RemoteControlledArea(
220209
display="image",
221210
)
211+
self.view_handler = ViewAdapter(self.render_window, view.name)
212+
view.add_view_handler(self.view_handler)
222213
with v3.VCard(classes="pa-4 ma-0", style=STATS_STYLES):
223214
rca.StatisticsDisplay(
224-
name="view",
215+
name=view.name,
225216
fps_delta=1.5,
226217
stat_window_size=10,
227218
history_window_size=30,
@@ -233,11 +224,6 @@ def update_cone(self, resolution, **kwargs):
233224
self.cone_source.SetResolution(resolution)
234225
self.view_handler.render()
235226

236-
@life_cycle.server_ready
237-
def on_server_ready(self, **_):
238-
# can only be called when server is ready
239-
self.ctrl.rc_area_register(self.view_handler)
240-
241227
def update_reset_resolution(self):
242228
self.state.resolution = DEFAULT_RESOLUTION
243229

examples/01_vtk/vtk_cone_bare_raw.py

Lines changed: 8 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@
44
import json
55
import asyncio
66

7-
from trame.app import get_server, asynchronous
7+
from trame.app import TrameApp, asynchronous
88
from trame.app.testing import enable_testing
9-
from trame.decorators import TrameApp, change, life_cycle
9+
from trame.decorators import change
1010
from trame.ui.vuetify3 import SinglePageLayout
1111
from trame.widgets import vuetify3 as v3
1212

@@ -137,23 +137,13 @@ def on_interaction(self, origin, event):
137137
vtkRemoteInteractionAdapter.ProcessEvent(self._iren, event_str)
138138

139139

140-
@TrameApp()
141-
class ConeApp:
140+
class ConeApp(TrameApp):
142141
def __init__(self, server=None):
143-
self.server = get_server(server, client_type="vue3")
142+
super().__init__(server)
144143

145144
self.render_window, self.cone_source = self.setup_vtk()
146-
self.view_handler = ViewAdapter(self.render_window, "view")
147145
self.build_ui()
148146

149-
@property
150-
def state(self):
151-
return self.server.state
152-
153-
@property
154-
def ctrl(self):
155-
return self.server.controller
156-
157147
def setup_vtk(self):
158148
renderer = vtkRenderer()
159149
renderWindow = vtkRenderWindow()
@@ -197,13 +187,14 @@ def build_ui(self):
197187
fluid=True,
198188
classes="pa-0 fill-height position-relative",
199189
):
200-
rca.RemoteControlledArea(
201-
name="view",
190+
view = rca.RemoteControlledArea(
202191
display="raw-image",
203192
)
193+
self.view_handler = ViewAdapter(self.render_window, view.name)
194+
view.add_view_handler(self.view_handler)
204195
with v3.VCard(classes="pa-4 ma-0", style=STATS_STYLES):
205196
rca.StatisticsDisplay(
206-
name="view",
197+
name=view.name,
207198
fps_delta=1.5,
208199
stat_window_size=10,
209200
history_window_size=30,
@@ -215,11 +206,6 @@ def update_cone(self, resolution, **kwargs):
215206
self.cone_source.SetResolution(resolution)
216207
self.view_handler.render()
217208

218-
@life_cycle.server_ready
219-
def on_server_ready(self, **_):
220-
# can only be called when server is ready
221-
self.ctrl.rc_area_register(self.view_handler)
222-
223209
def update_reset_resolution(self):
224210
self.state.resolution = DEFAULT_RESOLUTION
225211

examples/01_vtk/vtk_cone_simple.py

Lines changed: 5 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@
22
# local rendering, but doesn't hurt to include it
33
import asyncio
44

5-
from trame.app import get_server, asynchronous
5+
from trame.app import TrameApp, asynchronous
66
from trame.app.testing import enable_testing
7-
from trame.decorators import TrameApp, change, life_cycle
7+
from trame.decorators import change, life_cycle
88
from trame.ui.vuetify3 import SinglePageLayout
99
from trame.widgets import vuetify3 as v3
1010

@@ -37,27 +37,17 @@
3737
"""
3838

3939

40-
@TrameApp()
41-
class ConeApp:
40+
class ConeApp(TrameApp):
4241
def __init__(self, server=None):
43-
self.server = get_server(server, client_type="vue3")
42+
super().__init__(server)
4443

4544
self.server.cli.add_argument("--encoder", default="jpeg")
4645
args, _ = self.server.cli.parse_known_args()
4746
self.state.encoder = args.encoder
4847

49-
self.view_handler = None
5048
self.render_window, self.cone_source = self.setup_vtk()
5149
self.build_ui()
5250

53-
@property
54-
def state(self):
55-
return self.server.state
56-
57-
@property
58-
def ctrl(self):
59-
return self.server.controller
60-
6151
def setup_vtk(self):
6252
renderer = vtkRenderer()
6353
renderWindow = vtkRenderWindow()
@@ -133,7 +123,6 @@ def build_ui(self):
133123
classes="pa-0 fill-height position-relative",
134124
):
135125
view = rca.RemoteControlledArea(
136-
name="view",
137126
display="image",
138127
)
139128
self.view_handler = view.create_view_handler(
@@ -142,7 +131,7 @@ def build_ui(self):
142131
)
143132
with v3.VCard(classes="pa-4 ma-0", style=STATS_STYLES):
144133
rca.StatisticsDisplay(
145-
name="view",
134+
name=view.name,
146135
fps_delta=1.5,
147136
stat_window_size=10,
148137
history_window_size=30,

examples/02_doom/doom.wad

-12.4 MB
Binary file not shown.

tests/requirements.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@ pillow-avif-plugin
44
pixelmatch
55
pytest
66
pytest-asyncio
7+
pytest-playwright
78
pytest-xprocess
8-
seleniumbase
99
trame-server>=3
1010
trame-vuetify
1111
trame>=3.6

tests/test_rca_utils.py

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,7 @@
55
from pathlib import Path
66
from unittest.mock import MagicMock
77

8-
from selenium.webdriver import ActionChains
9-
from seleniumbase import SB
8+
from playwright.sync_api import expect, sync_playwright
109

1110
import pytest
1211
from PIL import Image
@@ -185,22 +184,26 @@ async def test_groups_close_request_render_together(
185184

186185
@pytest.mark.parametrize("server_path", ["examples/01_vtk/vtk_cone_simple.py"])
187186
def test_rca_view_is_interactive(server):
188-
with SB() as sb:
189-
assert server.port
190-
187+
with sync_playwright() as p:
191188
url = f"http://127.0.0.1:{server.port}/"
192-
sb.open(url)
189+
browser = p.chromium.launch()
190+
page = browser.new_page()
191+
page.goto(url)
193192

194-
element = sb.find_element("img")
193+
element = page.locator("img")
194+
expect(element).to_be_visible()
195195
initial_img_url = element.get_attribute("src")
196196

197-
ActionChains(sb.driver).move_to_element(element).perform()
198-
ActionChains(sb.driver).click_and_hold(element).move_by_offset(
199-
100, 0
200-
).release().perform()
197+
box = element.bounding_box()
198+
assert box is not None
201199

202-
# Expect image to have been updated following user action
200+
page.mouse.move(box["x"] + box["width"] / 2, box["y"] + box["height"] / 2)
201+
page.mouse.down()
202+
page.mouse.move(box["x"] + box["width"] / 2 + 100, box["y"] + box["height"] / 2)
203+
page.mouse.up()
204+
page.wait_for_timeout(100)
203205
new_img_url = element.get_attribute("src")
206+
204207
assert initial_img_url != new_img_url
205208

206209

trame_rca/widgets/rca.py

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,14 @@ def __init__(self, _elem_name, children=None, **kwargs):
2525

2626
# Expose your vue component(s)
2727
class RemoteControlledArea(HtmlElement):
28+
_next_id = 0
29+
2830
def __init__(self, **kwargs):
2931
super().__init__(
3032
"remote-controlled-area",
3133
**kwargs,
3234
)
35+
RemoteControlledArea._next_id += 1
3336
self._attr_names += [
3437
"name",
3538
"origin",
@@ -38,9 +41,19 @@ def __init__(self, **kwargs):
3841
("send_mouse_move", "sendMouseMove"),
3942
("event_throttle_ms", "eventThrottleMs"),
4043
]
44+
45+
self.name = kwargs.get("name") or f"trame_rca_{RemoteControlledArea._next_id}"
4146
self._handlers = []
4247
self.ctrl.on_server_ready.add(self._on_ready)
4348

49+
def add_view_handler(self, view_handler):
50+
if view_handler in self._handlers:
51+
return
52+
53+
self._handlers.append(view_handler)
54+
if self.server.running:
55+
self.server.root_server.controller.rc_area_register(view_handler)
56+
4457
def create_view_handler(
4558
self,
4659
render_window,
@@ -59,7 +72,7 @@ def create_view_handler(
5972
rca_encoder=encoder,
6073
)
6174
view_handler = RcaViewAdapter(render_window, self.name, scheduler=scheduler)
62-
self._handlers.append(view_handler)
75+
self.add_view_handler(view_handler)
6376
return view_handler
6477

6578
def create_vtk_handler(
@@ -82,7 +95,7 @@ def create_vtk_handler(
8295

8396
def _on_ready(self, **_):
8497
for handler in self._handlers:
85-
self.server.controller.rc_area_register(handler)
98+
self.server.root_server.controller.rc_area_register(handler)
8699

87100

88101
class DisplayArea(HtmlElement):

0 commit comments

Comments
 (0)