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

try:
    import py3Dmol
except ImportError:
    py3Dmol = None

try:
    from html2image import Html2Image
except ImportError:
    Html2Image = None

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