Python Bridge
extensions/python-bridge exposes the services layer via PyO3.
7.1 Installation
See §1.3 Python Package Installation for wheel installation instructions and from-source build steps. Quick verification:
python -c "import orbitron; print(orbitron.Orbitron().load('fixtures/benzene.xyz').atom_count())"7.2 Usage overview
Atom ids are 1-based, as they are in the CLI and the viewer. distance(scene, 0, 1) raises ValueError: Atom 0 not found.
import orbitron
orb = orbitron.Orbitron(data_root="/scratch/user/runs")
# Inspect a large local source without parsing it. A block verdict is returned
# as data so a notebook can decide what to do without an interactive prompt.
resources = orb.resources("benzene.out")
print(resources["report"]["source"]["projected_host_bytes"])
print(resources["verdict"]["band"])
scene = orb.load("benzene.out") # path is relative to the data root
print("Atoms:", scene.atom_count())
print("Bonds:", scene.bond_count())
print("First atom Z:", scene.atomic_numbers()[0])
print("Distance 1-2:", orb.distance(scene, 1, 2)) # atom ids are 1-based
# Geometry analysis (same output as CLI)
summary = orb.analyze_geometry(scene)
print(summary["atoms"], "atoms", summary["warnings"])
# Render a PNG headlessly
orb.render(scene, "benzene.png", width=1920, height=1080)
# Grab an RGBA frame for embedding in another UI
frame = orb.render_frame(scene, width=1024, height=768)
rgba = frame.as_bytes() # copy as Python bytes
mv = frame.as_memoryview() # zero-copy memoryview
# Jupyter display helper (requires pillow)
from PIL import Image
from IPython.display import display
img = Image.frombytes("RGBA", (frame.width, frame.height), rgba)
display(img)
# Load trajectories (optimization/MD logs)
trajectory = orb.load_trajectory("path/to/trajectory.log")
print("Frames:", trajectory.frame_count())
last_frame = trajectory.last_frame()
for index in range(trajectory.frame_count()):
# A variable-cell trajectory keeps the cell attached to each frame scene.
print(index, trajectory.frame(index).unit_cell())
# Structured export for custom dashboards
scene_data = scene.to_dict()
print(scene_data["atoms"][0]["position"])
# Molecular results and calculation provenance
print(scene.dipole_moment())
print(scene.excited_states())
print(orb.analyze_bond_orders(scene))
print(orb.task_summaries("benzene.out"))
# Ordered PDB/mmCIF residue records, including unresolved declared residues.
# atom_numbers uses the same one-based numbering as the rest of Orbitron.
for residue in scene.residues():
print(
residue["site"],
residue["name"],
residue["component_name"],
residue["atom_numbers"],
)
# Serialize a SceneGraph for wasm/Jupyter widgets
scene_bytes = scene.to_bytes()
# Export a single self-contained, interactive HTML viewer (embeds the WASM viewer
# + scene as one file — open in any browser, or drop into a slide deck / webpage)
import orbitron as orb
orb.export_html(scene, "benzene.html", camera=None, appearance="presentation")For large scenes, avoid building one Python object per coordinate. The packed buffer methods make one copy at the Rust/Python boundary and can be wrapped by NumPy without another copy:
import numpy as np
positions = np.frombuffer(scene.atom_positions_buffer(), dtype="<f4").reshape(-1, 3)
atomic_numbers = np.frombuffer(scene.atomic_numbers_buffer(), dtype=np.uint8)atom_positions() and atomic_numbers() remain convenient for small scenes. to_bytes() serializes the complete scene for the WASM component, while to_memoryview() exposes those serialized bytes as a Python memory view.
resources(path, mode="indexed-trajectory") reads file metadata and at most the first 64 KiB. It returns the same source report, host-memory snapshot, policy verdict, and configured hard cap as the CLI preflight. Use mode="trajectory" when the planned operation retains every frame, or mode="scene" for a static open. A "block" verdict does not raise MemoryError; load remains the operation that applies the Python warning or hard-cap policy. Missing files, directories, and unknown mode names raise an exception before parsing starts.
Build a scene from supported SMILES and declare its intended electronic state:
scene = orbitron.Scene.from_smiles("[NH3+]CC(=O)[O-]")
print(scene.molecular_electronic_state())
scene.set_molecular_electronic_state(total_charge=0, spin_multiplicity=1)
print(scene.molecular_electronic_state()["electron_count"])Scene.from_smiles(smiles, generate_3d=True, seed=0x005A11E5) uses a deterministic 3D embedding by default. The same supported subset and refusal rules as orbitron from-smiles apply. set_molecular_electronic_state rejects multiplicity zero and periodic scenes. It changes builder intent without altering electronic-structure evidence parsed from a calculation.
Scene.residues() returns an empty list for structures without biological identity. PDB and mmCIF records keep author and label chain/sequence fields, insertion codes, alternate locations, polymer classification, one-letter codes, unresolved-residue markers, and the source atoms for each residue. mmCIF records also include label_component_id, component_type, component_name, component_formula, component_formula_weight, and parent_component_id when the source supplies _chem_comp definitions.
Available helpers mirror the CLI modules: analyze_orbitals, analyze_populations, analyze_bond_orders, analyze_vibrations, analyze_band_structure, analyze_density_of_states, export, bounding_box, etc. Analysis Results maps these records to the desktop, TUI, and CLI views.
Chemical identifiers — generate InChI / InChIKey / SMILES / molecular formula, including for metal complexes and coordination compounds where RDKit-style perception fails:
scene = orb.load("cisplatin.xyz")
orb.inchi(scene) # 'InChI=1S/...'
orb.inchikey(scene) # 27-character hashed key
orb.smiles(scene) # SMILES (organometallic sandwich rings degrade to chains)
orb.formula(scene) # 'Cl2H4N2Pt' (Hill notation)Periodic datasets (VASP and Quantum ESPRESSO band/DOS) are exposed through the scene when present:
periodic = scene.periodic_electronic_structure()
if periodic:
print(periodic["fermi_energy_ev"])
print(periodic["band_structure"]["spin_channels"])
print(periodic["density_of_states"]["energies_ev"][:5])
# Fundamental gap, computed at load time. None for a metal, or when the
# run has no k-point coordinates to report the extrema at.
gap = periodic["band_gap"]
if gap:
kind = "direct" if gap["is_direct"] else "indirect"
print(f"{gap['value_ev']:.3f} eV ({kind})")
print("VBM at", gap["valence_kpoint"], "CBM at", gap["conduction_kpoint"])
# Site- and orbital-resolved DOS, from a VASP DOSCAR written with
# LORBIT >= 10. Indexed spin -> atom -> orbital -> energy.
pdos = periodic["projected_dos"]
if pdos:
print(pdos["orbitals"]) # ['s', 'py', 'pz', 'px', 'dxy', ...]
print(len(pdos["per_atom"][0])) # atoms in the decomposition
zone = scene.brillouin_zone()
if zone:
print(zone["zone"]["reciprocal_basis_inv_angstrom"])
print(len(zone["zone"]["faces"]), "first-zone faces")band_structure["sampling"] reports "path" or "mesh" when the run says which. Eigenvalues from a "mesh" are stored in grid order, so plotting them against k-point position is not a band structure — see the CLI guide for the full explanation.
Pass include_projections=True to request orbital/site projection weights (can be large).
- Hands-on examples (repo root):
extensions/python-bridge/examples/api_smoke_test.py— CLI-style smoke test that exercises loading, measurements, exports, rendering, and analysis helpers.extensions/python-bridge/examples/api_quickstart.ipynb— step-by-step notebook for geometry basics.extensions/python-bridge/examples/api_analysis_walkthrough.ipynb— notebook showcasing orbital, population, and vibrational summaries.extensions/python-bridge/examples/api_trajectory_view.ipynb— minimal trajectory preview with in-memory render frames.extensions/python-bridge/examples/api_viewer_widget.ipynb— Jupyter widget demo with wasm viewer + delta updates.extensions/python-bridge/examples/orbitron_tutorial.ipynb— end-to-end tutorial notebook for the Python bridge.viewer/wasm/scripts/generate_scene_bin.py— dump a SceneGraph to a binary file for the web viewer.
7.3 Exporting bundles
export_bundle mirrors orbitron canonical export, which matters when the notebook is running on a cluster and the data is far larger than what you want to carry home:
info = orb.export_bundle("run.out", "run.orbpack", mo=31)
print(info["source_bytes"], info["bytes"], info["transfer_reduction_percent"])
print(info["sections"])
scene = orb.load("run.orbpack") # opens like any other fileOptions match the CLI: with_source, mo, scrub, force. The returned byte counts compare a regular source file with the finished bundle. The source count and percentage are None for a directory input because directory metadata is not a count of the files Orbitron read. See Bundles for what goes in by default and why.
To show a bundle in a notebook, use orbitron.view:
import orbitron
orbitron.view("run.orbpack")orb.load("run.orbpack") gives you a Scene, and a scene displays inline too — but a scene is structure alone, so the bundle’s grids, frames, modes and recorded view are gone by the time it reaches the browser. view hands the container over intact, which is what makes an orbital surface or an optimisation appear in the cell.
7.4 Fonts (WASM widget)
The wasm viewer embeds the Inter Regular font (viewer/wasm/assets/Inter-Regular.ttf) for in-scene measurement labels. The font is licensed under SIL OFL 1.1; see viewer/wasm/assets/Inter-OFL.txt for the full license text.