Skip to content

Commit 20e4652

Browse files
authored
Merge pull request #93 from ManifoldFR/feature/capture_image
New feature: capture image
2 parents 3460c7f + decd6ae commit 20e4652

File tree

6 files changed

+48
-3
lines changed

6 files changed

+48
-3
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,4 @@ dist
99

1010
venv*
1111
.pytest_cache
12+
.vscode

setup.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
"tornado >= 4.0.0",
2525
"pyzmq >= 17.0.0",
2626
"pyngrok >= 4.1.6",
27+
"pillow >= 7.0.0"
2728
],
2829
zip_safe=False,
2930
include_package_data=True

src/meshcat/commands.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,13 @@ def lower(self):
4444
u"matrix": list(self.matrix.T.flatten())
4545
}
4646

47+
class CaptureImage:
48+
49+
def lower(self):
50+
return {
51+
u"type": u"capture_image"
52+
}
53+
4754

4855
class Delete:
4956
__slots__ = ["path"]

src/meshcat/servers/zmqserver.py

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import sys
88
import subprocess
99
import multiprocessing
10+
import json
1011

1112
import tornado.web
1213
import tornado.ioloop
@@ -128,7 +129,13 @@ def open(self):
128129
self.bridge.send_scene(self)
129130

130131
def on_message(self, message):
131-
pass
132+
try:
133+
message = json.loads(message)
134+
self.bridge.send_image(message['data'])
135+
return
136+
except Exception as err:
137+
print(err)
138+
raise
132139

133140
def on_close(self):
134141
self.bridge.websocket_pool.remove(self)
@@ -240,12 +247,23 @@ def wait_for_websockets(self):
240247
else:
241248
self.ioloop.call_later(0.1, self.wait_for_websockets)
242249

250+
def send_image(self, data):
251+
import base64
252+
mime, img_code = data.split(",", 1)
253+
img_bytes = base64.b64decode(img_code)
254+
self.zmq_stream.send(img_bytes)
255+
243256
def handle_zmq(self, frames):
244257
cmd = frames[0].decode("utf-8")
245258
if cmd == "url":
246259
self.zmq_socket.send(self.web_url.encode("utf-8"))
247260
elif cmd == "wait":
248261
self.ioloop.add_callback(self.wait_for_websockets)
262+
elif cmd == "capture_image":
263+
if len(self.websocket_pool) > 0:
264+
self.forward_to_websockets(frames) # on_message callback should handle the pb
265+
else:
266+
self.ioloop.call_later(0.3, lambda: self.handle_zmq(frames))
249267
elif cmd in MESHCAT_COMMANDS:
250268
if len(frames) != 3:
251269
self.zmq_socket.send(b"error: expected 3 frames")

src/meshcat/viewer

src/meshcat/visualizer.py

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,13 @@
22
import umsgpack
33
import numpy as np
44
import zmq
5+
import io
6+
from PIL import Image
57
from IPython.display import HTML
68

9+
710
from .path import Path
8-
from .commands import SetObject, SetTransform, Delete, SetProperty, SetAnimation
11+
from .commands import SetObject, SetTransform, Delete, SetProperty, SetAnimation, CaptureImage
912
from .geometry import MeshPhongMaterial
1013
from .servers.zmqserver import start_zmq_server_as_subprocess
1114

@@ -65,6 +68,17 @@ def get_scene(self):
6568
# we receive the HTML as utf-8-encoded, so decode here
6669
return self.zmq_socket.recv().decode('utf-8')
6770

71+
def get_image(self):
72+
cmd_data = CaptureImage().lower()
73+
self.zmq_socket.send_multipart([
74+
cmd_data["type"].encode("utf-8"),
75+
"".encode("utf-8"),
76+
umsgpack.packb(cmd_data)
77+
])
78+
img_bytes = self.zmq_socket.recv()
79+
img = Image.open(io.BytesIO(img_bytes))
80+
return img
81+
6882

6983
def srcdoc_escape(x):
7084
return x.replace("&", "&").replace('"', """)
@@ -143,6 +157,10 @@ def set_property(self, key, value):
143157
def set_animation(self, animation, play=True, repetitions=1):
144158
return self.window.send(SetAnimation(animation, play=play, repetitions=repetitions))
145159

160+
def get_image(self):
161+
"""Save an image"""
162+
return self.window.get_image()
163+
146164
def delete(self):
147165
return self.window.send(Delete(self.path))
148166

0 commit comments

Comments
 (0)