Source code for gofigr.backends.py3dmol

"""\
Copyright (c) 2023, Flagstaff Solutions, LLC
All rights reserved.

"""
import inspect
import os
import re
import sys
import tempfile

import py3Dmol
from html2image import Html2Image

from gofigr.backends import GoFigrBackend, get_all_function_arguments


[docs] class WatermarkedView(py3Dmol.view): """Overrides py3Dmol.view to include a GoFigr watermark""" def __init__(self, wrapped_obj, extra_html): super().__init__() self.wrapped_obj = wrapped_obj self.extra_html = extra_html def _make_html(self): # pylint: disable=protected-access return self.wrapped_obj._make_html() + self.extra_html
[docs] class Py3DmolBackend(GoFigrBackend): """Plotly backend for GoFigr""" def __init__(self, *args, debug=False, image_size=(1920, 1080), disable_logging=True, **kwargs): super().__init__(*args, **kwargs) self.image_size = image_size self.hti = Html2Image(size=image_size, disable_logging=disable_logging) self.debug = debug
[docs] def get_backend_name(self): return "py3dmol"
[docs] def is_compatible(self, fig): return isinstance(fig, py3Dmol.view)
[docs] def is_interactive(self, fig): return True
def _debug(self, text): if self.debug: print(text) def _infer_size(self, html): m = re.search(r'position: relative; width: (\d+)px; height: (\d+)px', html) if m is None: return self.image_size else: return int(m.group(1)), int(m.group(2))
[docs] def find_figures(self, shell, data): self._debug("Looking for py3dmol figures...") self._debug(f"Available MIME types: {data.keys()}") frames = inspect.stack() # Make sure there's an actual figure being published, as opposed to Plotly initialization scripts if not any('3dmoljs' in x for x in data.keys()): self._debug("Could not locate 3dmoljs MIME type. Stopping search early.") return else: self._debug("3dmoljs MIME found") # Walk through the stack in *reverse* order (from top to bottom), to find the first call # in case display() was called recursively for idx, f in enumerate(reversed(frames)): self._debug(f"Frame {idx + 1}: function={f.function} file={f.filename}") if ("show" in f.function or "repr_html" in f.function) and "py3dmol" in f.filename.lower(): self._debug("Found make_html call") for arg_idx, arg_value in enumerate(get_all_function_arguments(f)): self._debug(f"Inspecting argument {arg_idx + 1}: " f"{arg_value.__class__ if not arg_value is None else None}") if self.is_compatible(arg_value): self._debug("Argument is compatible with GoFigr") yield arg_value else: self._debug("Argument not compatible.") break
# pylint: disable=useless-return
[docs] def get_default_figure(self, silent=False): if not silent: print("py3Dmol does not have a default figure. Please specify a figure to publish.", file=sys.stderr) return None
[docs] def get_title(self, fig): title = getattr(fig, "title", None) if not isinstance(title, str): return None else: return title
[docs] def figure_to_bytes(self, fig, fmt, params): if fmt == "png": with tempfile.NamedTemporaryFile(suffix=".png", dir=self.hti.output_path) as ntf: fig_html = self.figure_to_html(fig) width, height = self._infer_size(fig_html) self.hti.screenshot(html_str=fig_html, save_as=os.path.basename(ntf.name), size=(width, height)) with open(ntf.name, 'rb') as f: return f.read() raise ValueError(f"Py3Dmol does not support {fmt.upper()}")
[docs] def figure_to_html(self, fig): return fig._make_html() # pylint: disable=protected-access
[docs] def add_interactive_watermark(self, fig, rev, watermark): watermark_html = f"<div><a href='{rev.revision_url}'>{rev.revision_url}</a></div>" return WatermarkedView(fig, watermark_html)
[docs] def get_supported_image_formats(self): return ["png"]
[docs] def close(self, fig): pass