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()Styles
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
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,
)styleparam- A string, one of
ball_and_stick,cartoon,ribbon,spheres,sticksorsurface; or a callable returning a style node (see Callables below).
- A string, one of
selectionparam- 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.
- An MDAnalysis selection phrase, an
materialparam- 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 DefaultorMN Squishy.
- One of the pre-built materials from
colorparam"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 aTypeError.
- Any remaining keyword arguments are passed to the style node, e.g.
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.
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()Style Gallery
Because styling is identical across all molecules, we can loop over structures fetched from the PDB and apply a different style to each.
canvas.clear()
codes = ["4ozs", "8H1B", "6N2Y"]
styles = ["cartoon", "ribbon", "surface"]
for code, style in zip(codes, styles):
mol = mn.Molecule.fetch(code)
mol.add_style(style, material="MN Flat")
canvas.look_at(mol, viewpoint="right")
display(canvas.snapshot())
canvas.clear()Info: Deleted 3 data-block(s)
Info: Deleted 2 data-block(s)
Info: Deleted 2 data-block(s)
Info: Deleted 2 data-block(s)
Styles compose, so a single molecule can combine several representations with different selections and materials.
mol = (
mn.Molecule.fetch("8H1B")
.add_style("cartoon", material="MN Ambient Occlusion")
.add_style(
style="surface",
selection="protein",
material="MN Default",
)
)
canvas.look_at(mol)
canvas.snapshot()canvas.clear()
canvas.engine = mn.scene.Cycles(samples=32)
mol = (
mn.Molecule.fetch("9EYM")
.add_style("cartoon", selection="is_peptide", material="MN Ambient Occlusion")
.add_style("ball_and_stick", selection="not protein", material="MN Default", bond_find=True)
)
canvas.look_at(mol)
canvas.snapshot()Info: Deleted 2 data-block(s)













