Python API Guide

xyzrender has a full Python API. All CLI flags are available as keyword arguments. Results display inline in Jupyter automatically.

See also the auto-generated API reference for docstrings and type signatures, and the runnable examples/examples.ipynb notebook.

Loading molecules

load() parses a file and returns a Molecule object. Pass it to render() to avoid re-reading the file on every call. You can also pass a path string directly to render() as a shorthand.

from xyzrender import load, render

mol = load("caffeine.xyz")
render(mol)                          # displays inline in Jupyter
render(mol, output="caffeine.svg")   # save as SVG
render(mol, output="caffeine.png")   # save as PNG

# Short-form: pass a path directly (re-parses each time)
render("caffeine.xyz")

Loading options

Use load() keyword arguments for non-default loading behaviour:

mol = load("ts.out", ts_detect=True)            # detect TS bonds via graphRC
mol = load("mol.xyz", nci_detect=True)          # detect NCI interactions
mol = load("mol.sdf", mol_frame=2, kekule=True) # SDF frame + Kekule bonds
mol = load("CC(=O)O", smiles=True)              # SMILES → 3D (requires rdkit)
mol = load("POSCAR")                            # VASP/QE/SIESTA/ABINIT auto-detected
mol = load("caffeine_cell.xyz", cell=True)      # extXYZ Lattice= header
mol = load("mol.xyz", quick=True)               # skip BO detection (faster, use with bo=False)

Render options

All CLI flags are available as keyword arguments to render():

Styling

config= accepts a built-in preset name, a path to your own JSON file, or a pre-built RenderConfig from build_config().

render(mol, config="flat")                                     # built-in preset
render(mol, config="paton", transparent=True)                  # preset + transparent bg
render(mol, config="./my_style.json")                          # custom JSON file
render(mol, config="./my_style.json", hy=True)                 # kwargs override file values
render(mol, bond_width=8, atom_scale=1.5, background="#f0f0f0") # individual overrides (no preset)
render(mol, hide_bonds=True)                                   # hide all bonds

See Building a custom preset for the JSON schema and regions layering.

Bond display rules

Selectively hide or add bonds using element categories, element symbols, pi-coordination, or atom indices. Specs are 1-indexed.

render(mol, unbond=["M-L"])                   # hide all metal-ligand bonds
render(mol, unbond=["sbm"])                   # hide all s-block metal bonds (Li, Na, K, etc.)
render(mol, unbond=["M-het"])                 # hide metal-heteroatom (not C, not H)
render(mol, unbond=["M-pi"])                  # hide metal pi-coordination (ferrocene-like)
render(mol, unbond=["pi"])                    # hide all pi-coordination bonds 
render(mol, unbond=["Fe-C"])                  # hide iron-carbon bonds specifically
render(mol, unbond=["Li"])                    # hide all bonds from lithium atoms
render(mol, unbond=["2"])                     # hide all bonds from atom 2
render(mol, unbond=["1-3"])                   # hide bond between atoms 1 and 3
render(mol, unbond=["M-L"], bond=["2-5"])     # hide M-L but keep bond 2-5
render(mol, unbond=["sbm", "M-pi", "1-3"])    # multiple specs

Available categories:

  • M — all metals

  • sbm — s-block metals (Li, Na, K, Rb, Cs + Be, Mg, Ca, Sr, Ba)

  • L — non-metals (or, when the graph has metals, atoms bonded to a metal)

  • het — heteroatoms (not C, not H, not metal)

  • pi — eta-coordination to aromatic rings

  • hal — halogens (F, Cl, Br, I, At)

  • pnic — pnictogens (N, P, As, Sb, Bi)

  • chal — chalcogens (O, S, Se, Te, Po)

  • noble — noble gases (He, Ne, Ar, Kr, Xe, Rn)

  • triel — group 13 (B, Al, Ga, In, Tl)

  • tetrel — group 14 (C, Si, Ge, Sn, Pb)

Any element symbol also works (Fe, Li, O, etc.). Tokens are case-insensitive (halHAL). Combine with commas ("hal,chal"). NCI and TS overlay edges are never removed by rules.

Haptic centroid bonds

Replace the fan of individual metal-to-ring bonds with a single dotted bond from the metal to the ring centroid:

render(mol, haptic=True)

Hydrogen visibility

Atom indices are 1-indexed.

ethanol = load("ethanol.xyz")
render(ethanol, hy=True)            # show all H
render(ethanol, no_hy=True)         # hide all H
render(ethanol, hy=[7, 8, 9])       # show specific H atoms

Atom filtering

Use only= or exclude= to remove atoms from the render-time graph before orientation, canvas fitting, bond rules, annotations, hulls, and overlays are resolved. Selectors use the same grammar as highlight and remain based on the original input atom numbering after filtering.

salt = load("salt.xyz")
render(salt, only="1-24")        # render one component of a salt
render(salt, exclude="Na,Cl")    # remove counterions
render(salt, exclude=["25-40"])  # repeatable/list form

Incident bonds are removed with excluded atoms. Cube/surface fields (mo, dens, esp, nci) are not cropped by atom filtering.

Overlays

render(mol, vdw=True)               # vdW spheres on all atoms
render(mol, vdw=[1, 3, 5])          # vdW spheres on specific atoms
render(mol, ts_bonds=[(1, 6)])      # manual TS bond (1-indexed)
render(mol, ts_color="dodgerblue")  # color for dashed TS bonds
render(mol, nci_bonds=[(2, 8)])     # manual NCI bond (1-indexed)
render(mol, nci_color="teal")       # color for dotted NCI bonds
render(mol, nci_element=True)       # split-half atom colour on NCI/haptic dots (off by default; on in pmol/btube/tube/mtube)
render(mol, ts_element=True)        # same, for TS dashes (off by default; needs a by_element preset)
render(mol, ts_dash="2.0,1.5")      # (length, gap) as bond_width multiples (default 1.2,2.2). Tuple also OK.
render(mol, ts_width=0.5)           # TS line width as a bond_width multiple (default 1.2)
render(mol, nci_dash=(0.2, 1.5))    # NCI/haptic (length, gap) multipliers (default 0.08,2.0)
render(mol, nci_width=0.8)          # NCI/haptic line width as a bond_width multiple (default 1.0)
render(mol, idx=True)               # atom index labels ("C1", "N3", ...)
render(mol, idx="n")                # index only ("1", "3", ...)
render(mol, mol_color="gray")                            # flat color for all atoms + bonds
render(mol, highlight="1-3,7")                           # highlight atoms 1-3 and 7 (orchid)
render(mol, highlight="C,N")                             # element symbols / categories ("M", "het", ...)
render(mol, highlight=[1, 2, 3, 7])                      # 1-indexed list
render(mol, highlight=[("1-5", "blue"), ("10-15", "red")])  # multi-group with colors
render(mol, highlight=["1-5", "10-15"])                  # multi-group, auto-colors from palette
render(mol, mol_color="gray", highlight="1-5")           # gray base + orchid highlight on top
render(mol, dof=True)                                   # depth-of-field blur
render(mol, dof=True, dof_strength=6.0)                 # stronger blur

Structural overlay

mol1 = load("isothio_xtb.xyz", charge=1)
mol2 = load("isothio_uma.xyz", charge=1)
render(mol1, overlay=mol2)                         # overlay mol2 onto mol1
render(mol1, overlay=mol2, overlay_color="green")  # custom overlay color
render(mol1, overlay=mol2, align_atoms=[1, 2, 3])  # explicit 1-indexed subset
render(mol1, overlay=mol2, align_atoms="M,L")      # metal + coord shell (organometallic)
render_gif(mol1, overlay=mol2, gif_rot="y")        # spinning overlay GIF

align_atoms accepts a 1-indexed atom list/string or a symbol/category spec ("M,L", "Fe,P", …). Symbol specs are resolved per-graph so atom ordering doesn’t need to match between the two structures. Metal-containing specs run metal-fragment overlay so paired metals coincide exactly; non-metal specs use MCS + K-subset Kabsch and pick the lowest-RMSD candidate.

See Structural Overlay and Conformer Ensemble for more.

Annotations

render(mol, labels=["1 2 d", "1 2 3 a"])   # inline spec strings
render(mol, label_file="annot.txt")         # bulk annotation file

See Annotations for the full spec syntax.

Atom property colormap

cmap accepts a {1-indexed atom: value} dict or a path to a two-column file. Atoms absent from the mapping are drawn white.

render(mol, cmap={1: 0.5, 2: -0.3}, cmap_range=(-1.0, 1.0))
render(mol, cmap="charges.txt", cmap_symm=True)   # symmetric range about 0

See Atom Property Colormap for details on file format, palettes, and the colorbar.

Surfaces (cube files)

mol_cube = load("caffeine_homo.cube")
render(mol_cube, mo=True)                                          # MO lobes
render(mol_cube, mo=True, iso=0.03, mo_pos_color="maroon", mo_neg_color="teal")

dens_cube = load("caffeine_dens.cube")
render(dens_cube, dens=True)                       # density isosurface
render(dens_cube, esp="caffeine_esp.cube")         # ESP mapped onto density
render(dens_cube, nci="caffeine_grad.cube")        # NCI surface
render(load("phenol_di-sl2r.cub"), nci="phenol_di-dg_inter.cub", iso=0.005)  # high_field surface

See Molecular Orbitals, Electron Density and ESP, and NCI Surface.

Convex hull

render(mol, hull=[1, 2, 3, 4, 5, 6],
       hull_color="steelblue", hull_opacity=0.35)
render(mol, hull="rings", hull_color="teal")       # auto-detect aromatic rings

See Convex hull, faces & pores for multi-subset hulls and all options.

Reusing a style config

For a single render, pass config= to render() directly — render(mol, config="paton") and render(mol, config="./my_style.json") both work without any helper. Use build_config() only when you want to build the styling once and reuse it across many renders:

from xyzrender import build_config, render, render_gif

cfg = build_config("flat", atom_scale=1.5, gradient=False)        # built-in + tweaks
cfg = build_config("./my_style.json")                              # custom JSON file
cfg = build_config("./my_style.json", bond_color="steelblue")     # custom file + tweaks

render(mol1, config=cfg)
render(mol2, config=cfg, ts_bonds=[(1, 6)])     # per-render overlay on shared style
render_gif("mol.xyz", gif_rot="y", config=cfg)

For fields not exposed as build_config() kwargs (vdw_interlocking, mo_outline_width, surface_style, skeletal_label_color, the overlay block, …), either set them in your JSON file or mutate the RenderConfig directly afterwards: cfg.surface_style = "mesh". Resolution order is always default.json < preset/your JSON < build_config() kwargs < render(...) kwargs.

Geometry measurements

measure() returns bonded distances, angles, and dihedrals as a dict. It does not render anything. Atom indices in the output are 0-indexed.

from xyzrender import measure

data = measure(mol)                    # all measurements
data = measure("mol.xyz")             # also accepts a path
data = measure(mol, modes=["d", "a"]) # distances and angles only

for i, j, d in data["distances"]:
    print(f"  {i+1}-{j+1}: {d:.3f} Å")

Saving geometry

Molecule.to_xyz() writes the structure to an XYZ file. Ghost atoms are excluded. If the molecule has cell_data (loaded with cell=True or crystal=...), the output is extXYZ with a Lattice= header so it can be reloaded directly.

mol = load("CC(=O)O", smiles=True)
mol.to_xyz("acetic_acid.xyz")                            # plain XYZ
mol.to_xyz("acetic_acid.xyz", title="acetic acid")       # with comment line

mol_cell = load("caffeine_cell.xyz", cell=True)
mol_cell.to_xyz("out.xyz")                               # extXYZ with Lattice= header

Interactive orientation

orient() opens the 3D viewer (v) so you can rotate a molecule manually, then locks the orientation for subsequent render() calls:

from xyzrender import orient

mol = load("caffeine.xyz")
orient(mol)        # opens viewer — rotate, close to confirm
render(mol)        # renders in the manually chosen orientation

Requires pip install xyzrender[v] (Linux only).

Orientation reference

Use ref= to save or load a reference orientation for consistent batch rendering:

mol1 = load("homo.cube")
render(mol1, mo=True, ref="reference.xyz")   # first call saves oriented positions

mol2 = load("lumo.cube")
render(mol2, mo=True, ref="reference.xyz")   # subsequent calls Kabsch-align to the reference

When the reference file exists, orient=True is ignored — the reference is the orientation.

GIF animations

from xyzrender import render_gif

render_gif("caffeine.xyz", gif_rot="y")           # rotation GIF
render_gif("ts.out", gif_ts=True)                  # TS vibration GIF
render_gif("traj.xyz", gif_trj=True)               # trajectory GIF
render_gif("mep.xyz", gif_trj=True, trj_bonds=True)  # re-detect bonds per frame
render_gif("mol.xyz", gif_rot="y", config=cfg)     # with shared style config

# Diffuse / assembly GIF
render_gif("caffeine.xyz", gif_diffuse=True)
render_gif("caffeine.xyz", gif_diffuse=True, diffuse_noise=0.5, diffuse_bonds="hide")
render_gif("caffeine.xyz", gif_diffuse=True, gif_rot="y", diffuse_rot=90)
render_gif("caffeine.xyz", gif_diffuse=True, anchor="1-5,8")

# Surface in rotation GIF (cube file)
mol_cube = load("caffeine_homo.cube")
render_gif(mol_cube, gif_rot="y", mo=True, output="homo_rot.gif")

Return types

SVGResult

render() returns an SVGResult object. In Jupyter it displays inline automatically.

result = render(mol)
str(result)              # raw SVG string
result.save("out.svg")   # write to file

GIFResult

render_gif() returns a GIFResult object. In Jupyter it displays inline automatically.

gif = render_gif("mol.xyz", gif_rot="y")
gif.path                 # pathlib.Path to the GIF on disk
bytes(gif)               # raw GIF bytes
gif.save("copy.gif")     # copy to another path