Skip to content

Commit cd15179

Browse files
committed
notebook
1 parent 249467b commit cd15179

File tree

4 files changed

+208
-164
lines changed

4 files changed

+208
-164
lines changed

nglview/widget.py

+6-46
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
seq_to_string)
3030
from .viewer_control import ViewerControl
3131
from ._frontend import __frontend_version__
32+
from .widget_base import WidgetBase
3233

3334
logger = getLogger(__name__)
3435

@@ -126,7 +127,7 @@ def _unset_serialization(views):
126127
f.write(html_code)
127128

128129

129-
class NGLWidget(DOMWidget):
130+
class NGLWidget(WidgetBase):
130131
_view_name = Unicode("NGLView").tag(sync=True)
131132
_view_module = Unicode("nglview-js-widgets").tag(sync=True)
132133
_view_module_version = Unicode(__frontend_version__).tag(sync=True)
@@ -135,7 +136,7 @@ class NGLWidget(DOMWidget):
135136
_model_module_version = Unicode(__frontend_version__).tag(sync=True)
136137
_ngl_version = Unicode().tag(sync=True)
137138

138-
# View and model attributes
139+
# View and model attributes
139140
_image_data = Unicode().tag(sync=False)
140141
_view_width = Unicode().tag(sync=True) # px
141142
_view_height = Unicode().tag(sync=True) # px
@@ -315,47 +316,6 @@ def _unset_serialization(self):
315316
self._ngl_serialize = False
316317
self._ngl_coordinate_resource = {}
317318

318-
@property
319-
def parameters(self):
320-
return self._parameters
321-
322-
@parameters.setter
323-
def parameters(self, params):
324-
params = _camelize_dict(params)
325-
self._parameters = params
326-
self._remote_call('setParameters', target='Widget', args=[
327-
params,
328-
])
329-
330-
@property
331-
def camera(self):
332-
return self._camera_str
333-
334-
@camera.setter
335-
def camera(self, value):
336-
"""
337-
338-
Parameters
339-
----------
340-
value : str, {'perspective', 'orthographic'}
341-
"""
342-
self._camera_str = value
343-
# use _remote_call so this function can be called right after
344-
# self is displayed
345-
self._remote_call("setParameters",
346-
target='Stage',
347-
kwargs=dict(cameraType=self._camera_str))
348-
349-
def _set_camera_orientation(self, arr):
350-
self._remote_call('set_camera_orientation',
351-
target='Widget',
352-
args=[
353-
arr,
354-
])
355-
356-
def _request_stage_parameters(self):
357-
self._remote_call('requestUpdateStageParameters', target='Widget')
358-
359319
@validate('gui_style')
360320
def _validate_gui_style(self, proposal):
361321
val = proposal['value']
@@ -1452,10 +1412,10 @@ class Fullscreen(DOMWidget):
14521412
_view_module = Unicode("nglview-js-widgets").tag(sync=True)
14531413
_view_module_version = Unicode(__frontend_version__).tag(sync=True)
14541414
_model_name = Unicode("FullscreenModel").tag(sync=True)
1455-
_model_module = Unicode("nglview-js-widgets").tag(sync=True)
1456-
_model_module_version = Unicode(__frontend_version__).tag(sync=True)
1415+
_model_module = Unicode("nglview-js-widgets").tag(sync(True))
1416+
_model_module_version = Unicode(__frontend_version__).tag(sync(True))
14571417

1458-
_is_fullscreen = Bool().tag(sync=True)
1418+
_is_fullscreen = Bool().tag(sync(True))
14591419

14601420
def __init__(self, target, views):
14611421
super().__init__()

nglview/widget_base.py

+97
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
import threading
2+
import base64
3+
import ipywidgets as widgets
4+
from traitlets import Bool, Dict, Integer, Unicode, observe
5+
from .remote_thread import RemoteCallThread
6+
7+
class WidgetBase(widgets.DOMWidget):
8+
frame = Integer().tag(sync=True)
9+
loaded = Bool(False).tag(sync=False)
10+
_component_ids = []
11+
_trajlist = []
12+
_callbacks_before_loaded = []
13+
_event = threading.Event()
14+
15+
def __init__(self, **kwargs):
16+
super().__init__(**kwargs)
17+
self._remote_call_thread = RemoteCallThread(self, registered_funcs=[])
18+
self._remote_call_thread.daemon = True
19+
self._remote_call_thread.start()
20+
self._handle_msg_thread = threading.Thread(target=self.on_msg, args=(self._handle_message,))
21+
self._handle_msg_thread.daemon = True
22+
self._handle_msg_thread.start()
23+
24+
def render_image(self):
25+
image = widgets.Image()
26+
self._js(f"this.exportImage('{image.model_id}')")
27+
return image
28+
29+
def handle_resize(self):
30+
self._js("this.plugin.handleResize()")
31+
32+
@observe('loaded')
33+
def on_loaded(self, change):
34+
if change['new']:
35+
self._fire_callbacks(self._callbacks_before_loaded)
36+
37+
def _thread_run(self, func, *args):
38+
thread = threading.Thread(target=func, args=args)
39+
thread.daemon = True
40+
thread.start()
41+
return thread
42+
43+
def _fire_callbacks(self, callbacks):
44+
def _call(event):
45+
for callback in callbacks:
46+
callback(self)
47+
self._thread_run(_call, self._event)
48+
49+
def _wait_until_finished(self, timeout=0.0001):
50+
pass
51+
52+
def _js(self, code, **kwargs):
53+
self._remote_call('executeCode', target='Widget', args=[code], **kwargs)
54+
55+
def _remote_call(self, method_name, target='Widget', args=None, kwargs=None, **other_kwargs):
56+
msg = self._get_remote_call_msg(method_name, target=target, args=args, kwargs=kwargs, **other_kwargs)
57+
def callback(widget, msg=msg):
58+
widget.send(msg)
59+
callback._method_name = method_name
60+
callback._msg = msg
61+
if self.loaded:
62+
self._remote_call_thread.q.append(callback)
63+
else:
64+
self._callbacks_before_loaded.append(callback)
65+
66+
def _get_remote_call_msg(self, method_name, target='Widget', args=None, kwargs=None, **other_kwargs):
67+
msg = {'target': target, 'type': 'call_method', 'methodName': method_name, 'args': args, 'kwargs': kwargs}
68+
msg.update(other_kwargs)
69+
return msg
70+
71+
def _update_max_frame(self):
72+
self.max_frame = max(int(traj.n_frames) for traj in self._trajlist if hasattr(traj, 'n_frames')) - 1
73+
74+
def _set_coordinates(self, index):
75+
if self._trajlist:
76+
coordinates_dict = {}
77+
for trajectory in self._trajlist:
78+
traj_index = self._component_ids.index(trajectory.id)
79+
try:
80+
coordinates_dict[traj_index] = trajectory.get_coordinates(index)
81+
except (IndexError, ValueError):
82+
coordinates_dict[traj_index] = np.empty((0), dtype='f4')
83+
self._send_coordinates(coordinates_dict)
84+
85+
def _send_coordinates(self, arr_dict):
86+
self._coordinates_dict = arr_dict
87+
buffers = []
88+
coords_indices = dict()
89+
for index, arr in self._coordinates_dict.items():
90+
buffers.append(arr.astype('f4').tobytes())
91+
coords_indices[index] = index
92+
msg = {'type': 'binary_single', 'data': coords_indices}
93+
self.send(msg, buffers=buffers)
94+
95+
@observe('frame')
96+
def _on_frame_changed(self, change):
97+
self._set_coordinates(self.frame)

nglview/widget_molstar.py

+19-103
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,13 @@
55
from traitlets import (Bool, Dict, Integer,
66
Unicode, observe)
77
from ._frontend import __frontend_version__
8+
from .widget_base import WidgetBase
89

910
from .remote_thread import RemoteCallThread
1011

1112

1213
@widgets.register
13-
class MolstarView(widgets.DOMWidget):
14+
class MolstarView(WidgetBase):
1415
# Name of the widget view class in front-end
1516
_view_name = Unicode('MolstarView').tag(sync=True)
1617

@@ -35,44 +36,31 @@ class MolstarView(widgets.DOMWidget):
3536
def __init__(self):
3637
super().__init__()
3738
self._molstar_component_ids = []
38-
self._trajlist = []
39-
self._callbacks_before_loaded = []
40-
self._event = threading.Event()
41-
self._remote_call_thread = RemoteCallThread(
42-
self,
43-
registered_funcs=[])
44-
self._remote_call_thread.daemon = True
45-
self._remote_call_thread.start()
46-
self._handle_msg_thread = threading.Thread(
47-
target=self.on_msg, args=(self._molstar_handle_message, ))
48-
# register to get data from JS side
49-
self._handle_msg_thread.daemon = True
50-
self._handle_msg_thread.start()
5139
self._state = None
5240

53-
def render_image(self):
54-
image = widgets.Image()
55-
self._js(f"this.exportImage('{image.model_id}')")
56-
# image.value will be updated in _molstar_handle_message
57-
return image
58-
59-
def handle_resize(self):
60-
self._js("this.plugin.handleResize()")
41+
def _molstar_handle_message(self, widget, msg, buffers):
42+
msg_type = msg.get("type")
43+
data = msg.get("data")
44+
if msg_type == "exportImage":
45+
image = widgets.Widget.widgets[msg.get("model_id")]
46+
image.value = base64.b64decode(data)
47+
elif msg_type == "state":
48+
self._state = data
49+
elif msg_type == 'request_loaded':
50+
if not self.loaded:
51+
# FIXME: doublecheck this
52+
# trick to trigger observe loaded
53+
# so two viewers can have the same representations
54+
self.loaded = False
55+
self.loaded = msg.get('data')
56+
elif msg_type == 'getCamera':
57+
self._molcamera = data
6158

6259
@observe('loaded')
6360
def on_loaded(self, change):
6461
if change['new']:
6562
self._fire_callbacks(self._callbacks_before_loaded)
6663

67-
def _thread_run(self, func, *args):
68-
thread = threading.Thread(
69-
target=func,
70-
args=args,
71-
)
72-
thread.daemon = True
73-
thread.start()
74-
return thread
75-
7664
def _fire_callbacks(self, callbacks):
7765
def _call(event):
7866
for callback in callbacks:
@@ -88,78 +76,6 @@ def _load_structure_data(self, data: str, format: str = 'pdb', preset="default")
8876
target="Widget",
8977
args=[data, format, preset])
9078

91-
def _molstar_handle_message(self, widget, msg, buffers):
92-
msg_type = msg.get("type")
93-
data = msg.get("data")
94-
if msg_type == "exportImage":
95-
image = widgets.Widget.widgets[msg.get("model_id")]
96-
image.value = base64.b64decode(data)
97-
elif msg_type == "state":
98-
self._state = data
99-
elif msg_type == 'request_loaded':
100-
if not self.loaded:
101-
# FIXME: doublecheck this
102-
# trick to trigger observe loaded
103-
# so two viewers can have the same representations
104-
self.loaded = False
105-
self.loaded = msg.get('data')
106-
elif msg_type == 'getCamera':
107-
self._molcamera = data
108-
109-
def render_image(self):
110-
image = widgets.Image()
111-
self._js(f"this.exportImage('{image.model_id}')")
112-
# image.value will be updated in _molview_handle_message
113-
return image
114-
115-
def _js(self, code, **kwargs):
116-
# nglview code
117-
self._remote_call('executeCode',
118-
target='Widget',
119-
args=[code],
120-
**kwargs)
121-
122-
def _remote_call(self,
123-
method_name,
124-
target='Widget',
125-
args=None,
126-
kwargs=None,
127-
**other_kwargs):
128-
129-
# adapted from nglview
130-
msg = self._get_remote_call_msg(method_name,
131-
target=target,
132-
args=args,
133-
kwargs=kwargs,
134-
**other_kwargs)
135-
def callback(widget, msg=msg):
136-
widget.send(msg)
137-
138-
callback._method_name = method_name
139-
callback._msg = msg
140-
141-
if self.loaded:
142-
self._remote_call_thread.q.append(callback)
143-
else:
144-
# send later
145-
# all callbacks will be called right after widget is loaded
146-
self._callbacks_before_loaded.append(callback)
147-
148-
def _get_remote_call_msg(self,
149-
method_name,
150-
target='Widget',
151-
args=None,
152-
kwargs=None,
153-
**other_kwargs):
154-
# adapted from nglview
155-
msg = {}
156-
msg['target'] = target
157-
msg['type'] = 'call_method'
158-
msg['methodName'] = method_name
159-
msg['args'] = args
160-
msg['kwargs'] = kwargs
161-
return msg
162-
16379
def add_trajectory(self, trajectory):
16480
self._load_structure_data(trajectory.get_structure_string(),
16581
'pdb') # FIXME

0 commit comments

Comments
 (0)