Skip to content

dkratzert/Fastmolwidget

Repository files navigation

Fastmolwidget

A PyQt/PySide6 widget to display crystal structures

Fastmolwidget is a lightweight, embeddable Qt widget that renders molecular and crystal structures in 2D projection with interactive mouse controls. It supports anisotropic displacement parameter (ADP) ellipsoids, ball-and-stick diagrams, and plain sphere representations — all drawn with a pure-Python QPainter backend (no OpenGL required).

Screenshot

Fastmolwidget showing an ORTEP-style crystal structure

View of a crystal structure from a CIF file with ADP ellipsoids. The control bar allows toggling display options interactively.

Features

  • ADP ellipsoids at the 50 % probability level, rendered from anisotropic displacement parameters
  • Ball-and-stick and isotropic sphere representations as fallbacks or when speed is more important than detail
  • Interactive mouse controls: rotate (left-drag), zoom (right-drag), pan (middle-drag), scroll wheel to resize labels
  • Atom and bond selection: single click or Ctrl+click for multi-selection; emits atomClicked / bondClicked Qt signals
  • Hydrogen visibility toggle
  • Atom label display toggle with adjustable font size
  • Bond width adjustment via spin box
  • Multiple file formats: CIF, SHELX .res/.ins, and plain XYZ. More to come...
  • EmbeddableMoleculeWidget is a plain QWidget subclass; drop it into any layout
  • Ready-to-use MoleculeViewerWidget bundles the renderer with a full control bar

Supported File Formats

Extension Format Notes
.cif Crystallographic Information File Reads atoms, unit cell, and ADPs
.res / .ins SHELXL instruction file Reads atoms and unit cell via shelxfile
.xyz Standard XYZ coordinate file Cartesian coordinates, no cell or ADPs

Installation

pip install fastmolwidget

By default, fastmolwidget installs without a concrete Qt binding. Install one binding explicitly via extras:

pip install "fastmolwidget[pyside6]"
pip install "fastmolwidget[pyqt6]"

Requirements: Python >= 3.12, NumPy, gemmi, shelxfile, qtpy, and either PySide6 or PyQt6.

Quick Start

Standalone viewer

from qtpy.QtWidgets import QApplication
from fastmolwidget import MoleculeViewerWidget

app = QApplication([])
viewer = MoleculeViewerWidget()
viewer.load_file("structure.cif")
viewer.show()
app.exec()

Embedding in your own layout

from fastmolwidget import MoleculeWidget, MoleculeLoader

mol = MoleculeWidget(parent=self)
loader = MoleculeLoader(mol)
# The loader recognizes the file format from the extension and populates `mol` accordingly
loader.load_file("structure.cif")

# drop `mol` into any QLayout
layout.addWidget(mol)

Loading a different file at runtime

viewer.load_file("new_structure.res")

Reacting to atom / bond clicks

mol.atomClicked.connect(lambda label: print(f"Clicked atom: {label}"))
mol.bondClicked.connect(lambda a, b: print(f"Clicked bond: {a}{b}"))

Mouse Controls

Action Effect
Left-drag Rotate the molecule
Right-drag Zoom in / out
Middle-drag Pan the view
Scroll wheel Increase / decrease label font size
Left-click Select a single atom or bond
Ctrl + Left-click Toggle multi-selection

Control Bar Options (MoleculeViewerWidget)

Control Default Description
Show ADP Toggle ORTEP ellipsoid / isotropic sphere rendering
Show Labels Toggle non-hydrogen atom labels
Round Bonds Switch between 3D-shaded and flat bond drawing
Show Hydrogens Show or hide hydrogen atoms and their bonds
Bond Width 3 Stroke width for bonds (1–15 px)

API Overview

MoleculeViewerWidget(parent=None)

A self-contained widget combining MoleculeWidget with the control bar described above.

  • load_file(path) — load a structure file (format auto-detected from extension)
  • render_widget — read-only property exposing the underlying MoleculeWidget

MoleculeWidget(parent=None)

The low-level renderer widget. It is a plain QWidget subclass that you can drop into any layout. Provide atom data directly via open_molecule() instead of loading a file through MoleculeLoader.

Qt Signals

Signal Signature Emitted when
atomClicked (label: str) The user clicks on an atom; label is the atom name (e.g. "C1")
bondClicked (label1: str, label2: str) The user clicks on a bond; both atom labels are passed

Data Methods

  • open_molecule(atoms, cell=None, adps=None, keep_view=False)
    Load a new set of atoms and reset (or optionally preserve) the view.

    • atoms — list of Atomtuple(label, type, x, y, z, part) in Cartesian coordinates (Å)
    • cell — optional (a, b, c, α, β, γ) tuple of unit-cell parameters (Å / °); required for ADP rendering
    • adps — optional dict mapping atom labels to (U11, U22, U33, U23, U13, U12) ADP tensors
    • keep_view — when True, the current zoom, pan, and rotation are preserved (useful for live updates)
  • grow_molecule(atoms, cell=None, adps=None)
    Replace the atom set while always preserving the current view.
    Equivalent to calling open_molecule(..., keep_view=True).

  • clear()
    Remove all atoms and bonds from the display.

Display Methods

  • show_adps(value: bool)
    Toggle ORTEP-style ADP ellipsoid rendering. When False, atoms are drawn as isotropic spheres.

  • show_labels(value: bool)
    Show or hide non-hydrogen atom labels.

  • show_hydrogens(value: bool)
    Show or hide hydrogen / deuterium atoms and their bonds.

  • show_round_bonds(value: bool)
    Switch between 3D-shaded cylinder-style bonds (True, default) and flat single-colour bonds (False).

  • set_bond_width(width: int)
    Set the stroke width for bonds in pixels (valid range: 1–15).

  • setLabelFont(font_size: int)
    Set the pixel size used for atom labels.

  • set_background_color(color: QColor)
    Change the widget background colour.

  • reset_view()
    Reset zoom, pan, and rotation to their defaults.

Example — feeding atom data directly

from fastmolwidget import MoleculeWidget
from fastmolwidget.sdm import Atomtuple

mol = MoleculeWidget(parent=self)

atoms = [
    Atomtuple(label="C1", type="C", x=0.0,  y=0.0,  z=0.0,  part=0),
    Atomtuple(label="O1", type="O", x=1.22, y=0.0,  z=0.0,  part=0),
    Atomtuple(label="H1", type="H", x=-0.5, y=0.94, z=0.0,  part=0),
]

# ADP tensors: {atom_label: (U11, U22, U33, U23, U13, U12)}
adps = {
    "C1": (0.02, 0.02, 0.02, 0.0, 0.0, 0.0),
    "O1": (0.03, 0.03, 0.03, 0.0, 0.0, 0.0),
}

cell = (5.0, 5.0, 5.0, 90.0, 90.0, 90.0)   # optional

mol.open_molecule(atoms=atoms, cell=cell, adps=adps)
mol.atomClicked.connect(lambda label: print(f"Selected: {label}"))

layout.addWidget(mol)

MoleculeLoader(widget)

Format-aware loader that populates a MoleculeWidget.

  • load_file(path, keep_view=False) — parse file and call open_molecule / grow_molecule

License

BSD 2-Clause License — see LICENSE for details.

© 2026 Daniel Kratzert

Maintainer Release Workflow

The release workflow is tag-driven and currently publishes to TestPyPI only.

  1. Ensure project.version in pyproject.toml is the version to publish.
  2. Create and push a matching tag in the format version-X.Y.Z.
  3. GitHub Actions builds sdist/wheel and uploads to TestPyPI.

Example:

git tag version-0.1.0
git push origin version-0.1.0

About

A PyQt widget to display crystal structures

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages