Scenes and structures

A StructureScene holds all the data needed to render a structure: atom positions, species, bonds, polyhedra, and a camera view. The Getting started guide covers how to create a scene from an XBS file, an ASE Atoms object, or a pymatgen Structure. This page explains how to customise what goes into a scene.

Atoms and styles

When building a scene from an ASE Atoms object or a pymatgen Structure, from_ase() and from_pymatgen() generate default AtomStyle objects for every species using default_atom_style(). You can override individual species by passing an atom_styles dict – only the species you include are replaced; the rest keep their defaults:

from hofmann import AtomStyle, StructureScene

scene = StructureScene.from_pymatgen(
    structure, bonds,
    atom_styles={
        "Zr": AtomStyle(radius=1.4, colour=(0.5, 1.0, 0.5)),
        "O": AtomStyle(radius=0.8, colour="red"),
    },
    title="Custom colours",
)

This also works with styles loaded from a file (see Styles and presets):

from hofmann import load_styles

styles = load_styles("my_styles.json")
scene = StructureScene.from_pymatgen(
    structure, bonds,
    atom_styles=styles.atom_styles,
)

The following keyword arguments are accepted by from_ase() and from_pymatgen() (and their corresponding classmethods):

  • atom_styles – per-species AtomStyle overrides, merged on top of auto-generated defaults.

  • title – scene title for display.

  • view – a ViewState to use instead of the auto-centred default.

  • atom_data – per-atom metadata arrays for colourmap rendering (see Colouring by per-atom data).

Sites with multiple species or fractional occupancy are also supported: pass a Composition value in place of a species label, and the renderer draws the site as VESTA-style pie wedges. See Partial and mixed occupancy for the full guide.

Bonds

Bonds are detected at render time from declarative BondSpec rules. Only the species pair and maximum length are required; min_length, radius, and colour all have sensible defaults:

from hofmann import BondSpec

spec = BondSpec(species=("C", "H"), max_length=1.2)

You can override any default on a per-spec basis:

spec = BondSpec(species=("C", "H"), max_length=1.2,
                radius=0.15, colour="steelblue")

Species matching supports wildcards:

# Match any bond between any species:
BondSpec(species=("*", "*"), max_length=2.5)

When no bond specs are provided, from_ase() and from_pymatgen() generate sensible defaults from VESTA bond length cutoffs.

SrTiO3 perovskite with bonds

Bond display defaults

radius and colour fall back to BondSpec.default_radius (0.1) and BondSpec.default_colour (0.5, grey) when not set explicitly. You can change these class-level defaults to affect all specs that have not been given an explicit value:

BondSpec.default_radius = 0.15
BondSpec.default_colour = "grey"

The repr() of a spec shows <default ...> for values that will follow the class default, making it easy to see what has been explicitly set and what has not.

Polyhedra

Coordination polyhedra are built from the bond graph: for each atom whose species matches the centre pattern, a convex hull is constructed from its bonded neighbours.

from hofmann import PolyhedronSpec

spec = PolyhedronSpec(
    centre="Ti",
    colour=(0.5, 0.7, 1.0),
    alpha=0.3,
)
scene = StructureScene.from_pymatgen(
    structure, bonds, polyhedra=[spec],
)
SrTiO3 perovskite with TiO6 octahedra

Polyhedra can also inherit per-atom colours from colour_by data attached to their centre atoms. See Colouring by per-atom data for details on per-atom colouring, custom colouring functions, and polyhedra colour inheritance.

Periodic structures

When a scene has a lattice (i.e. it was created from a periodic ASE Atoms object or a pymatgen Structure), the renderer can expand periodic image atoms so that bonds crossing cell boundaries are drawn correctly. PBC behaviour is controlled at render time via RenderStyle fields:

  • pbc (default True) – enable or disable PBC expansion.

  • pbc_padding (default 0.1 angstroms) – the Cartesian margin around the unit cell. Atoms within this distance of a cell face get an image on the opposite side. The default of 0.1 angstroms captures atoms sitting on cell boundaries without cluttering the scene. Set to None to disable geometric padding entirely (image atoms are still created by complete and recursive bond specs).

scene = StructureScene.from_pymatgen(structure, bonds)
scene.render_mpl(pbc=True, pbc_padding=0.1)
Diamond-cubic Si with PBC expansion

When polyhedra are defined, the PBC expansion also ensures that every atom matching a polyhedron centre pattern has its full coordination shell present, so that boundary polyhedra are complete.

Bond completion across boundaries

When atoms sit near cell boundaries, some of their bonded neighbours may lie outside the pbc_padding margin and are not included in the scene. Without those image atoms the bonds are missing entirely. In the Zr-S network below (large green = Zr, small yellow = S), some atoms have fewer bonds than expected because their partners across the cell face are missing:

Zr-S structure with incomplete bonds at cell boundaries

Setting complete on a bond spec tells hofmann to add the missing neighbours. Here complete="Zr" adds missing S neighbours around visible Zr atoms, without pulling in new Zr images around visible S:

BondSpec(species=("S", "Zr"), max_length=2.9, complete="Zr")
Zr-S network with complete="Zr" adding missing S neighbours

Use complete="*" to complete around both species in the pair.

Molecule deduplication

When molecules span cell boundaries, recursive expansion can produce duplicate fragments – the same molecule may be reconstructed starting from different periodic images. In the recursive example above, many N2H6 molecules appear more than once because they are reconstructed from multiple image atoms.

Setting deduplicate_molecules=True on the render call keeps only the canonical image of each molecule, removing the duplicates:

scene.render_mpl(deduplicate_molecules=True)
Same structure with deduplicate_molecules=True removing duplicate molecules

The deduplication algorithm applies several heuristics to handle mixed systems (e.g. a slab with adsorbed solvent):

  • Extended structure detection. A connected component that contains both a physical atom and a periodic image of the same atom is classified as an extended structure (slab, framework, or bulk crystal). These components are always preserved and excluded from deduplication.

  • Subset removal. Non-wrapped components whose source atoms are all already represented by an extended structure are treated as redundant image copies and removed. This handles molecules that bond to a surface: one copy is absorbed into the extended structure while standalone image copies are discarded.

  • Canonical selection. Among remaining duplicate molecules that share source atoms, the algorithm keeps the copy with the most atoms, breaking ties by the number of physical (non-image) atoms, then by proximity to the cell origin in fractional coordinates.

  • Orphan cleanup. After selection, any image atom that has no bonds within the kept set is removed. This catches isolated padding artefacts at cell edges.