
Pymol
- 1.2k installs
- 2.6k repo stars
- Updated July 7, 2026
- google-deepmind/science-skills
pymol is a Google DeepMind agent skill that runs headless PyMOL scripts to render molecular structures and export PNG images for developers and researchers who need automated protein visualization without a GPU or GUI di
About
pymol is an agent skill providing PyMOL quick-reference patterns for headless molecular visualization. Every script must start with a mandatory initialization sequence—import pymol, set pymol_argv to ["pymol", "-cq"] for command-line quiet mode, then call pymol.finish_launching() before importing cmd; reversing this order crashes the session. PyMOL runs with OSMesa software rendering—no GPU or X display available. Use cmd.png(path, width, height, dpi) for standard output; cmd.ray() works for ray-traced quality but is slow. cmd.draw() must never be used because it requires hardware OpenGL. Researchers and computational biologists reach for pymol when agents need to programmatically render PDB structures, generate publication figures, or batch-export molecular images in CI or headless server environments.
- Mandatory init sequence: import pymol, set pymol_argv to ["pymol", "-cq"], finish_launching(), then from pymol import cm
- OSMesa software rendering — no GPU or X display; prefer cmd.png over cmd.draw or hardware OpenGL
- Selection cheatsheet for chain, resi ranges/lists, resn, name CA, and ss h/s/l
- Ray tracing via cmd.ray when quality matters; cmd.set ray_opaque_background for white backgrounds
Pymol by the numbers
- 1,166 all-time installs (skills.sh)
- +55 installs in the week ending Aug 4, 2026 (Skillselion tracking)
- Ranked #263 of 2,064 Data Science & ML skills by installs in the Skillselion catalog
- Security screen: LOW risk (skills.sh audit)
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/google-deepmind/science-skills --skill pymolAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 1.2k |
|---|---|
| repo stars | ★ 2.6k |
| Security audit | 3 / 3 scanners passed |
| Last updated | July 7, 2026 |
| Repository | google-deepmind/science-skills ↗ |
How do you render molecules headlessly with PyMOL?
Run headless PyMOL scripts from an agent to render molecular structures and export PNGs without a GPU or GUI.
Who is it for?
Computational biologists and researchers who need automated PyMOL molecular rendering on headless servers without GPU or X display.
Skip if: Interactive molecular modeling sessions requiring GUI manipulation or GPU-accelerated real-time OpenGL rendering with cmd.draw().
When should I use this skill?
An agent must render PDB structures, export molecular PNG figures, or run PyMOL scripts in headless CI or server environments.
What you get
PNG molecular structure images exported via cmd.png or cmd.ray from headless PyMOL scripts.
- PNG molecular renders
- Headless PyMOL scripts
Files
PyMOL
Prerequisites
1. `uv`: Read the uv skill and follow its Setup instructions to ensure uv is installed and on PATH. 2. User Notification: If LICENSE_NOTIFICATION.txt does not already exist in this skill directory then (1) prominently notify the user to check the license at https://www.pymol.org/, then (2) create the file recording the notification text and timestamp.
Overview
All PyMOL commands run on the host via uv with OSMesa software rendering — no GPU, display, or X server is needed. Structure files must be downloaded to the host before running PyMOL.
Do NOT use when:
- The user wants to run AlphaFold predictions.
- The user wants docking or molecular dynamics simulations.
- The user only has a sequence and no structure file — fetch the structure
first. Check if any other installed skills can retrieve structures from the PDB or AlphaFold Database before proceeding.
Setup (Agent Instructions)
Ensure that uv is installed on the host system. The PyMOL scripts use PEP 0723 headers to declare their dependencies, and uv run will automatically handle installing them (including pymol-open-source-whl) when the script is executed.
Core Rules
- Output paths must be absolute or relative to the user's project root.
Always run PyMOL scripts from the user's project directory.
- Software rendering only. Use
cmd.png()for output. Never use
cmd.draw() or cmd.ray() with hardware acceleration — OSMesa does not support it. Set environment variable PYOPENGL_PLATFORM=osmesa for headless rendering.
- Always save a `.pse` session file alongside any PNG output. This lets
the user open the session in their local PyMOL for further inspection.
- Always call `cmd.quit()` at the end of every PyMOL script. Omitting it
causes the process to stop responding.
- Init boilerplate is mandatory. Every PyMOL script must begin with the
initialization sequence. from pymol import cmd must come after finish_launching(), not before.
- See references/PYMOL_REFERENCE.md for
selection syntax, common commands, and gotchas.
- Pre-Flight File Check: Before writing the PyMOL script or running it,
you MUST verify that the requested structure file actually exists on the host machine.
- Verify Structure Load: After loading a structure with
cmd.load(),
always verify it succeeded by checking cmd.count_atoms("all"). If the result is 0, print an error to stdout and call cmd.quit() immediately.
- Notification: If this skill is used, ensure this is mentioned in the
output.
Quick Start
- Ensure structure files are downloaded to a directory in the user's project.
- Write a PyMOL Python script (e.g.,
render.py) with the required init
boilerplate and PEP 0723 header.
- Run it via
uv run:bash uv run render.py
Minimal example script (render.py)
# /// script
# requires-python = ">=3.10, <3.13"
# dependencies = [
# "pymol-open-source-whl",
# ]
# ///
import os
import sys
# Set environment variable for headless rendering
os.environ["PYOPENGL_PLATFORM"] = "osmesa"
import pymol # pytype: disable=import-error
pymol.pymol_argv = ["pymol", "-cq"]
pymol.finish_launching()
from pymol import cmd # pytype: disable=import-error
cmd.load("AF-P00520-F1-model_v4.cif", "structure")
cmd.show("cartoon")
cmd.color("green", "ss h")
cmd.color("yellow", "ss s")
cmd.color("gray", "ss l+''")
cmd.orient()
cmd.set("ray_opaque_background", 1)
cmd.png("output/render.png", width=1200, height=900, dpi=150)
cmd.save("output/session.pse")
cmd.quit()Common Recipes
See references/RECIPES.md for complete, copy-paste ready recipes. Available recipes:
- Cartoon with secondary structure coloring — basic helix/sheet/loop
coloring
- B-factor (pLDDT) coloring — continuous spectrum coloring by B-factor
- AlphaFold pLDDT coloring — canonical threshold-based confidence colors
- Highlight specific residues — show active site or key residues as sticks
- Surface rendering — transparent surface over cartoon
- Electrostatic surface rendering — vacuum electrostatics (qualitative)
- Multi-chain complex colors — automatic per-chain coloring
- B-factor putty analysis — tube width proportional to flexibility
- Cavity and pocket visualization — surface cavity detection with ligand
focus
- Multi-structure batch rendering — render a directory of structures
- Measure distance between residues — CA–CA distance with labels
- Zoom into binding pocket — simple pocket focus
- Protein-ligand interaction — ligand isolation, styled rendering, polar
contacts
- Two-structure superposition with RMSD — align/cealign with auto-fallback
- In silico mutagenesis — mutate residues with the mutagenesis wizard
- Load and modify an existing session — re-open a
.psefile
Interpreting Output
- The
output/directory contains PNG images and a.psesession file. - Any measurements or metrics (distances, RMSD, atom counts) are printed to
stdout by the PyMOL script. Report these values to the user.
- Present PNG images to the user and describe the visualization.
- Tell the user they can open the
.psefile in their local PyMOL to further
explore, rotate, or modify the visualization.
- If the user wants modifications, load the saved
.psein a new script and
re-run.
- Large sessions with surfaces can exceed the
--max_output_mblimit (default
500 MB). Increase it with --max_output_mb=1000 if needed.
PyMOL Quick Reference
Mandatory Initialization Boilerplate
Every PyMOL script must start with this exact sequence. The order matters — reversing the import and finish_launching() will crash.
import pymol
pymol.pymol_argv = ["pymol", "-cq"]
pymol.finish_launching()
from pymol import cmd-
-c= command-line mode (no GUI) -
-q= quiet (suppress startup messages)
Rendering Backend
PyMOL runs with OSMesa (software rendering). There is no GPU or X display.
- Use
cmd.png(path, width, height, dpi)for output. -
cmd.ray()works but is slow — use it only when you need ray-traced
quality.
- Never use
cmd.draw()(requires hardware OpenGL). - Always set
cmd.set("ray_opaque_background", 1)if you want a white
background instead of transparent.
Selection Syntax
Identifiers
| Selector | Example | Selects |
|---|---|---|
chain | chain A | All atoms in chain A |
resi | resi 100 | Residue number 100 |
resi (range) | resi 100-200 | Residues 100 through 200 |
resi (list) | resi 100+102+150 | Specific residues |
resn | resn ALA | All alanine residues |
name | name CA | All C-alpha atoms |
ss | ss h | Helices (h), sheets (s), loops (l) |
Structure types
| Selector | Selects |
|---|---|
polymer.protein | All protein atoms |
organic | Organic ligands (non-polymer, non-solvent) |
solvent | Water molecules |
hetatm | Heteroatoms (ligands, ions, water) |
all | Everything |
Logical operators
| Operator | Example | Meaning |
|---|---|---|
and | chain A and resi 100 | Intersection |
or | resn ALA or resn GLY | Union |
not | not solvent | Negation |
() | chain A and (not resi 100-110) | Grouping |
Proximity selectors
| Selector | Example |
|---|---|
within X of (selection) | polymer.protein within 4 of organic |
byres (selection) | byres (polymer.protein within 4 of organic) |
around X | resi 100 around 5 |
Named selections
cmd.select("binding_site", "byres (polymer.protein within 4 of organic)")
cmd.select("alpha_carbons", "name CA and polymer.protein")File Paths
- When running with
uv run, files are accessed directly from the host
filesystem.
- Paths are relative to the directory where you run the command, or you can
use absolute paths.
- Ensure output directories exist before trying to write to them.
Common Commands
Loading structures
cmd.load("data/structure.cif", "myprotein")
cmd.load("data/structure.pdb", "myprotein")Display modes
cmd.show("cartoon", "polymer.protein")
cmd.show("sticks", "resi 100-110")
cmd.show("surface", "polymer.protein")
cmd.show("spheres", "resi 50")
cmd.hide("everything", "solvent")Coloring
cmd.color("green", "ss h")
cmd.color("cyan", "chain A")
cmd.spectrum("b", "red_white_blue", "polymer.protein")
cmd.spectrum("count", "rainbow", "polymer.protein")Structural operations
cmd.align("mobile", "target")
cmd.super("mobile", "target")
cmd.select("site", "resi 100-120 and chain A")
cmd.distance("dist1", "resi 100 and name CA", "resi 200 and name CA")Output
cmd.png("output/image.png", width=1200, height=900, dpi=150)
cmd.save("output/modified.pdb", "myprotein")Cleanup (REQUIRED)
cmd.quit()Common Pitfalls
1. `cmd.quit()` is mandatory — without it, the PyMOL process hangs and the container will time out. 2. Selection case sensitivity — "chain a" is NOT the same as "chain A". 3. `cmd.fetch()` will fail — there is no network inside the container. Use cmd.load() with pre-downloaded files. 4. `cmd.png()` before `cmd.quit()` — ensure all rendering is done before quitting. 5. Paths are relative to current directory — ensure you run the script from the correct directory or use absolute paths. 6. Container timeout — the default timeout is 300 seconds (5 minutes), which is sufficient for most rendering tasks. For long operations (e.g., ray-tracing large complexes), increase with --container_timeout=<seconds>. Do not reduce below 60 seconds. 7. Distance objects are NOT selections — cmd.distance() creates a measurement object, not an atom selection. Do NOT use cmd.count_atoms() on distance objects — it will error. Only use cmd.count_atoms() on valid atom selections (e.g., by residue name, chain, or proximity). 8. Selection names must be valid identifiers — names passed to cmd.select("name", ...) must be alphanumeric and underscores only, start with a letter, and contain no spaces. binding_site is valid; binding site or 1_ligand will crash PyMOL. 9. Multi-state structures (NMR) — for NMR ensembles or multi-model files, restrict distance measurements, alignments, and rendering to state=1 to prevent visual clutter and errors across all states simultaneously.
Common Recipes
Copy-paste ready recipes for common PyMOL visualization tasks. Each recipe assumes the init boilerplate has already been set up (see PYMOL_REFERENCE.md).
Cartoon with secondary structure coloring
cmd.load("data/structure.cif", "protein")
cmd.show("cartoon")
cmd.color("green", "ss h")
cmd.color("yellow", "ss s")
cmd.color("gray", "ss l+''")
cmd.orient()
cmd.png("output/cartoon.png", width=1200, height=900, dpi=150)
cmd.save("output/session.pse")B-factor (pLDDT) coloring
cmd.load("data/AF-P00520-F1-model_v4.cif", "protein")
cmd.show("cartoon")
cmd.spectrum("b", "red_white_blue", "polymer.protein")
cmd.orient()
cmd.png("output/bfactor.png", width=1200, height=900, dpi=150)
cmd.save("output/session.pse")AlphaFold pLDDT coloring (canonical thresholds)
cmd.load("data/structure.cif", "protein")
cmd.show("cartoon")
# Define AlphaFold's canonical pLDDT colors
cmd.set_color("af_very_low", [0xFF, 0x7D, 0x45]) # orange, pLDDT < 50
cmd.set_color("af_low", [0xFF, 0xDB, 0x13]) # yellow, 50 <= pLDDT < 70
cmd.set_color("af_confident", [0x65, 0xCB, 0xF3]) # light blue, 70 <= pLDDT < 90
cmd.set_color("af_very_high", [0x00, 0x53, 0xD6]) # dark blue, pLDDT >= 90
# Apply from lowest to highest threshold
cmd.color("af_very_low", "polymer.protein")
cmd.color("af_low", "polymer.protein and b > 50")
cmd.color("af_confident", "polymer.protein and b > 70")
cmd.color("af_very_high", "polymer.protein and b > 90")
cmd.orient()
cmd.png("output/plddt.png", width=1200, height=900, dpi=150)
cmd.save("output/session.pse")Highlight specific residues
cmd.load("data/structure.cif", "protein")
cmd.show("cartoon")
cmd.color("gray", "all")
cmd.select("active_site", "chain A and resi 100+102+150")
cmd.show("sticks", "active_site")
cmd.color("red", "active_site")
cmd.orient()
cmd.png("output/highlight.png", width=1200, height=900, dpi=150)
cmd.save("output/session.pse")Surface rendering
cmd.load("data/structure.cif", "protein")
cmd.show("cartoon", "polymer.protein")
cmd.color("green", "polymer.protein and ss h")
cmd.color("yellow", "polymer.protein and ss s")
cmd.color("gray", "polymer.protein and (ss l+'')")
cmd.show("surface", "polymer.protein")
cmd.set("surface_color", "white", "polymer.protein")
cmd.set("transparency", 0.3, "polymer.protein")
cmd.orient()
cmd.png("output/surface.png", width=1200, height=900, dpi=150)
cmd.save("output/session.pse")Electrostatic surface rendering
cmd.load("data/structure.cif", "protein")
cmd.remove("solvent")
cmd.show("cartoon", "polymer.protein")
cmd.color("gray80", "polymer.protein")
util.protein_vacuum_esp("polymer.protein", quiet=0)
cmd.show("surface", "polymer.protein")
cmd.set("transparency", 0.0, "polymer.protein")
cmd.set("two_sided_lighting", 1)
cmd.orient()
cmd.png("output/electrostatic.png", width=1200, height=900, dpi=150)
cmd.save("output/session.pse")Multi-chain complex colors
cmd.load("data/complex.cif", "complex")
cmd.remove("solvent")
cmd.show("cartoon", "polymer.protein")
chain_colors = ["cyan", "salmon", "green", "yellow", "magenta",
"orange", "slate", "limon", "deeppurple", "wheat"]
chains = cmd.get_chains("complex")
print(f"Chains found: {', '.join(chains)}")
for i, chain in enumerate(chains):
color = chain_colors[i % len(chain_colors)]
cmd.color(color, f"chain {chain}")
print(f" Chain {chain}: {color} ({cmd.count_atoms(f'chain {chain} and name CA')} residues)")
cmd.orient()
cmd.png("output/chains.png", width=1200, height=900, dpi=150)
cmd.save("output/session.pse")B-factor putty analysis
cmd.load("data/structure.cif", "protein")
cmd.remove("solvent")
cmd.show("cartoon", "polymer.protein")
cmd.cartoon("putty", "polymer.protein")
cmd.set("cartoon_putty_scale_min", 0.3)
cmd.set("cartoon_putty_scale_max", 3.0)
cmd.set("cartoon_putty_transform", 0)
cmd.spectrum("b", "blue_white_red", "polymer.protein")
cmd.orient()
cmd.png("output/putty.png", width=1200, height=900, dpi=150)
cmd.save("output/session.pse")Cavity and pocket visualization (including ligand focus)
cmd.load("data/structure.cif", "protein")
cmd.remove("solvent")
cmd.show("cartoon", "polymer.protein")
cmd.color("gray80", "polymer.protein")
# If a ligand is present, isolate one to focus the cavity view
if cmd.count_atoms("organic") > 0:
# Isolate a single ligand to prevent zoomed-out views on symmetrical complexes
first_atom = cmd.get_model("organic").atom[0]
cmd.select("target_ligand", f"organic and chain '{first_atom.chain}' and resi '{first_atom.resi}'")
cmd.show("sticks", "target_ligand")
util.cnc("target_ligand")
# Orient on the single ligand and zoom with enough buffer to see the pocket context
cmd.orient("target_ligand")
cmd.zoom("target_ligand", buffer=10.0)
else:
cmd.orient()
cmd.show("surface", "polymer.protein")
cmd.set("surface_color", "white", "polymer.protein")
cmd.set("transparency", 0.6, "polymer.protein")
cmd.set("surface_cavity_mode", 1)
cmd.set("surface_cavity_radius", 5.0)
cmd.set("surface_cavity_cutoff", -1.0)
cmd.set("cavity_cull", 50)
cmd.set("two_sided_lighting", 1)
cmd.png("output/cavities.png", width=1200, height=900, dpi=150)
cmd.save("output/session.pse")`surface_cavity_mode` values: 0 = no cavity (default), 1 = show cavities only, 2 = show outer surface trimmed around cavities.
Multi-structure batch rendering
For comparing multiple structures side-by-side or generating consistent renders across a set of PDB files. This is common in design campaigns.
import os
import glob
structures = glob.glob("data/*.pdb") + glob.glob("data/*.cif")
print(f"Found {len(structures)} structures to render")
for struct_path in sorted(structures):
name = os.path.splitext(os.path.basename(struct_path))[0]
cmd.load(struct_path, name)
n_atoms = cmd.count_atoms(name)
if n_atoms == 0:
print(f" SKIP {name}: 0 atoms loaded")
cmd.delete(name)
continue
cmd.show("cartoon", name)
cmd.color("green", f"{name} and ss h")
cmd.color("yellow", f"{name} and ss s")
cmd.color("gray", f"{name} and (ss l+'')")
cmd.orient(name)
cmd.png(f"output/{name}.png", width=1200, height=900, dpi=150)
print(f" Rendered {name} ({n_atoms} atoms)")
cmd.delete(name)Measure distance between residues
cmd.load("data/structure.cif", "protein")
cmd.show("cartoon")
cmd.distance("d1", "chain A and resi 10 and name CA", "chain A and resi 50 and name CA")
print(f"Distance: {cmd.get_distance('chain A and resi 10 and name CA', 'chain A and resi 50 and name CA'):.2f} A")
cmd.orient()
cmd.png("output/distance.png", width=1200, height=900, dpi=150)
cmd.save("output/session.pse")Zoom into binding pocket
cmd.load("data/complex.pdb", "complex")
cmd.show("cartoon", "polymer.protein")
cmd.color("gray", "polymer.protein")
cmd.select("pocket", "byres (polymer.protein within 5.0 of organic)")
cmd.show("sticks", "pocket")
cmd.color("cyan", "pocket")
cmd.zoom("pocket", buffer=3.0)
cmd.png("output/pocket_zoom.png", width=1200, height=900, dpi=150)
cmd.save("output/session.pse")Protein-ligand interaction
cmd.load("data/complex.pdb", "complex")
# Isolate a single ligand to prevent zoomed-out views on symmetrical complexes
if cmd.count_atoms("organic") > 0:
first_atom = cmd.get_model("organic").atom[0]
cmd.select("target_ligand", f"organic and chain '{first_atom.chain}'")
else:
raise RuntimeError("No ligand found.")
cmd.select("binding_site", "byres (polymer.protein within 4.0 of target_ligand)")
# Styled rendering with heteroatom coloring
cmd.show("cartoon", "polymer.protein")
cmd.set("cartoon_transparency", 0.4, "polymer.protein")
cmd.color("gray80", "polymer.protein")
cmd.show("sticks", "binding_site")
util.cbac("binding_site")
util.cnc("binding_site")
cmd.show("sticks", "target_ligand")
util.cbag("target_ligand")
util.cnc("target_ligand")
cmd.hide("sticks", "hydro")
# Show pocket waters
cmd.select("pocket_waters", "solvent within 4.0 of target_ligand")
cmd.show("spheres", "pocket_waters")
cmd.color("red", "pocket_waters")
cmd.set("sphere_scale", 0.15, "pocket_waters")
# Polar contacts (hydrogen bonds)
cmd.distance("polar_contacts", "target_ligand", "binding_site", cutoff=3.5, mode=2)
cmd.set("dash_color", "yellow")
cmd.set("dash_gap", 0.4)
cmd.set("dash_radius", 0.08)
cmd.hide("labels", "polar_contacts")
cmd.orient("target_ligand | binding_site")
cmd.zoom("target_ligand | binding_site", buffer=3.0)
cmd.png("output/ligand.png", width=1200, height=900, dpi=150)
cmd.save("output/session.pse")In silico mutagenesis
cmd.load("data/structure.cif", "protein")
cmd.show("cartoon")
cmd.wizard("mutagenesis")
cmd.get_wizard().set_mode("ALA")
cmd.get_wizard().do_select("chain A and resi 100")
cmd.get_wizard().apply()
cmd.set_wizard()
cmd.orient()
cmd.png("output/mutant.png", width=1200, height=900, dpi=150)
cmd.save("output/session.pse")Two-structure superposition with RMSD
PyMOL provides three structural alignment methods:
-
align— sequence-dependent; best when structures share >40% sequence
identity
-
super— sequence-independent superposition; use when structures are
structurally similar but have poor sequence identity
-
cealign— combinatorial extension; use when there is neither sequence nor
strong structural similarity
The recipe below attempts align first and falls back to cealign automatically.
cmd.load("data/structure1.cif", "model1")
cmd.load("data/structure2.cif", "model2")
try:
result = cmd.align("model2", "model1")
if result[1] < 20:
raise ValueError("Poor sequence alignment")
print(f"Method: align (>40% sequence identity)")
print(f"RMSD: {result[0]:.3f} A over {result[1]} atoms")
except Exception:
result = cmd.cealign("model1", "model2")
print(f"Method: cealign (low sequence identity fallback)")
print(f"RMSD: {result['RMSD']:.3f} A over {result['alignment_length']} atoms")
cmd.show("cartoon", "all")
cmd.color("cyan", "model1")
cmd.color("salmon", "model2")
cmd.orient()
cmd.png("output/superposition.png", width=1200, height=900, dpi=150)
cmd.save("output/session.pse")Load and modify an existing session
cmd.load("data/previous_session.pse")
cmd.color("marine", "chain A")
cmd.show("surface", "chain B")
cmd.orient()
cmd.png("output/modified.png", width=1200, height=900, dpi=150)
cmd.save("output/session.pse")To run any of these recipes, place the code in a Python script with the required header and boilerplate (see SKILL.md) and run it with:
uv run your_script.pyRelated skills
How it compares
Use pymol for scripted PyMOL PNG export on headless servers; use ChimeraX or interactive PyMOL GUI for manual molecular exploration.
FAQ
What is the required PyMOL headless initialization?
pymol requires every script to start with import pymol, set pymol_argv to ["pymol", "-cq"], call pymol.finish_launching(), then import cmd. Reversing import and finish_launching() crashes the session.
Can pymol use GPU rendering headlessly?
pymol runs PyMOL with OSMesa software rendering—no GPU or X display. Use cmd.png() for output or slow cmd.ray() for ray-traced quality. cmd.draw() requires hardware OpenGL and must not be used.
Is Pymol safe to install?
skills.sh reports 3 of 3 security scanners passed. Review the Security Audits panel on this page before installing in production.