Styles

Adding styles and representations to molecules

Styles (or representations) are how we turn the raw atomic data of a Molecule into something we can actually see. Every Molecule is styled the same way, whether it is a single static structure or a multi-frame trajectory.

Setup

import molecularnodes as mn
from molecularnodes.nodes import geometry as g
import MDAnalysis as mda
from MDAnalysis.tests.datafiles import PSF, DCD

u = mda.Universe(PSF, DCD)
canvas = mn.Canvas()

Add a Style

A style can be specified when adding the molecule to Blender. Styles are given as a string, with any style-specific options passed as keyword arguments to add_style.

mol = mn.Molecule(u).add_style("ribbon", quality=4, peptide_radius=0.5)
canvas.look_at(mol)
canvas.snapshot()

The add_style API takes the following arguments:

Molecule.add_style(
    style="spheres",
    selection=None,
    material="MN Default",
    color=None,
    **kwargs,
)
  • style param
    • A string, one of ball_and_stick, cartoon, ribbon, spheres, sticks or surface; or a callable returning a style node (see Callables below).
  • selection param
    • An MDAnalysis selection phrase, an AtomGroup, the name of an existing boolean attribute, or a callable returning a boolean socket. A selection phrase is stored as a named attribute and used to mask the style.
  • material param
    • One of the pre-built materials from mn.material, e.g. mn.material.AmbientOcclusion(distance=0.5), a Blender material, or a material name to append from the asset file, e.g. MN Default or MN Squishy.
  • color param
    • "common" / "default", "plddt", an RGBA tuple, the name of an existing colour attribute, or a callable returning a colour socket. Anything else warns rather than silently rendering black.
  • **kwargs
    • Any remaining keyword arguments are passed to the style node, e.g. geometry, quality, scale, peptide_radius. Names that are not inputs on that node raise a TypeError.

Selections

Each call to add_style appends another style branch to the node tree, so different selections of the molecule can be shown with different styles.

Add StyleSpheres to just residues 1 and 129. The string is used as a selection string to create an mda.AtomGroup which is used to create a boolean attribute, and that is used inside of the node tree for rendering.

mol.add_style("spheres", sphere="Instance", selection="resid 1 129")
canvas.look_at(mol)
canvas.snapshot()

mol.add_style("surface", selection="resid 100:150", material="MN Flat")
canvas.look_at(mol)
canvas.snapshot()

A selection can also be an mda.AtomGroup.

mol.add_style("ball_and_stick", quality=4, selection=u.select_atoms("resid 180:200"))
canvas.look_at(mol)
canvas.snapshot()

Callables for Full Control

style, selection and color all accept a callable, which is evaluated inside the node tree context. This reaches the full node API without having to write out a whole tree, and is the recommended middle ground between add_style and building the tree yourself.

A callable color reaches any of the Color* nodes:

canvas.clear()
mol = mn.Molecule.fetch("8H1B")
mol.add_style("cartoon", color=lambda: g.ColorSecondaryStructure())
canvas.look_at(mol)
canvas.snapshot()
Info: Deleted 2 data-block(s)

A callable selection composes the selection nodes with &, | and ~:

mol.add_style("sticks", selection=lambda: g.IsPeptide() & g.IsSideChain())
canvas.look_at(mol)
canvas.snapshot()

A callable style sets allows easier setting of the values of the node itself.

mol.add_style(
  lambda: g.StyleSpheres(
    sphere="Instance",
    quality=4,
    scale=0.4,
    material=mn.material.Default().material
    )
  )
canvas.look_at(mol)
canvas.snapshot()

A callable style defines the style node completely, so selection, material and style keyword arguments cannot be passed alongside it - set them inside the callable instead. To use an MDAnalysis selection phrase there, turn it into a node with node:

canvas.clear()
mol = mn.Molecule.fetch("9EYM")
mol.add_style("cartoon", color=lambda: g.ColorRainbow())
mol.add_style(
    lambda: g.StyleSpheres(
        selection=mol.selections.node("not protein"),
        sphere="Instance",
    )
)
canvas.look_at(mol)
canvas.snapshot()
Info: Deleted 3 data-block(s)

selections.node() reuses an existing selection where it can, so calling it repeatedly (inside a loop, or on every rebuild of a tree) does not pile up duplicate selections:

before = len(mol.selections)
with mol.tree:
    for _ in range(5):
        mol.selections.node("not protein")
print(f"{before} selection(s) before, {len(mol.selections)} after")
1 selection(s) before, 1 after

Building a Style Tree

To take full control — or to start from a clean slate — build the node tree yourself with the tree context manager. tree.reset() clears the existing tree and yields the input atoms and output join sockets, so you can compose exactly the styles you want with their specific parameters. This is the recommended way to create styling that is more complex than simple styles and their selections.

Attempting to expose the branching tree structure of Geometry Nodes via a Python API is a truly difficult task, and so instead we build the node tree using nodebpy as a specialised scripting interface for node trees.

mat = mn.material.AmbientOcclusion()

mol.dssp.init()
# `from_string` creates a managed selection; `.node()` gives a node reading its
# boolean attribute, ready to plug into a style node's `Selection` input
sel = mol.selections.from_string("resname LYS")

with mol.tree.reset() as (atoms, join):
  atoms >> g.StyleSticks(selection = sel.node(), scale=0.6, material=mat.material) >> join
  atoms >> g.SetColor(color=g.ColorRainbow()) >> g.StyleCartoon(material=mat.material) >> join

canvas.look_at(mol)
canvas.snapshot()