Skip to content

SurfaceViewer

Cube-backed isosurface viewer for orbital, density, and two-cube delta-density data.

SurfaceViewer renders on top of the same browser runtime as MolecularViewer, so the standard molecule controls, frame slider, camera presets, PNG export, and GIF export remain available for surface pages. GIF export uses an opaque viewer background by default to avoid transparent frame trails; PNG export can still be transparent.

Install the optional surface dependencies before using it:

pip install "aseview[surface]"

See the Surface Viewer example for complete single-cube, density, diff, and multi-isovalue snippets.

Interactive Controls

Surface pages open in the Spring theme by default and place the Isosurface card first in the control rail. A compact canvas badge shows the active surface kind and visible-shell count; select it at any time to reopen the controls.

The card provides:

  • solid, mesh, contour, and dot rendering styles;
  • a global opacity control;
  • independent visibility, color, and opacity controls for every shell;
  • Show all, Hide all, and Fit view actions;
  • frame and named-surface selection only when viewing a surface sequence; and
  • a live signed-isovalue control when using the on-the-fly surface server.

Positive and negative orbital or difference-density lobes are separate shells, so they can be recolored or hidden independently. Per-shell choices persist when molecule settings trigger a redraw and while equivalent shells advance through a sequence.

Constructor

SurfaceViewer(source, **kwargs)

Parameters

Parameter Type Description Default
source str, Path, cube.Cube, cube.GridField, payload Cube-like input or a prebuilt compact surface payload Required
surface_kind str "orbital" or "density" for single-cube inputs "orbital"
dataset str, int, None Dataset selector for multi-dataset cube inputs None
molecule ASE Atoms, viewer dict, path, list, None Optional molecule context shown with the surface None
theme str, None Visual theme. None uses the global default. None

Use SurfaceViewer.from_cube_diff(left, right, ...) for two-cube delta-density surfaces. Use SurfaceViewer(source, surface_kind="delta_density") when source is already a signed CHGDIFF/charge-density-difference field.

Surface Options

Parameter Type Description Default
isovalue float, list[float], str, "auto" Nonzero signed density level, signed-surface magnitude, multiple levels, or a range spec "auto"
surface_color str Color for positive density shells "#3b82f6"
positive_color str Color for positive orbital or delta-density shells "#ef4444"
negative_color str Color for negative density, orbital, or delta-density shells "#2563eb"
opacity float Maximum shell opacity 0.65
opacity_ramp list[float], None Per-isovalue opacity values. Length must match isovalue=[...]. None
surface_style str "solid", "mesh", "contour", or "dot" "solid"
quality str "low", "medium", or "high" extraction budget "medium"
max_grid_points int, None Explicit grid downsampling budget None
max_triangles int, None Explicit triangle budget None
smoothing float, None Optional scalar-field smoothing before extraction None
normalize_orbital bool Normalize orbital data before isosurface extraction True

Display Options

Parameter Type Description Default
show_molecule bool Show the optional molecule context True
show_cell bool Show the molecule cell when available True
show_bonds bool Show molecule bonds when available True

Camera options such as viewMode, viewPreset, viewDirection, viewEuler, viewUp, viewFit, and rotationMode follow the same conventions as the other Python viewers.

isovalue range strings are accepted anywhere a Python or CLI surface isovalue is parsed:

Syntax Meaning
"0.02,0.04,0.08" Explicit levels
"0.02:0.08:4" NumPy-style evenly spaced count, equivalent to 4 points from start to stop
"linspace(0.02,0.08,4)" Explicit linspace form with both endpoints included

For surface_kind="density", each signed level extracts one shell: positive levels use surface_color and negative levels use negative_color. For orbital and delta_density, levels are treated as magnitudes, so a negative input is accepted but still generates both positive and negative phases. Zero is not a valid isovalue. On all-negative density fields, "auto" selects a negative level inside the data range.

surface_style="contour" uses a denser default extraction grid than solid or mesh surfaces and renders dense horizontal slice lines instead of mesh edges.

Solid Surface Transparency

surface_style="solid" keeps transparent orbital and density shells readable with the molecule layer by rendering each transparent shell as separate back-face and front-face passes. The front pass uses opacity; the back pass is attenuated so overlapping lobes remain visible without creating the hard clipping artifacts that depth pre-passes can introduce. Opaque shells still use a single mesh pass.

This is a practical browser-rendering compromise for Three.js r128. It improves the usual orbital and charge-density cases, but it is not full order-independent transparency for every possible self-overlapping mesh.

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()

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")

Two-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()

Precomputed Difference Field

from aseview import SurfaceViewer

viewer = SurfaceViewer(
    "CHGDIFF.vasp",
    surface_kind="delta_density",
    isovalue=0.02,
    positive_color="#f97316",
    negative_color="#2563eb",
)
viewer.show()

Surface Sequences

Use SurfaceViewer.sequence() when the surface geometry changes frame by frame. The Python API takes an explicit list of frame dictionaries; each frame can contain one surface or a named surfaces mapping.

from aseview import SurfaceViewer

frames = [
    {
        "label": "0 fs",
        "molecule": atoms_0,
        "surfaces": {
            "HOMO": "frame_000/HOMO.cube",
            "LUMO": "frame_000/LUMO.cube",
        },
    },
    {
        "label": "1 fs",
        "molecule": atoms_1,
        "surfaces": {
            "HOMO": "frame_001/HOMO.cube",
            "LUMO": "frame_001/LUMO.cube",
        },
    },
]

viewer = SurfaceViewer.sequence(frames, surface_kind="orbital", isovalue=0.03)
viewer.save_html("homo-lumo-trajectory.html")

For a single frame with multiple related density fields, put each field under a surface label. Two-path entries are treated as delta-density surfaces unless a different surface_kind is set explicitly:

viewer = SurfaceViewer.sequence(
    [
        {
            "label": "adsorbate comparison",
            "surfaces": {
                "AB": {"path": "AB_CHGCAR", "surface_kind": "density"},
                "A": {"path": "A_CHGCAR", "surface_kind": "density"},
                "B": {"path": "B_CHGCAR", "surface_kind": "density"},
                "AB - A": {
                    "paths": ["AB_CHGCAR", "A_CHGCAR"],
                    "isovalue": 0.01,
                },
            },
        }
    ],
    surface_kind="density",
    isovalue=0.02,
)

For CLI or archive workflows, use a JSONL manifest with one frame object per line. Paths in JSONL are resolved relative to the manifest file. As an import convenience, extxyz trajectories can store per-frame surface paths in Atoms.info["surface_path"].

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],
)
viewer.show()

For unsigned density surfaces, each parsed isovalue entry creates one shell using surface_color. For signed orbital and delta-density surfaces, each level creates positive and negative shells using positive_color and negative_color; the same opacity_ramp is applied to both phases.

v1 Scope

SurfaceViewer v1 intentionally excludes ESP/NCI workflows: no ESP colormaps, ESP colorbars, NCI modes, NCI color controls, solvent surfaces, or VS Code .cube editor integration are implemented. Use it for cube-backed orbital, density, and two-cube delta-density isosurfaces in Python, Jupyter, and saved HTML.