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 metalssbm— 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 ringshal— 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 (hal ≡ HAL). 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