Surface Viewer
SurfaceViewer renders compact, precomputed isosurfaces from Gaussian CUBE
data. Install the optional dependencies first:
See the SurfaceViewer API reference for the full parameter list.
Single Orbital Cube
from aseview import SurfaceViewer
viewer = SurfaceViewer(
"HOMO.cube",
surface_kind="orbital",
isovalue=0.04,
positive_color="#ef4444",
negative_color="#2563eb",
)
viewer.show()
Orbital surfaces are signed, so positive and negative lobes use separate colors.
Density Cube
from aseview import SurfaceViewer
viewer = SurfaceViewer(
"density.cube",
surface_kind="density",
isovalue=0.002,
surface_color="#22c55e",
opacity=0.55,
)
viewer.save_html("density-surface.html")
Density surfaces are unsigned and use surface_color.
Opacity And Layering
Solid surfaces can be transparent while still sharing the molecular viewer's atoms, bonds, and cell rendering. Transparent solid shells are drawn with a back-face pass followed by a front-face pass, which keeps orbital lobes readable around atoms without the hard clipping artifacts caused by depth pre-passes.
viewer = SurfaceViewer(
"HOMO.cube",
surface_kind="orbital",
isovalue=0.04,
opacity=0.65,
surface_style="solid",
)
viewer.show()
PNG export follows the normal viewer behavior and can keep a transparent background. GIF export uses the viewer background by default so animated surface or trajectory exports do not accumulate transparent-frame trails.
Cube Difference
from aseview import SurfaceViewer
viewer = SurfaceViewer.from_cube_diff(
"state-a-density.cube",
"state-b-density.cube",
diff_sign="1-2",
isovalue=0.0015,
positive_color="#f97316",
negative_color="#0ea5e9",
)
viewer.show()
Use diff_sign="2-1" when the second cube should be subtracted from the first
in the opposite order.
Three-File CHGCAR Difference
The documentation ships a BSD-licensed matgenb CHGCAR fixture under
docs/assets/examples/surface/chgcar_data/. It contains the combined
LiMoS2_F_CHGCAR system and the constituent LiMoS2_CHGCAR and F_CHGCAR
systems on the same real-space grid.
from pathlib import Path
from aseview import SurfaceViewer
from cube import delta_grid_field, read_grid_field
base = Path("docs/assets/examples/surface/chgcar_data")
chgcar_ab = read_grid_field(
base / "LiMoS2_F_CHGCAR",
format="chgcar",
name="LiMoS2_F",
kind="density",
chgcar_dataset="total",
)
chgcar_a = read_grid_field(
base / "LiMoS2_CHGCAR",
format="chgcar",
name="LiMoS2",
kind="density",
chgcar_dataset="total",
)
chgcar_b = read_grid_field(
base / "F_CHGCAR",
format="chgcar",
name="F",
kind="density",
chgcar_dataset="total",
)
chgdiff_ab_minus_a = delta_grid_field(chgcar_ab, chgcar_a, sign="1-2")
chgdiff = delta_grid_field(chgdiff_ab_minus_a, chgcar_b, sign="1-2")
chgdiff = chgdiff.copy_with(name="LiMoS2_F_minus_LiMoS2_minus_F")
viewer = SurfaceViewer(
chgdiff,
surface_kind="delta_density",
isovalue=0.01,
positive_color="#f97316",
negative_color="#2563eb",
molecule=chgcar_ab.to_atoms(),
)
viewer.save_html("surface_limos2_f_chgdiff.html")
The same field can also be written explicitly when direct array arithmetic is clearer for an exploratory notebook:
from cube import require_same_grid
require_same_grid(chgcar_ab, chgcar_a)
require_same_grid(chgcar_ab, chgcar_b)
manual_chgdiff = chgcar_ab.copy_with(
data=chgcar_ab.data - chgcar_a.data - chgcar_b.data,
name="LiMoS2_F_minus_LiMoS2_minus_F",
kind="delta_density",
)
viewer = SurfaceViewer(
manual_chgdiff,
surface_kind="delta_density",
isovalue=0.01,
positive_color="#f97316",
negative_color="#2563eb",
molecule=chgcar_ab.to_atoms(),
)
viewer.save_html("surface_limos2_f_chgdiff.html")
Precomputed CHGDIFF/charge-density-difference files can also be loaded directly
with surface_kind="delta_density" when the file already contains signed
difference data.
from aseview import SurfaceViewer
viewer = SurfaceViewer(
"docs/assets/examples/surface/samples/chgdiff/zenodo_13752735_d24_diff.vasp",
surface_kind="delta_density",
isovalue=0.025,
positive_color="#f97316",
negative_color="#2563eb",
)
viewer.save_html("surface_zenodo_chgdiff_direct.html")
Additional CHGCAR, CHGDIFF, and CUBE fixtures are cataloged under
docs/assets/examples/surface/samples/.
Multi-Isovalue Shells
from aseview import SurfaceViewer
viewer = SurfaceViewer(
"HOMO.cube",
surface_kind="orbital",
isovalue="0.03:0.09:4",
surface_color="#3b82f6",
positive_color="#ef4444",
negative_color="#2563eb",
opacity_ramp=[0.25, 0.45, 0.65, 0.8],
surface_style="solid",
)
viewer.show()
isovalue accepts explicit lists, comma-separated strings, start:stop:count,
and linspace(start,stop,count) strings. For density
surfaces, all shells use
surface_color. For orbital and delta-density surfaces, each level creates a
positive shell and a negative shell using positive_color and
negative_color; opacity_ramp supplies the per-level opacity.
Contour Style
viewer = SurfaceViewer(
"HOMO.cube",
surface_kind="orbital",
isovalue="0.03:0.09:4",
surface_style="contour",
)
viewer.show()
Contour surfaces use a denser extraction grid by default and render horizontal slice lines instead of mesh edges, so a higher isovalue count produces a more closely spaced contour field.
Surface Sequences
SurfaceViewer.sequence() renders a frame list where each molecular frame can
carry one or more volumetric surfaces. This is the direct Python contract and is
best for scripts that already know the density files next to each trajectory
frame:
The generated viewer uses the normal MolecularViewer animation controls: the
frame slider and play button step through trajectory frames, while the Surface
card switches between per-frame surfaces such as HOMO/LUMO. PNG and GIF export
use the same toolbar buttons as MolecularViewer.
from aseview import SurfaceViewer
frames = [
{
"label": "step 0",
"molecule": atoms_0,
"surfaces": {
"HOMO": "step_000/HOMO.cube",
"LUMO": "step_000/LUMO.cube",
},
},
{
"label": "step 1",
"molecule": atoms_1,
"surfaces": {
"HOMO": "step_001/HOMO.cube",
"LUMO": "step_001/LUMO.cube",
},
},
]
viewer = SurfaceViewer.sequence(frames, surface_kind="orbital", isovalue=0.03)
viewer.save_html("homo-lumo-sequence.html")
For the CLI, put one frame object per line in JSONL. Paths are resolved relative to the manifest:
{"label":"step 0","molecule":{"symbols":["H"],"positions":[[0,0,0]]},"surfaces":{"HOMO":"step_000/HOMO.cube","LUMO":"step_000/LUMO.cube"}}
{"label":"step 1","molecule":{"symbols":["H"],"positions":[[0,0,0.1]]},"surfaces":{"HOMO":"step_001/HOMO.cube","LUMO":"step_001/LUMO.cube"}}
The tracked Psi4 density sample uses an in-memory two-frame manifest over
docs/assets/examples/surface/samples/cube/psi4_Da.cube then
docs/assets/examples/surface/samples/cube/psi4_Db.cube:
from pathlib import Path
from aseview import SurfaceViewer
base = Path("docs/assets/examples/surface/samples/cube")
frames = [
{
"label": "Da density",
"surfaces": [
{
"label": "Da density",
"path": str(base / "psi4_Da.cube"),
"surface_kind": "density",
"isovalue": 0.0317329,
}
],
},
{
"label": "Db density",
"surfaces": [
{
"label": "Db density",
"path": str(base / "psi4_Db.cube"),
"surface_kind": "density",
"isovalue": 0.0317329,
}
],
},
]
viewer = SurfaceViewer.sequence(
frames,
surface_kind="density",
isovalue=0.0317329,
normalize_orbital=False,
)
viewer.save_html("surface_psi4_density_sequence.html")
The embedded docs viewer keeps the sequence compact by rendering only the two
tracked density frames, with labels Da density and Db density.
For archive-style trajectories, extxyz frame metadata can hold a surface path:
v1 Scope
The first SurfaceViewer release does not implement ESP/NCI features. ESP
colormaps, ESP colorbars, NCI modes, NCI color controls, solvent surfaces, and
VS Code .cube editor integration are excluded from v1.