diff --git a/README.md b/README.md index 6b40da2f9..37aa1a5ff 100644 --- a/README.md +++ b/README.md @@ -28,14 +28,26 @@ example notebook. from zndraw import ZnDraw import ase -zndraw = ZnDraw() +vis = ZnDraw() -zndraw.socket.sleep(2) # give it some time to fully connect -zndraw[0] = ase.Atoms( +vis.socket.sleep(2) # give it some time to fully connect +vis[0] = ase.Atoms( "H2O", positions=[[0.75, -0.75, 0], [0.75, 0.75, 0], [0, 0, 0]] ) ``` +ZnDraw also provides an interface to the Python +[logging](https://docs.python.org/3/library/logging.html) library, including +support for formatters and different logging levels. + +```python +import logging + +log = logging.getLogger(__name__) +log.addHandler(vis.get_logging_handler()) +log.critical("Critical Message") +``` + ## User Interface ![ZnDraw UI](https://raw.githubusercontent.com/zincware/ZnDraw/main/misc/zndraw_ui.png "ZnDraw UI") diff --git a/zndraw/app.py b/zndraw/app.py index 55b8d53c7..3f962cb43 100644 --- a/zndraw/app.py +++ b/zndraw/app.py @@ -348,3 +348,8 @@ def delete_atoms(data): @io.on("atoms:insert") def insert_atoms(data): emit("atoms:insert", data, broadcast=True, include_self=False) + + +@io.on("message:log") +def log(data): + emit("message:log", data, broadcast=True, include_self=False) diff --git a/zndraw/static/main.js b/zndraw/static/main.js index 76dc33eb1..e5d7eb47f 100644 --- a/zndraw/static/main.js +++ b/zndraw/static/main.js @@ -41,6 +41,10 @@ function main() { socket.emit("atoms:request", window.location.href, () => { displayIncomingAtoms(); }); + + socket.on("message:log", (msg) => { + console.log(msg); + }); } main(); diff --git a/zndraw/zndraw.py b/zndraw/zndraw.py index 2d92942db..4685abda9 100644 --- a/zndraw/zndraw.py +++ b/zndraw/zndraw.py @@ -1,6 +1,7 @@ import collections.abc import contextlib import dataclasses +import logging import pathlib import threading import time @@ -19,6 +20,23 @@ from zndraw.utils import get_port +class ZnDrawLoggingHandler(logging.Handler): + """Logging handler which emits log messages to the ZnDraw server.""" + + def __init__(self, socket): + super().__init__() + self.socket = socket + + def emit(self, record): + try: + msg = self.format(record) + self.socket.emit("message:log", msg) + except RecursionError: # See StreamHandler + raise + except Exception: + self.handleError(record) + + @dataclasses.dataclass class ZnDraw(collections.abc.MutableSequence): url: str = None @@ -187,6 +205,13 @@ def extend(self, values: list[ase.Atoms]) -> None: if self.display_new: self.display(len(self) - 1) + def log(self, message: str) -> None: + """Log a message to the console""" + self.socket.emit("message:log", message) + + def get_logging_handler(self) -> ZnDrawLoggingHandler: + return ZnDrawLoggingHandler(self.socket) + def read(self, filename: str, start: int, stop: int, step: int): """Read atoms from file and return a list of atoms dicts.