Skip to content

Commit ab3b640

Browse files
committed
fix(listeners): allow dynamic update
1 parent cbc01b9 commit ab3b640

File tree

3 files changed

+123
-51
lines changed

3 files changed

+123
-51
lines changed

examples/vtk/widgets_plane.py

Lines changed: 50 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -125,21 +125,36 @@ def __init__(self, server=None):
125125
args, _ = self.server.cli.parse_known_args()
126126
self.render_window, self.widget, self.plane = create_vtk_pipeline(args.data)
127127

128+
# Allocation state variable for widget state
129+
self.state.plane_widget = None
130+
131+
# Build UI
128132
self.html_view = None
129133
self.ui = self._ui()
130134

131-
def register_widget_listeners(self):
132-
self.html_view.register_widget(self.widget)
135+
@property
136+
def state(self):
137+
return self.server.state
133138

134-
# extract wasm ids
135-
widget_id = self.html_view.get_wasm_id(self.widget)
136-
rep_id = self.html_view.get_wasm_id(self.widget.representation)
139+
@change("plane_widget")
140+
def _on_widget_update(self, plane_widget, **_):
141+
if plane_widget is None:
142+
return
137143

138-
# init state vars and listener properties
139-
self.server.state.plane_widget = None
140-
self.html_view.listeners = (
141-
"wasm_listeners",
142-
{
144+
# update cutting plane
145+
self.plane.SetNormal(plane_widget.get("normal"))
146+
self.plane.SetOrigin(plane_widget.get("origin"))
147+
148+
# prevent requesting geometry too often
149+
self.html_view.render_throttle()
150+
151+
def toggle_listeners(self):
152+
if self.state.wasm_listeners is not None and len(self.state.wasm_listeners):
153+
self.state.wasm_listeners = {}
154+
else:
155+
widget_id = self.html_view.get_wasm_id(self.widget)
156+
rep_id = self.html_view.get_wasm_id(self.widget.representation)
157+
self.state.wasm_listeners = {
143158
widget_id: {
144159
"InteractionEvent": {
145160
"plane_widget": {
@@ -150,32 +165,43 @@ def register_widget_listeners(self):
150165
}
151166
}
152167
}
153-
},
154-
)
168+
}
155169

156-
@change("plane_widget")
157-
def _on_widget_update(self, plane_widget, **_):
158-
if plane_widget is None:
159-
return
160-
161-
# update cutting plane
162-
self.plane.SetNormal(plane_widget.get("normal"))
163-
self.plane.SetOrigin(plane_widget.get("origin"))
164-
165-
# prevent requesting geometry too often
166-
self.html_view.render_throttle()
170+
def one_time_update(self):
171+
rep_id = self.html_view.get_wasm_id(self.widget.representation)
172+
self.html_view.eval(
173+
{
174+
"plane_widget": {
175+
rep_id: {
176+
"Normal": "normal",
177+
"Origin": "origin",
178+
}
179+
}
180+
}
181+
)
167182

168183
def _ui(self):
169184
with DivLayout(self.server) as layout:
170185
client.Style("body { margin: 0; }")
186+
html.Button(
187+
"Toggle listeners",
188+
click=self.toggle_listeners,
189+
style="position: absolute; left: 1rem; top: 1rem; z-index: 10;",
190+
)
191+
html.Button(
192+
"Update cut",
193+
click=self.one_time_update,
194+
style="position: absolute; right: 1rem; top: 1rem; z-index: 10;",
195+
)
171196
with html.Div(
172197
style="position: absolute; left: 0; top: 0; width: 100vw; height: 100vh;"
173198
):
174199
self.html_view = vtklocal.LocalView(
175200
self.render_window,
176201
throttle_rate=20,
202+
listeners=("wasm_listeners", {}),
177203
)
178-
self.register_widget_listeners()
204+
self.html_view.register_widget(self.widget)
179205

180206
return layout
181207

trame_vtklocal/widgets/vtklocal.py

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ def __init__(self, render_window, throttle_rate=10, **kwargs):
7171
self._attr_names += [
7272
("cache_size", "cacheSize"),
7373
("eager_sync", "eagerSync"),
74-
"listeners", # only processed at mount time for now
74+
("listeners", ":listeners"),
7575
]
7676
self._event_names += [
7777
"updated",
@@ -94,6 +94,24 @@ def object_manager(self):
9494
"""Return object_manager"""
9595
return self.api.vtk_object_manager
9696

97+
def eval(self, state_mapping):
98+
"""Evaluate WASM state extract and map it onto trame state variables
99+
100+
state_mapping = {
101+
trame_state_name: {
102+
wasm_id1: {
103+
wasm_state_prop_name: js_name_in_trame_state_name_obj1
104+
wasm_state_prop_name2: js_name2_in_trame_state_name_obj1
105+
},
106+
wasm_id2: {
107+
wasm_state_prop_name: js_name_in_trame_state_name_obj2
108+
wasm_state_prop_name2: js_name2_in_trame_state_name_obj2
109+
},
110+
}
111+
}
112+
"""
113+
self.server.js_call(self.__ref, "evalStateExtract", state_mapping)
114+
97115
def update(self, push_camera=False):
98116
"""Sync view by pushing updates to client"""
99117
self.api.update(push_camera=push_camera)

vue-components/src/components/VtkLocal.js

Lines changed: 54 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,18 @@
1-
import { inject, ref, unref, onMounted, onBeforeUnmount } from "vue";
1+
import {
2+
inject,
3+
ref,
4+
unref,
5+
onMounted,
6+
onBeforeUnmount,
7+
toRef,
8+
watchEffect,
9+
} from "vue";
210
import { createModule } from "../utils";
311

412
function idToState(sceneManager, cid) {
5-
sceneManager.updateStateFromObject(cid);
6-
return sceneManager.getState(cid);
13+
const wasmId = Number(cid);
14+
sceneManager.updateStateFromObject(wasmId);
15+
return sceneManager.getState(wasmId);
716
}
817

918
function createExtractCallback(trame, sceneManager, extractInfo) {
@@ -64,14 +73,16 @@ export default {
6473
const trame = inject("trame");
6574
const wasmURL = trame.state.get("__trame_vtklocal_wasm_url");
6675
const cameraIds = [];
67-
const observerTags = [];
76+
const cameraTags = [];
77+
const listenersTags = [];
6878
const container = ref(null);
6979
const canvas = ref(null);
7080
const client = props.wsClient || trame?.client;
7181
const stateMTimes = {};
7282
const hashesMTime = {};
7383
const pendingArrays = {};
7484
const currentMTime = ref(1);
85+
const listeners = toRef(props, "listeners");
7586
let sceneManager = null;
7687
let updateInProgress = 0;
7788
let subscription = null;
@@ -238,7 +249,6 @@ export default {
238249

239250
// For listeners
240251
cameraIds.push(...serverStatus.cameras);
241-
// interactorId = serverStatus.interactor;
242252

243253
serverStatus.ignore_ids.forEach((vtkId) => {
244254
sceneManager.unRegisterState(vtkId);
@@ -295,31 +305,15 @@ export default {
295305

296306
// Camera listener
297307
for (let i = 0; i < cameraIds.length; i++) {
298-
const cid = cameraIds[i];
299-
observerTags.push([
308+
const cid = Number(cameraIds[i]);
309+
cameraTags.push([
300310
cid,
301311
sceneManager.addObserver(cid, "ModifiedEvent", () => {
302-
sceneManager.updateStateFromObject(cid);
303-
const cameraState = sceneManager.getState(cid);
304-
emit("camera", cameraState);
312+
emit("camera", idToState(sceneManager, cid));
305313
}),
306314
]);
307315
}
308316

309-
// Other listeners
310-
for (const [cid, eventMap] of Object.entries(props.listeners || {})) {
311-
for (const [eventName, extractInfo] of Object.entries(eventMap || {})) {
312-
observerTags.push([
313-
cid,
314-
sceneManager.addObserver(
315-
cid,
316-
eventName,
317-
createExtractCallback(trame, sceneManager, extractInfo)
318-
),
319-
]);
320-
}
321-
}
322-
323317
sceneManager.startEventLoop(props.renderWindow);
324318
if (resizeObserver) {
325319
resizeObserver.observe(unref(container));
@@ -332,8 +326,12 @@ export default {
332326
}
333327

334328
// Camera listeners
335-
while (observerTags.length) {
336-
const [cid, tag] = observerTags.pop();
329+
while (cameraTags.length) {
330+
const [cid, tag] = cameraTags.pop();
331+
sceneManager.removeObserver(cid, tag);
332+
}
333+
while (listenersTags.length) {
334+
const [cid, tag] = listenersTags.pop();
337335
sceneManager.removeObserver(cid, tag);
338336
}
339337

@@ -345,12 +343,42 @@ export default {
345343
}
346344
});
347345

346+
// Dynamic props ----------------------------------------------------------
347+
348+
watchEffect(() => {
349+
while (listenersTags.length) {
350+
const [cid, tag] = listenersTags.pop();
351+
sceneManager.removeObserver(cid, tag);
352+
}
353+
354+
for (const [cid, eventMap] of Object.entries(listeners.value || {})) {
355+
const wasmId = Number(cid);
356+
for (const [eventName, extractInfo] of Object.entries(eventMap || {})) {
357+
const fn = createExtractCallback(trame, sceneManager, extractInfo);
358+
listenersTags.push([
359+
wasmId,
360+
sceneManager.addObserver(wasmId, eventName, fn),
361+
]);
362+
363+
// Push update at registration
364+
fn();
365+
}
366+
}
367+
});
368+
348369
// Public -----------------------------------------------------------------
370+
371+
function evalStateExtract(definition) {
372+
console.log("definition", definition);
373+
createExtractCallback(trame, sceneManager, definition)();
374+
}
375+
349376
return {
350377
container,
351378
canvas,
352379
update,
353380
resetCamera,
381+
evalStateExtract,
354382
};
355383
},
356384
template: `

0 commit comments

Comments
 (0)