API reference

Scene construction

class hofmann.StructureScene[source]

Top-level scene holding atoms, frames, styles, bond rules, and view.

The view (camera/projection state) and atom_data (per-atom metadata) properties are documented individually below.

species

One entry per site row, either a species label or a Composition describing a partially occupied or mixed site.

frames

List of coordinate snapshots. Each Frame may carry its own lattice for variable-cell trajectories.

atom_styles

Mapping from species label to visual style.

bond_specs

Declarative bond detection rules.

polyhedra

Declarative polyhedron rendering rules.

title

Scene title for display.

Parameters:
__init__(species, frames, atom_styles=None, bond_specs=None, polyhedra=None, view=None, title='', atom_data=None)[source]
Parameters:
Return type:

None

property view: ViewState

Camera / projection state.

property lattice: ndarray | None

Lattice matrix of the first frame.

Convenience accessor equivalent to self.frames[0].lattice. Returns None when the scene has no frames or the first frame has no lattice (non-periodic structure).

property atom_data: AtomData

Return a read-only mapping view of per-atom metadata.

Each stored value is either a 1-D array of length n_atoms (static across the trajectory) or a 2-D array of shape (len(self.frames), n_atoms) (per-frame values). The 2-D shape is checked against the trajectory length at two points: by set_atom_data() at assignment time, and by the private _validate_for_render helper at the start of every public render_* call. Mutating self.frames after a 2-D assignment leaves the container temporarily out of sync until the next render call raises, or until a set_atom_data() call (with clear_2d_atom_data() first if more than one 2-D entry is stored) restores consistency.

Stored arrays are returned read-only. The property has no setter; scene.atom_data = ... raises AttributeError. The container itself exposes only Mapping reads, so scene.atom_data[key] = ... raises TypeError and scene.atom_data.pop(...) raises AttributeError. Use colour_by on the render methods to visualise a key, and see set_atom_data(), del_atom_data(), and clear_2d_atom_data() for all modifications.

classmethod from_xbs(bs_path, mv_path=None)[source]

Create a StructureScene from XBS .bs (and optional .mv) files.

Parameters:
  • bs_path (str | Path) – Path to the .bs structure file.

  • mv_path (str | Path | None (default: None)) – Optional path to a .mv trajectory file. When provided, the scene will contain multiple frames.

Return type:

StructureScene

Returns:

A fully configured StructureScene with styles and bond specs parsed from the file.

See also

hofmann.construction.scene_builders.from_xbs()

classmethod from_ase(atoms, bond_specs=None, *, polyhedra=None, centre_atom=None, atom_styles=None, title='', view=None, atom_data=None)[source]

Create a StructureScene from ASE Atoms object(s).

For periodic systems, fractional coordinates are wrapped to [0, 1) and stored as Cartesian coordinates. For non-periodic systems, Cartesian positions are stored directly and lattice is None.

Parameters:
  • atoms (Atoms | Sequence[Atoms]) – A single ASE Atoms object or a sequence of Atoms (e.g. from an MD trajectory or ase.io.Trajectory).

  • bond_specs (list[BondSpec] | None (default: None)) – Bond detection rules. None generates sensible defaults from VESTA bond length cutoffs; pass an empty list to disable bonds.

  • polyhedra (list[PolyhedronSpec] | None (default: None)) – Polyhedron rendering rules. None disables polyhedra.

  • centre_atom (int | None (default: None)) – Index of the atom to centre the unit cell on. Fractional coordinates are shifted so this atom sits at (0.5, 0.5, 0.5). Only valid for periodic systems. If view is also provided, the explicit view takes precedence and only the fractional-coordinate shift is applied.

  • atom_styles (dict[str, AtomStyle] | None (default: None)) – Per-species style overrides. When provided, these are merged on top of the auto-generated defaults so you only need to specify the species you want to customise.

  • title (str (default: '')) – Scene title for display.

  • view (ViewState | None (default: None)) – Camera / projection state. When None (the default), the view is auto-centred on the centre atom (if set) or the centroid of all atoms.

  • atom_data (dict[str, TypeAliasType] | None (default: None)) – Per-atom metadata arrays, keyed by name. Each value is a 1-D array of length n_atoms (same every frame) or a 2-D array of shape (n_frames, n_atoms) (per-frame values).

Return type:

StructureScene

Returns:

A StructureScene with default element styles.

Raises:
  • ImportError – If ASE is not installed.

  • ValueError – If atoms is an empty sequence, if centre_atom is out of range, if centre_atom is used with a non-periodic system, or if frames in a trajectory have inconsistent species, atom counts, or periodicity.

See also

hofmann.construction.scene_builders.from_ase()

classmethod from_pymatgen(structure, bond_specs=None, *, polyhedra=None, centre_atom=None, atom_styles=None, title='', view=None, atom_data=None)[source]

Create a StructureScene from pymatgen Structure object(s).

Fractional coordinates are wrapped to [0, 1) and stored as Cartesian coordinates. Periodic boundary handling (image-atom expansion, recursive bond depth, molecule deduplication) is controlled at render time via RenderStyle.

Parameters:
  • structure (Structure | Sequence[Structure]) – A single pymatgen Structure or a sequence of structures (e.g. from an MD trajectory).

  • bond_specs (list[BondSpec] | None (default: None)) – Bond detection rules. None generates sensible defaults from VESTA bond length cutoffs; pass an empty list to disable bonds.

  • polyhedra (list[PolyhedronSpec] | None (default: None)) – Polyhedron rendering rules. None disables polyhedra.

  • centre_atom (int | None (default: None)) – Index of the atom to centre the unit cell on. Fractional coordinates are shifted so this atom sits at (0.5, 0.5, 0.5). If view is also provided, the explicit view takes precedence and only the fractional- coordinate shift is applied.

  • atom_styles (dict[str, AtomStyle] | None (default: None)) – Per-species style overrides. When provided, these are merged on top of the auto-generated defaults so you only need to specify the species you want to customise.

  • title (str (default: '')) – Scene title for display.

  • view (ViewState | None (default: None)) – Camera / projection state. When None (the default), the view is auto-centred on the structure.

  • atom_data (dict[str, TypeAliasType] | None (default: None)) – Per-atom metadata arrays, keyed by name. Each value is a 1-D array of length n_atoms (same every frame) or a 2-D array of shape (n_frames, n_atoms) (per-frame values).

Return type:

StructureScene

Returns:

A StructureScene with default element styles.

Raises:

ImportError – If pymatgen is not installed.

See also

hofmann.construction.scene_builders.from_pymatgen()

save_styles(path)[source]

Save the scene’s styles to a JSON file.

Writes atom_styles, bond_specs, and polyhedra sections. Render style is not included (it belongs to the render call, not the scene).

Parameters:

path (str | Path) – Destination file path.

Return type:

None

load_styles(path)[source]

Load styles from a JSON file and apply them to the scene.

Atom styles are merged (existing species keep their styles unless overridden). Bond specs and polyhedra are replaced entirely. The render_style section, if present in the file, is ignored; pass it to the render call instead.

Parameters:

path (str | Path) – Source file path.

Return type:

None

centre_on(atom_index, *, frame=0)[source]

Centre the view on a specific atom.

Sets view.centre to the Cartesian position of the atom at atom_index in the given frame.

Parameters:
  • atom_index (int) – Index of the atom to centre on.

  • frame (int (default: 0)) – Frame index to read coordinates from.

Return type:

None

set_atom_data(key, values=None, *, by_species=None, by_index=None)[source]

Set per-atom metadata for colourmap-based rendering.

Canonical write entry point for per-atom metadata. The container is otherwise read-only: to remove a single entry use del_atom_data(), and to bulk-drop all 2-D entries (for example after extending the trajectory) use clear_2d_atom_data().

Provide data in one of two forms:

  • Full array via values: a 1-D array-like of length n_atoms (same value every frame) or a 2-D array-like of shape (n_frames, n_atoms) (per-frame values).

  • Sparse via by_species and/or by_index: maps species labels or atom indices to values. See below for shape rules and precedence.

Mixing values with by_species or by_index raises ValueError.

by_species maps species labels to values. Scalars broadcast to all atoms of the species; 1-D arrays (length = count of that species’ atoms) assign per-atom; 2-D arrays of shape (n_frames, n_species_atoms) assign per-frame. A 1-D array is always interpreted as static per-atom, even when its length equals n_frames.

by_index maps atom indices to values. Scalars are static; 1-D arrays of length n_frames are per-frame.

When both are provided, by_index values take precedence over by_species at overlapping atoms.

Unspecified atoms are filled with NaN (numeric) or None (categorical, stored as object-dtype).

A 2-D values array, or any by_* form that promotes to 2-D, is validated against the container’s prospective post-write state: the array’s frame count must match len(self.frames).

Parameters:
  • key (str) – Name for this metadata (e.g. "charge", "site").

  • values (TypeAliasType | None (default: None)) – Full-length array-like. Must not be a dict; use by_index for sparse assignment by atom index.

  • by_species (dict[str, object] | None (default: None)) – Maps species labels to scalar, 1-D, or 2-D values. All keys must be present in scene.species.

  • by_index (dict[int, object] | None (default: None)) – Maps atom indices to scalar or 1-D values. All keys must be in range(len(scene.species)).

Raises:
  • ValueError – If values is mixed with by_species or by_index; if all three are absent; if a species label is unknown; if an atom index is out of range; if an array has the wrong shape for its context; or if a 2-D array’s frame count does not match len(self.frames).

  • TypeError – If a dict is passed as values (use by_index= instead), or if values contain a mixture of string and numeric types.

Return type:

None

See also

del_atom_data(): Remove a single entry. clear_2d_atom_data(): Remove all 2-D entries.

Return type:

None

Parameters:
del_atom_data(key)[source]

Remove a per-atom metadata entry.

Parameters:

key (str) – The metadata key to remove.

Raises:

KeyError – If key is not present in atom_data.

Return type:

None

See also

set_atom_data(): Canonical write entry point. clear_2d_atom_data(): Remove all 2-D entries at once.

Return type:

None

Parameters:

key (str)

clear_2d_atom_data()[source]

Remove all 2-D per-atom metadata entries, preserving 1-D.

Required when two or more 2-D entries are stored and the trajectory has been extended: every stored 2-D entry is now stale relative to len(self.frames), so each must be replaced before the next render. For scenes with a single 2-D entry, set_atom_data() can reassign the key directly at the new shape – the stored version is treated as overridden by the pending write – and this method is unnecessary.

The multi-entry recovery workflow is: call this method, then re-assign each 2-D key via set_atom_data() at the new shape, then render.

See also

set_atom_data(): Canonical write entry point. del_atom_data(): Remove a single entry.

Return type:

None

select_by_species(values, species)[source]

Keep values for selected species, fill the rest with sentinels.

Returns a copy of values with entries for non-selected atoms replaced by the appropriate missing sentinel: NaN for numeric data (with integer-to-float promotion) or None for categorical data (with unicode-to-object promotion).

Intended for filtering a full-length array before passing it to set_atom_data():

scene.set_atom_data(
    "charge",
    scene.select_by_species(full_array, "O"),
)
Parameters:
  • values (TypeAliasType) – Array-like of shape (n_atoms,) or (n_frames, n_atoms).

  • species (str | Iterable[str]) – A single species label or an iterable of labels to keep.

Return type:

ndarray

Returns:

A new array with the same shape as values.

Raises:

ValueError – If species contains unknown labels or if values has the wrong shape.

render_mpl(output=None, *, ax=None, style=None, frame_index=0, figsize=(5.0, 5.0), dpi=150, background='white', show=None, colour_by=None, cmap='viridis', colour_range=None, **style_kwargs)[source]

Render the scene as a static matplotlib figure.

Parameters:
  • output (str | Path | None (default: None)) – Optional file path to save the figure. The format is inferred from the extension (.svg, .pdf, .png). Ignored when ax is provided.

  • ax (Axes | None (default: None)) – Optional matplotlib Axes to draw into. When provided, the caller is responsible for saving and closing the figure. The output, figsize, dpi, background, and show parameters are ignored.

  • style (RenderStyle | None (default: None)) – A RenderStyle controlling visual appearance. Any RenderStyle field name may also be passed as a keyword argument to override individual fields.

  • frame_index (int (default: 0)) – Which frame to render (default 0).

  • figsize (tuple[float, float] (default: (5.0, 5.0))) – Figure size in inches (width, height).

  • dpi (int (default: 150)) – Resolution for raster output formats.

  • background (str | float | tuple[float, float, float] | list[float] (default: 'white')) – Background colour.

  • show (bool | None (default: None)) – Whether to call plt.show(). Defaults to True when output is None, False when saving to a file.

  • colour_by (str | list[str] | None (default: None)) – Key (or list of keys) into atom_data to colour atoms by. When None (the default), species-based colouring is used. When a list, layers are tried in priority order and the first non-missing value determines the atom’s colour.

  • cmap (str | Callable[[float], Sequence[float]] | list[str | Callable[[float], Sequence[float]]] (default: 'viridis')) – A CmapSpec: matplotlib colourmap name (e.g. "viridis"), Colormap object, or callable mapping a float in [0, 1] to an (r, g, b) tuple. When colour_by is a list, cmap may also be a list of the same length (one per layer).

  • colour_range (tuple[float, float] | None | list[tuple[float, float] | None] (default: None)) – Explicit (vmin, vmax) for normalising numerical data. None auto-ranges from the data. When colour_by is a list, may also be a list of the same length.

  • **style_kwargs (object) – Any RenderStyle field name as a keyword argument (e.g. show_bonds=False).

Return type:

Figure

Returns:

The matplotlib Figure.

render_mpl_interactive(*, style=None, frame_index=0, figsize=(5.0, 5.0), dpi=150, background='white', colour_by=None, cmap='viridis', colour_range=None, **style_kwargs)[source]

Open an interactive matplotlib viewer with mouse and keyboard controls.

Left-drag rotates, scroll zooms, and keyboard shortcuts control rotation, pan, perspective, display toggles, and frame navigation. Press h to show a help overlay listing all keybindings.

When the window is closed the updated ViewState and RenderStyle are returned so they can be reused for static rendering:

view, style = scene.render_mpl_interactive()
scene.view = view
scene.render_mpl("output.svg", style=style)
Parameters:
Return type:

tuple[ViewState, RenderStyle]

Returns:

A (ViewState, RenderStyle) tuple reflecting any view and style changes applied during the interactive session.

render_animation(output, *, style=None, frames=None, fps=30, figsize=(5.0, 5.0), dpi=150, background='white', colour_by=None, cmap='viridis', colour_range=None, **style_kwargs)[source]

Render a trajectory animation to a GIF or MP4 file.

Loops over the specified frames, rendering each with the per-frame pipeline and writing it to the output file.

Parameters:
Return type:

Path

Returns:

The output file path as a Path.

Raises:
  • ValueError – If frames is empty or contains out-of-range indices, if fps is less than 1, or if output has an unsupported file extension (must be .gif or .mp4).

  • ImportError – If imageio is not installed.

See also

hofmann.rendering.animation.render_animation()

hofmann.from_xbs(bs_path, mv_path=None)[source]

Create a StructureScene from XBS .bs (and optional .mv) files.

Parameters:
  • bs_path (str | Path) – Path to the .bs file.

  • mv_path (str | Path | None (default: None)) – Optional path to a .mv trajectory file.

Return type:

StructureScene

Returns:

A fully configured StructureScene.

hofmann.from_ase(atoms, bond_specs=None, *, polyhedra=None, centre_atom=None, atom_styles=None, title='', view=None, atom_data=None)[source]

Create a StructureScene from ASE Atoms object(s).

For periodic systems (where atoms.pbc is set and the cell is non-degenerate), fractional coordinates are wrapped to [0, 1) and stored as Cartesian coordinates, following the same approach as from_pymatgen(). For non-periodic systems, Cartesian positions are stored directly and lattice is None.

Parameters:
  • atoms (Atoms | Sequence[Atoms]) – A single ASE Atoms object or a sequence of Atoms (e.g. from an MD trajectory or ase.io.Trajectory).

  • bond_specs (list[BondSpec] | None (default: None)) – Bond detection rules. None generates sensible defaults from VESTA bond length cutoffs; pass an empty list to disable bonds.

  • polyhedra (list[PolyhedronSpec] | None (default: None)) – Polyhedron rendering rules. None disables polyhedra.

  • centre_atom (int | None (default: None)) – Index of the atom to centre the unit cell on. Fractional coordinates are shifted so this atom sits at (0.5, 0.5, 0.5). Only valid for periodic systems. If view is also provided, the explicit view takes precedence and only the fractional-coordinate shift is applied.

  • atom_styles (dict[str, AtomStyle] | None (default: None)) – Per-species style overrides. When provided, these are merged on top of the auto-generated defaults so you only need to specify the species you want to customise.

  • title (str (default: '')) – Scene title for display.

  • view (ViewState | None (default: None)) – Camera / projection state. When None (the default), the view is auto-centred on the centre atom (if set) or the centroid of all atoms.

  • atom_data (dict[str, TypeAliasType] | None (default: None)) – Per-atom metadata arrays, keyed by name.

Return type:

StructureScene

Returns:

A StructureScene with default element styles.

Raises:
  • ImportError – If ASE is not installed.

  • ValueError – If atoms is an empty sequence, if centre_atom is out of range, if centre_atom is used with a non-periodic system, or if frames in a trajectory have inconsistent species, atom counts, or periodicity.

hofmann.from_pymatgen(structure, bond_specs=None, *, polyhedra=None, centre_atom=None, atom_styles=None, title='', view=None, atom_data=None)[source]

Create a StructureScene from pymatgen Structure(s).

Fractional coordinates are wrapped to [0, 1) and stored as Cartesian coordinates. Periodic boundary handling (PBC bond computation, image-atom expansion, recursive depth, molecule deduplication) is controlled at render time via RenderStyle.

Parameters:
  • structure (Structure | Sequence[Structure]) – A single pymatgen Structure or a list of Structure objects (e.g. from an MD trajectory).

  • bond_specs (list[BondSpec] | None (default: None)) – Optional bond specification rules. If None, sensible defaults are generated from VESTA bond length cutoffs. Pass an empty list to disable bonds.

  • polyhedra (list[PolyhedronSpec] | None (default: None)) – Optional polyhedron rendering rules. If None, no polyhedra are drawn.

  • centre_atom (int | None (default: None)) – Index of the atom to centre the unit cell on. When set, all fractional coordinates are shifted so that this atom sits at (0.5, 0.5, 0.5), and the view is centred on this atom. If view is also provided, the explicit view takes precedence and only the fractional- coordinate shift is applied.

  • atom_styles (dict[str, AtomStyle] | None (default: None)) – Per-species style overrides. When provided, these are merged on top of the auto-generated defaults so you only need to specify the species you want to customise.

  • title (str (default: '')) – Scene title for display.

  • view (ViewState | None (default: None)) – Camera / projection state. When None (the default), the view is auto-centred on the structure.

  • atom_data (dict[str, TypeAliasType] | None (default: None)) – Per-atom metadata arrays, keyed by name.

Return type:

StructureScene

Returns:

A StructureScene with default element styles.

Raises:

ImportError – If pymatgen is not installed.

Data model

class hofmann.Frame[source]

A single snapshot of atomic coordinates.

coords

Cartesian coordinates, shape (n_atoms, 3).

lattice

Unit-cell matrix, shape (3, 3) with rows as lattice vectors, or None for non-periodic structures.

label

Optional frame label or identifier.

Raises:
  • ValueError – If coords does not have shape (n_atoms, 3).

  • ValueError – If lattice is not None and does not have shape (3, 3).

Parameters:
__init__(coords, lattice=None, label='')
Parameters:
Return type:

None

class hofmann.AtomStyle[source]

Visual style for an atomic species.

radius

Display radius in angstroms. Typical values range from about 0.5 (hydrogen) to 2.0 (heavy metals). See COVALENT_RADII for physically motivated starting points.

colour

Fill colour specification (CSS name, hex string, grey float, or RGB tuple/list). See Colour.

visible

Whether atoms of this species are drawn. Set to False to hide atoms without removing them from the scene. Bonds to hidden atoms are also suppressed. This flag has no effect on sites occupied by a Composition: mixed sites always render every constituent regardless of any constituent’s visible flag.

Parameters:
to_dict()[source]

Serialise to a JSON-compatible dictionary.

Colours are normalised to [r, g, b] lists. The visible field is omitted when True (the default).

Return type:

dict

classmethod from_dict(d)[source]

Deserialise from a dictionary.

Accepts any colour format understood by normalise_colour().

Parameters:

d (dict)

Return type:

AtomStyle

__init__(radius, colour, visible=True)
Parameters:
Return type:

None

class hofmann.Composition[source]

Species-to-occupancy mapping for a (possibly mixed) site.

A frozen, hashable Mapping[str, float] describing how a site is occupied: a single species at full occupancy (a “pure” site, also expressible as a plain str), a weighted mix of species (a “mixed” site), or any of the above with an implicit vacancy fraction (1 - sum(occupancies)).

Validated at construction:

  • All occupancy values must be finite and lie in [0, 1]. Zero values are dropped before further validation; negative or non-finite values raise.

  • The occupancy sum must not exceed 1.0 (within a tolerance of 1e-9). Any deficit is interpreted as a vacancy fraction.

  • Keys must be non-empty strings.

Iteration order is canonical: descending occupancy, with alphabetical tiebreak. This ordering determines wedge layout in the renderer, so visual reproducibility is guaranteed across runs.

Parameters:

occupancies (Mapping[str, float]) – A mapping of species labels to occupancy fractions.

Raises:
  • ValueError – If any value is non-finite or outside [0, 1]; if the sum exceeds 1.0; if the resulting mapping is empty (all values zero, or the input was empty).

  • TypeError – If any key is not a string.

property species: frozenset[str]

Set of constituent species (vacancy excluded).

property dominant_species: str

alphabetical.

Type:

Species with the highest occupancy. Tiebreak

property vacancy: float

1 - sum(occupancies), clamped to [0, 1).

Type:

Vacancy fraction

__init__(occupancies)
Parameters:

occupancies (Mapping[str, float])

Return type:

None

class hofmann.BondSpec[source]

Declarative rule for bond detection between species pairs.

The species pair is stored in sorted order so that the data structure is invariant under exchange of the two labels.

Species names support fnmatch-style wildcards (*, ?).

Only species and max_length are required. radius and colour default to None, meaning “use the class-level default” (BondSpec.default_radius and BondSpec.default_colour). The resolved value is returned by the corresponding property; repr() shows radius=<default 0.1> when unset so the intent is visible.

To change the defaults for all specs that have not been explicitly set:

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

Sorted pair of species patterns.

max_length

Maximum bond length threshold.

min_length

Minimum bond length threshold. Defaults to 0.0.

complete

Controls single-pass bond completion across periodic boundaries. A species string (e.g. "Zr") adds missing partners around visible atoms of that species. "*" completes around both species in the pair. False (default) disables completion. Unlike recursive, newly added atoms are not themselves searched.

recursive

If True, atoms bonded via this spec are searched recursively across periodic boundaries. When an image atom is materialised, its own bonded partners are checked on the next iteration, extending the expansion outward. Useful for molecules that span periodic boundaries.

Parameters:
default_radius: ClassVar[float] = 0.1

Class-level default for radius when not set explicitly.

default_colour: ClassVar[str | float | tuple[float, float, float] | list[float]] = 0.5

Class-level default for colour when not set explicitly.

__init__(species, max_length, min_length=0.0, radius=None, colour=None, complete=False, recursive=False)[source]
Parameters:
Return type:

None

property radius: float

Visual radius of the bond cylinder.

property colour: str | float | tuple[float, float, float] | list[float]

Bond colour (resolved from class default when not set explicitly).

matches(species_1, species_2)[source]

Check whether this spec matches a given species pair.

Matching is symmetric: BondSpec(("C", "H"), ...).matches("H", "C") returns True.

Parameters:
  • species_1 (str) – First species label.

  • species_2 (str) – Second species label.

Return type:

bool

Returns:

True if the pair matches in either order.

to_dict()[source]

Serialise to a JSON-compatible dictionary.

Fields at their default values are omitted (min_length=0, complete=False, recursive=False). When radius or colour use the class-level default (i.e. were not set explicitly), they are omitted too.

Return type:

dict

classmethod from_dict(d)[source]

Deserialise from a dictionary.

Missing optional fields use their defaults.

Parameters:

d (dict)

Return type:

BondSpec

class hofmann.Bond[source]

A computed bond between two atoms.

index_a

Index of the first atom.

index_b

Index of the second atom.

length

Interatomic distance.

spec

The BondSpec rule that produced this bond.

image

Lattice translation applied to atom b to form the bond. (0, 0, 0) means a direct bond within the cell. (1, 0, 0) means atom b is shifted by +1 along lattice vector a. Always (0, 0, 0) for non-periodic scenes.

Parameters:
__init__(index_a, index_b, length, spec, image=(0, 0, 0))
Parameters:
Return type:

None

class hofmann.PolyhedronSpec[source]

Declarative rule for rendering coordination polyhedra.

A polyhedron is drawn around each atom whose species matches centre, using its bonded neighbours as vertices of a convex hull. Species names support fnmatch-style wildcards.

centre

Species pattern for the centre atom (e.g. "Ti").

colour

Face colour, or None to inherit from the centre atom’s style colour.

alpha

Face transparency (0 = fully transparent, 1 = opaque).

edge_colour

Colour for face wireframe edges.

edge_width

Line width for face wireframe edges (points).

hide_centre

Whether to hide the centre atom circle when a polyhedron is drawn.

hide_bonds

Whether to hide bonds from the centre atom to its coordinating neighbours when a polyhedron is drawn.

hide_vertices

Whether to hide the vertex atom circles. An atom is only hidden if every polyhedron it participates in has hide_vertices=True.

min_vertices

Minimum number of bonded neighbours required to draw a polyhedron. Centre atoms with fewer neighbours are skipped. None uses the default minimum of 3.

Parameters:
to_dict()[source]

Serialise to a JSON-compatible dictionary.

Fields at their default values are omitted. Colours are normalised to [r, g, b] lists.

Return type:

dict

classmethod from_dict(d)[source]

Deserialise from a dictionary.

Parameters:

d (dict)

Return type:

PolyhedronSpec

__init__(centre, colour=None, alpha=0.4, edge_colour=(0.15, 0.15, 0.15), edge_width=1.0, hide_centre=False, hide_bonds=False, hide_vertices=False, min_vertices=None)
Parameters:
Return type:

None

class hofmann.Polyhedron[source]

A computed coordination polyhedron.

centre_index

Index of the centre atom.

neighbour_indices

Indices of the coordinating atoms.

faces

List of faces, each a 1-D array of vertex indices into neighbour_indices. Triangular faces have length 3; merged coplanar faces may have 4 or more vertices.

spec

The PolyhedronSpec that produced this polyhedron.

Parameters:
__init__(centre_index, neighbour_indices, faces, spec)
Parameters:
Return type:

None

class hofmann.ViewState[source]

Camera state for 3D-to-2D projection.

Encapsulates rotation, zoom, centring, and optional perspective projection. Renderers consume the projected 2D coordinates and depth values produced by project().

Depth-slab clipping is controlled by slab_near, slab_far, and slab_origin. When set, only atoms whose depth (along the viewing direction) falls within the range [origin_depth + slab_near, origin_depth + slab_far] are rendered. If slab_origin is None, the slab is centred on centre.

rotation

3x3 rotation matrix.

zoom

Magnification factor.

centre

3D point about which to centre the view.

perspective

Perspective strength (0 = orthographic).

view_distance

Distance from camera to scene centre.

slab_origin

3D point defining the slab reference depth, or None to use centre.

slab_near

Near offset from the slab origin depth (negative = further from camera), or None for no near limit.

slab_far

Far offset from the slab origin depth (positive = closer to camera), or None for no far limit.

Parameters:
  • rotation (ndarray (default: <factory>))

  • zoom (float (default: 1.0))

  • centre (ndarray (default: <factory>))

  • perspective (float (default: 0.0))

  • view_distance (float (default: 10.0))

  • slab_origin (ndarray | None (default: None))

  • slab_near (float | None (default: None))

  • slab_far (float | None (default: None))

project(coords, radii=None)[source]

Project 3D coordinates to 2D with depth information.

The eye sits at [0, 0, view_distance] and each sphere’s visible silhouette is projected onto the z=0 plane.

Parameters:
  • coords (ndarray) – Array of shape (n, 3).

  • radii (ndarray | None (default: None)) – Optional array of shape (n,) giving 3D sphere radii. When provided the returned projected_radii are the screen-space silhouette radii; otherwise zeros.

Returns:

  • xy: (n, 2) projected 2D coordinates.

  • depth: (n,) depth values (larger = closer to viewer).

  • projected_radii: (n,) screen-space sphere radii.

Return type:

tuple[ndarray, ndarray, ndarray]

slab_mask(coords)[source]

Return a boolean mask selecting atoms within the depth slab.

If neither slab_near nor slab_far is set, all atoms are selected. The depth of each atom is measured along the current viewing direction, relative to the slab origin (or centre if no origin is set).

Parameters:

coords (ndarray) – World-space coordinates, shape (n, 3).

Return type:

ndarray

Returns:

Boolean array of shape (n,).

look_along(direction, *, up=(0.0, 1.0, 0.0))[source]

Set the rotation so the camera looks along direction.

The view is oriented so that direction points into the screen (along +z in camera space). The up vector determines which way is “up” on screen.

This is equivalent to placing the camera at a point along direction looking back towards the origin.

Returns self so callers can chain, e.g.:

scene.view = ViewState(centre=centroid).look_along([1, 1, 1])
Parameters:
  • direction (ndarray | list[float] | tuple[float, ...]) – 3D vector giving the viewing direction (from the camera towards the scene). Need not be normalised.

  • up (ndarray | list[float] | tuple[float, ...] (default: (0.0, 1.0, 0.0))) – 3D vector indicating the upward direction in screen space. Defaults to [0, 1, 0].

Return type:

ViewState

Returns:

self, with the rotation updated in place.

Raises:

ValueError – If direction is zero-length or up is parallel to direction.

__init__(rotation=<factory>, zoom=1.0, centre=<factory>, perspective=0.0, view_distance=10.0, slab_origin=None, slab_near=None, slab_far=None)
Parameters:
  • rotation (ndarray (default: <factory>))

  • zoom (float (default: 1.0))

  • centre (ndarray (default: <factory>))

  • perspective (float (default: 0.0))

  • view_distance (float (default: 10.0))

  • slab_origin (ndarray | None (default: None))

  • slab_near (float | None (default: None))

  • slab_far (float | None (default: None))

Return type:

None

class hofmann.model.atom_data.AtomData[source]

Per-atom metadata container.

The supported way to obtain an AtomData is via atom_data; the class is not re-exported from hofmann or hofmann.model, and direct construction is considered an internal implementation detail. User-facing access goes through atom_data for reads, set_atom_data(), del_atom_data(), and clear_2d_atom_data() for writes.

Stores named per-atom arrays. Each value is either a 1-D array of shape (n_atoms,) (static across the trajectory) or a 2-D array of shape (m, n_atoms) where m is the trajectory length the caller declares at write time via the expected_frames kwarg on _set. All stored 2-D entries in a single container must share the same m; this cross- entry invariant is enforced at assignment.

The container holds no cached frame count between calls; each _set is told expected_frames by the caller, and the invariant is re-derived from the stored data on every call. Frame consistency is enforced at two sites:

  • At assignment, via _set calling _check_2d_consistency with pending={key: arr}, validating the prospective post-write state against expected_frames. Both the incoming array and any already-stored 2-D entries not being overridden are checked.

  • At render, via render_mpl() (and friends) calling the scene’s private _validate_for_render helper, which in turn calls _check_2d_consistency with no pending. This validates the current stored state against len(scene.frames) as a backstop that catches the specific case where scene.frames is mutated after the last write but before the next render.

Inherits from collections.abc.Mapping (not MutableMapping). Mutation goes through the private _set, _del, and _clear_2d methods; no ad[key] = value or del ad[key] shortcut exists. Assigned values are always copied via numpy.array() – including existing numpy arrays – so the container owns the buffer and the caller’s source array is left untouched.

Note

Stored arrays are returned read-only. In-place mutation (e.g. ad["charge"][0] = 99) raises ValueError: assignment destination is read-only. To update values, pass a fresh array through set_atom_data(), which re-validates the shape and recomputes the ranges and labels entries for the key. Only the array buffer is frozen – for object-dtype arrays, any mutable objects stored inside remain mutable.

n_atoms

The number of atoms the container was built for. Fixed at construction and not mutable.

ranges

Read-only mapping of keys to (min, max) tuples for 2-D numeric arrays, or None for keys that do not have a meaningful numeric range (1-D arrays, categorical arrays, empty arrays, all-NaN numeric arrays). Entries are added on assignment, replaced on reassignment, and removed on deletion.

labels

Read-only mapping of keys to tuples of unique non-missing categorical labels, or None for keys without a meaningful label set (1-D arrays, numeric dtypes, categorical arrays with no non-missing values). Missing values (None, "", NaN) are excluded from the label set. Entries are added on assignment, replaced on reassignment, and removed on deletion.

For 2-D arrays, ranges is populated for numeric dtypes and labels for categorical dtypes; the other side is always None. Either side may itself be None for empty arrays or arrays containing only missing values. For 1-D arrays, both are None.

Parameters:

n_atoms (int) – Number of atoms in the scene. Non-negative.

Raises:

ValueError – If n_atoms is negative.

__init__(*, n_atoms)[source]
Parameters:

n_atoms (int)

Return type:

None

classmethod __new__(*args, **kwargs)

Rendering

class hofmann.RenderStyle[source]

Visual style settings for rendering.

Groups all appearance parameters that control how a scene is drawn, independent of the scene data itself. A default RenderStyle() gives the standard ball-and-stick look.

Pass a style to render_mpl() via the style keyword, or override individual fields with convenience kwargs:

style = RenderStyle(show_outlines=False, atom_scale=0.8)
scene.render_mpl("out.svg", style=style)

# Or override a single field:
scene.render_mpl("out.svg", show_bonds=False)
atom_scale

Scale factor for atom display radii. 0.5 gives ball-and-stick; 1.0 gives space-filling.

bond_scale

Scale factor for bond cylinder radii.

bond_colour

Override colour for all bonds, or None to use per-spec / half-bond colouring.

half_bonds

Split each bond at the midpoint and colour halves to match the nearest atom.

show_bonds

Whether to draw bonds at all.

show_polyhedra

Whether to draw coordination polyhedra.

show_outlines

Whether to draw outlines around atoms and bonds.

outline_colour

Colour for outlines when show_outlines is True.

atom_outline_width

Line width for atom outlines (points). Applies uniformly to pure-string sites and to the outer outline and radial wedge separators of mixed sites.

bond_outline_width

Line width for bond outlines (points).

slab_clip_mode

How slab clipping affects polyhedra at the boundary. "per_face" drops individual faces with out-of-slab vertices (default), "clip_whole" hides the entire polyhedron if any vertex is clipped, and "include_whole" forces the complete polyhedron to be visible when its centre atom is in the slab.

circle_segments

Number of line segments used to approximate atom circles in static output. Higher values give smoother circles in vector output (PDF/SVG). The default (72) gives publication-quality output.

arc_segments

Number of line segments per semicircular bond end-cap in static output. Higher values give smoother bond ends in vector output. The default (12) gives publication-quality output.

interactive_circle_segments

Number of line segments for atom circles in the interactive viewer. Lower values give faster redraws. The default (24) balances quality and responsiveness.

interactive_arc_segments

Number of line segments per bond end-cap in the interactive viewer. Lower values give faster redraws. The default (5) balances quality and responsiveness.

polyhedra_shading

Strength of diffuse shading on polyhedra faces. 0.0 gives flat colouring (no shading); 1.0 (the default) gives full Lambertian-style shading where faces pointing at the viewer are bright and edge-on faces are dimmed.

light_direction

Direction of the virtual light source for polyhedra face shading, in screen space (x = right, y = up, z = towards viewer). Normalised internally before use. The zero vector is rejected.

polyhedra_outline_width

Global override for polyhedra outline line width (points). When None (the default), each polyhedron uses its own PolyhedronSpec.edge_width. When set, overrides all per-spec values.

show_cell

Whether to draw unit cell edges. None (the default) auto-detects: edges are drawn when the scene has a lattice. True forces drawing (raises ValueError at render time if no lattice is available). False suppresses drawing.

cell_style

Visual style for unit cell edges. See CellEdgeStyle.

show_axes

Whether to draw the crystallographic axes orientation widget. None (the default) auto-detects: the widget is drawn when the scene has a lattice. True forces drawing (raises ValueError at render time if no lattice is available). False suppresses drawing.

axes_style

Visual style for the axes widget. See AxesStyle.

show_legend

Whether to draw the species legend. False (the default) suppresses drawing. True draws a legend showing each visible species with its colour.

legend_style

Visual style for the legend widget. See LegendStyle.

pbc

Whether to use the lattice for periodic bond computation and image-atom expansion. Only meaningful when the scene has a lattice. Set to False to disable all periodic boundary handling and render only the physical atoms with Euclidean bond detection.

pbc_padding

Cartesian margin (angstroms) for geometric cell-face expansion. Atoms within this distance of a unit cell face are duplicated on the opposite side, producing an expanded view of the structure. None disables geometric expansion. The default of 0.1 angstroms gives a thin shell that catches atoms sitting exactly on cell edges.

max_recursive_depth

Maximum iterations for recursive bond expansion. Only relevant when one or more bond_specs have recursive=True. Must be >= 1.

deduplicate_molecules

Whether to remove duplicate molecular fragments that span cell boundaries. When True, each molecule appears only once, keeping the largest connected cluster.

Raises:

ValueError – If atom_scale or bond_scale are not positive, max_recursive_depth is less than 1, atom_outline_width or bond_outline_width are negative, circle_segments or interactive_circle_segments < 3, arc_segments or interactive_arc_segments < 2, polyhedra_shading is outside [0, 1], light_direction does not have exactly 3 components or is the zero vector, or polyhedra_outline_width is negative.

Parameters:
  • atom_scale (float (default: 0.5))

  • bond_scale (float (default: 1.0))

  • bond_colour (str | float | tuple[float, float, float] | list[float] | None (default: None))

  • half_bonds (bool (default: True))

  • show_bonds (bool (default: True))

  • show_polyhedra (bool (default: True))

  • show_outlines (bool (default: True))

  • outline_colour (str | float | tuple[float, float, float] | list[float] (default: (0.15, 0.15, 0.15)))

  • atom_outline_width (float (default: 1.0))

  • bond_outline_width (float (default: 1.0))

  • wedge_start_angle (float (default: 1.5707963267948966))

  • vacancy_colour (str | float | tuple[float, float, float] | list[float] | None (default: None))

  • show_wedge_edges (bool (default: True))

  • slab_clip_mode (SlabClipMode (default: <SlabClipMode.PER_FACE: 'per_face'>))

  • circle_segments (int (default: 72))

  • arc_segments (int (default: 12))

  • interactive_circle_segments (int (default: 24))

  • interactive_arc_segments (int (default: 5))

  • polyhedra_shading (float (default: 1.0))

  • light_direction (tuple[float, float, float] (default: (0.0, 0.0, 1.0)))

  • polyhedra_outline_width (float | None (default: None))

  • show_cell (bool | None (default: None))

  • cell_style (CellEdgeStyle (default: <factory>))

  • show_axes (bool | None (default: None))

  • axes_style (AxesStyle (default: <factory>))

  • show_legend (bool (default: False))

  • legend_style (LegendStyle (default: <factory>))

  • pbc (bool (default: True))

  • pbc_padding (float | None (default: 0.1))

  • max_recursive_depth (int (default: 5))

  • deduplicate_molecules (bool (default: False))

wedge_start_angle: float = 1.5707963267948966

Starting angle for mixed-site pie wedges (radians).

Default pi / 2 (12 o’clock). Applied globally to all mixed sites in the scene.

vacancy_colour: str | float | tuple[float, float, float] | list[float] | None = None

Fill colour for the vacancy fraction of a partially occupied site.

None (the default) fills the vacancy wedge with the canvas background colour, so partial sites read as opaque circles with one slice “missing”. Set to an explicit colour to make the vacancy stand out against the background (for example, "lightgrey" on a white canvas).

show_wedge_edges: bool = True

Whether to draw radial separators between wedges of a mixed site.

Default True draws thin radial lines between adjacent wedges, including at the vacancy boundary. Set to False to render mixed sites as seamless pies bounded only by the outer arc.

Both the outer arc and the radial separators are stroked at atom_outline_width points, so mixed-site outlines have the same visual weight as pure-circle outlines.

__init__(atom_scale=0.5, bond_scale=1.0, bond_colour=None, half_bonds=True, show_bonds=True, show_polyhedra=True, show_outlines=True, outline_colour=(0.15, 0.15, 0.15), atom_outline_width=1.0, bond_outline_width=1.0, wedge_start_angle=1.5707963267948966, vacancy_colour=None, show_wedge_edges=True, slab_clip_mode=SlabClipMode.PER_FACE, circle_segments=72, arc_segments=12, interactive_circle_segments=24, interactive_arc_segments=5, polyhedra_shading=1.0, light_direction=(0.0, 0.0, 1.0), polyhedra_outline_width=None, show_cell=None, cell_style=<factory>, show_axes=None, axes_style=<factory>, show_legend=False, legend_style=<factory>, pbc=True, pbc_padding=0.1, max_recursive_depth=5, deduplicate_molecules=False)
Parameters:
  • atom_scale (float (default: 0.5))

  • bond_scale (float (default: 1.0))

  • bond_colour (str | float | tuple[float, float, float] | list[float] | None (default: None))

  • half_bonds (bool (default: True))

  • show_bonds (bool (default: True))

  • show_polyhedra (bool (default: True))

  • show_outlines (bool (default: True))

  • outline_colour (str | float | tuple[float, float, float] | list[float] (default: (0.15, 0.15, 0.15)))

  • atom_outline_width (float (default: 1.0))

  • bond_outline_width (float (default: 1.0))

  • wedge_start_angle (float (default: 1.5707963267948966))

  • vacancy_colour (str | float | tuple[float, float, float] | list[float] | None (default: None))

  • show_wedge_edges (bool (default: True))

  • slab_clip_mode (SlabClipMode (default: <SlabClipMode.PER_FACE: 'per_face'>))

  • circle_segments (int (default: 72))

  • arc_segments (int (default: 12))

  • interactive_circle_segments (int (default: 24))

  • interactive_arc_segments (int (default: 5))

  • polyhedra_shading (float (default: 1.0))

  • light_direction (tuple[float, float, float] (default: (0.0, 0.0, 1.0)))

  • polyhedra_outline_width (float | None (default: None))

  • show_cell (bool | None (default: None))

  • cell_style (CellEdgeStyle (default: <factory>))

  • show_axes (bool | None (default: None))

  • axes_style (AxesStyle (default: <factory>))

  • show_legend (bool (default: False))

  • legend_style (LegendStyle (default: <factory>))

  • pbc (bool (default: True))

  • pbc_padding (float | None (default: 0.1))

  • max_recursive_depth (int (default: 5))

  • deduplicate_molecules (bool (default: False))

Return type:

None

to_dict()[source]

Serialise to a JSON-compatible dictionary.

Fields at their default values are omitted. Nested cell_style, axes_style, and legend_style are serialised as sub-dicts (omitted entirely when they equal their own defaults).

Return type:

dict

classmethod from_dict(d)[source]

Deserialise from a dictionary.

Missing fields use their defaults. The slab_clip_mode string is coerced to SlabClipMode and bond_colour lists are converted to tuples for type consistency.

Parameters:

d (dict)

Return type:

RenderStyle

class hofmann.CellEdgeStyle[source]

Visual style for unit cell edges.

colour

Edge colour. Accepts any format understood by normalise_colour().

line_width

Width of the edge line in display units.

linestyle

Line pattern: "solid", "dashed", "dotted", or "dashdot".

Parameters:
to_dict()[source]

Serialise to a JSON-compatible dictionary.

Fields at their default values are omitted.

Return type:

dict

classmethod from_dict(d)[source]

Deserialise from a dictionary.

Parameters:

d (dict)

Return type:

CellEdgeStyle

__init__(colour=(0.3, 0.3, 0.3), line_width=0.8, linestyle='solid')
Parameters:
Return type:

None

class hofmann.AxesStyle[source]

Visual style for the crystallographic axes orientation widget.

The widget draws three axis lines (a, b, c lattice directions) from a common origin in a corner of the viewport. Lines rotate in sync with the structure, with italic labels at the tips.

colours

Tuple of three colours for the (a, b, c) axes. Each element accepts any format understood by normalise_colour(). Defaults to uniform dark grey. Pass distinct colours for per-axis colouring.

labels

Tuple of three label strings for the axes.

font_size

Font size for axis labels in points.

italic

Whether to render labels in italic (crystallographic convention).

arrow_length

Axis line length as a fraction of the viewport half-extent.

line_width

Width of the axis lines in points.

corner

Widget origin position. Pass a WidgetCorner (or its string value) for automatic placement in one of the four viewport corners, offset by margin. Pass an (x, y) tuple of fractional viewport coordinates (0.0 = left/bottom, 1.0 = right/top) for an explicit position; margin is ignored in this case.

margin

Offset from the corner as a fraction of the viewport half-extent. Only used when corner is a WidgetCorner.

Parameters:
to_dict()[source]

Serialise to a JSON-compatible dictionary.

Fields at their default values are omitted.

Return type:

dict

classmethod from_dict(d)[source]

Deserialise from a dictionary.

Parameters:

d (dict)

Return type:

AxesStyle

__init__(colours=((0.3, 0.3, 0.3), (0.3, 0.3, 0.3), (0.3, 0.3, 0.3)), labels=('a', 'b', 'c'), font_size=10.0, italic=True, arrow_length=0.12, line_width=1.0, corner=WidgetCorner.BOTTOM_LEFT, margin=0.15)
Parameters:
Return type:

None

class hofmann.LegendItem[source]

Abstract base class for legend entries.

Concrete subclasses:

  • AtomLegendItem — circle marker (atoms).

  • PolygonLegendItem — regular-polygon marker.

  • PolyhedronLegendItem — miniature 3D polyhedron icon.

Shared fields live on the base class; marker-specific fields (sides, rotation, shape) live on the relevant subclass.

Parameters:
  • key (str) – Identifier for this legend entry. Also used as the default display label when label is None.

  • colour (str | float | tuple[float, float, float] | list[float]) – Fill colour for the legend marker. Accepts any format understood by normalise_colour(); the value is normalised to an (R, G, B) tuple on assignment.

  • label (str | None (default: None)) – Display label text. None falls back to key. Common chemical notation is auto-formatted at render time: trailing charges become superscripts, embedded digits become subscripts. Labels containing $ are passed through as explicit matplotlib mathtext.

  • radius (float | None (default: None)) – Marker radius in points (before display-space scaling). None falls back to LegendStyle.circle_radius when that is a plain float, or to its default value otherwise (the proportional and per-species dict modes do not apply to individual items).

  • gap_after (float | None (default: None)) – Vertical gap in points between this entry and the next one. None falls back to LegendStyle.spacing. Must be non-negative. Ignored for the final entry in the list.

  • alpha (float (default: 1.0)) – Opacity of the marker face, from 0.0 (fully transparent) to 1.0 (fully opaque, the default). Marker outlines are unaffected and remain fully opaque.

  • edge_colour (str | float | tuple[float, float, float] | list[float] | None (default: None)) – Override edge/outline colour for this item. None (the default) falls back to the scene-level outline colour. Accepts any format understood by normalise_colour().

  • edge_width (float | None (default: None)) – Override edge/outline width in points for this item. None (the default) falls back to the scene-level outline width. Must be non-negative.

__init__(key, colour, label=None, radius=None, gap_after=None, alpha=1.0, edge_colour=None, edge_width=None)[source]
Parameters:
Return type:

None

abstract property marker_type: str

Return the marker type identifier ("atom", "polygon", or "polyhedron").

property colour: tuple[float, float, float]

Fill colour for the legend marker (normalised RGB).

property radius: float | None

Marker radius in points, or None for the style default.

property gap_after: float | None

Gap in points after this entry, or None for the style default.

property alpha: float

Opacity of the marker face (0.0 = transparent, 1.0 = opaque).

property edge_colour: tuple[float, float, float] | None

Per-item edge colour (normalised RGB), or None for scene default.

property edge_width: float | None

Per-item edge width in points, or None for scene default.

property display_label: str

The label to display, falling back to key.

classmethod from_dict(d)[source]

Deserialise from a dictionary.

Dispatches to the correct subclass based on the "type" key. Dictionaries without a "type" key default to AtomLegendItem. Legacy dicts containing sides or polyhedron fields from 0.12.x are not inferred automatically and must be migrated.

Parameters:

d (dict)

Return type:

LegendItem

abstractmethod to_dict()[source]

Serialise to a JSON-compatible dictionary.

Return type:

dict

class hofmann.LegendStyle[source]

Visual style for the species legend widget.

The widget draws a vertical column of coloured circles with labels beside them. By default, entries are auto-generated from the scene’s species and atom styles. To display a fully custom legend (e.g. for colour_by data), pass a tuple of LegendItem instances via the items parameter — this bypasses species auto-generation entirely.

corner

Widget position. Pass a WidgetCorner (or its string value) for automatic placement in one of the four viewport corners, offset by margin. Pass an (x, y) tuple of fractional viewport coordinates (0.0 = left/bottom, 1.0 = right/top) for an explicit position; margin is ignored in this case.

margin

Offset from the corner as a fraction of the viewport half-extent. Only used when corner is a WidgetCorner.

font_size

Font size for species labels in points.

circle_radius

Controls the size of the coloured circles in points. Accepts three forms:

  • float — uniform radius for all entries (default 5.0).

  • tuple (min, max) — proportional sizing. Each species’ circle radius is linearly interpolated between min and max based on its AtomStyle.radius relative to the smallest and largest radii in the legend. When all atom radii are equal, max is used.

  • dict[str, float] — explicit per-species radii. Species not present in the dict use the class default (5.0 points).

spacing

Vertical gap between legend entries in points.

label_gap

Horizontal gap between the circle edge and the species label in points.

species

Explicit list of species to include, in display order. None (the default) auto-detects from the scene: unique species in first-seen order. Pure-string site rows contribute their species only when its AtomStyle.visible is True; species sourced from a Composition constituent are always included (mixed-site rendering ignores per-constituent visibility). Ignored when items is provided.

labels

Custom display labels for legend entries, mapping species name to label string. Common chemical notation is auto-formatted: trailing charges become superscripts ("Sr2+"), embedded digits become subscripts ("TiO6"). Labels containing $ are passed through as explicit matplotlib mathtext. None (the default) uses species names for all entries. Ignored when items is provided.

items

Explicit legend entries. When provided, the legend displays these items instead of auto-generating from species. species, labels, and the tuple/dict forms of circle_radius are all ignored. Items with radius=None fall back to circle_radius when that is a plain float, or to 5.0 points otherwise.

Parameters:
to_dict()[source]

Serialise to a JSON-compatible dictionary.

Fields at their default values are omitted.

Return type:

dict

classmethod from_dict(d)[source]

Deserialise from a dictionary.

Parameters:

d (dict)

Return type:

LegendStyle

__init__(corner=WidgetCorner.BOTTOM_RIGHT, margin=0.15, font_size=10.0, circle_radius=5.0, spacing=2.5, label_gap=5.0, species=None, labels=None, items=None)
Parameters:
Return type:

None

class hofmann.WidgetCorner[source]

Which corner of the viewport to place a widget.

Used by both AxesStyle (default BOTTOM_LEFT) and LegendStyle (default BOTTOM_RIGHT).

BOTTOM_LEFT

Bottom-left corner.

BOTTOM_RIGHT

Bottom-right corner.

TOP_LEFT

Top-left corner.

TOP_RIGHT

Top-right corner.

__new__(value)
class hofmann.SlabClipMode[source]

How slab clipping interacts with coordination polyhedra.

Controls whether polyhedra at the slab boundary are drawn partially, dropped entirely, or forced to be complete.

PER_FACE

Drop individual faces whose vertices lie outside the slab. May produce partial polyhedron fragments.

CLIP_WHOLE

If any vertex of a polyhedron is outside the slab, skip the entire polyhedron and its centre-to-vertex bonds.

INCLUDE_WHOLE

If the centre atom is inside the slab, force all vertices and bonds of the polyhedron to be visible regardless of slab depth.

__new__(value)
hofmann.rendering.static.render_mpl(scene, output=None, *, ax=None, style=None, frame_index=0, figsize=(5.0, 5.0), dpi=150, background='white', show=None, colour_by=None, cmap='viridis', colour_range=None, **style_kwargs)[source]

Render a StructureScene as a static matplotlib figure.

Uses a depth-sorted painter’s algorithm: atoms are sorted back-to-front, and each atom’s bonds are drawn just before the atom itself is painted. Bond-sphere intersections are computed in 3D and then projected to screen space.

Example usage:

scene = StructureScene.from_xbs("ch4.bs")

# Save to file (no interactive window):
scene.render_mpl("ch4.png")

# Publication-quality SVG with custom sizing:
scene.render_mpl("ch4.svg", figsize=(8, 8), dpi=300,
                 background="black")

# Interactive display (no file):
scene.render_mpl()

# Custom style with no outlines:
from hofmann import RenderStyle
style = RenderStyle(show_outlines=False, atom_scale=0.8)
scene.render_mpl("clean.svg", style=style)

# View along the [1, 1, 1] direction with a depth slab:
scene.view.look_along([1, 1, 1])
scene.view.slab_near = -2.0
scene.view.slab_far = 2.0
scene.render_mpl("slice.png")

# Colour by per-atom metadata:
scene.set_atom_data("charge", charges)
scene.render_mpl(colour_by="charge", cmap="coolwarm")

# Render into an existing axes for multi-panel figures:
import matplotlib.pyplot as plt
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(10, 5))
ax1.plot(x, y)
scene.render_mpl(ax=ax2)
fig.savefig("panel.pdf", bbox_inches="tight")
Parameters:
  • scene (StructureScene) – The StructureScene to render.

  • output (str | Path | None (default: None)) – Optional file path to save the figure. The format is inferred from the extension (e.g. .svg, .pdf, .png). Ignored when ax is provided.

  • ax (Axes | None (default: None)) – Optional matplotlib Axes to draw into. When provided, the scene is rendered onto this axes and the caller retains control of the parent figure (saving, display, layout). The caller is responsible for saving and closing the figure. The output, figsize, dpi, background, and show parameters are ignored.

  • style (RenderStyle | None (default: None)) – A RenderStyle controlling visual appearance. If None, defaults are used. Any RenderStyle field name may also be passed as a keyword argument to override individual fields (e.g. show_bonds=False, half_bonds=False).

  • frame_index (int (default: 0)) – Which frame to render (default 0).

  • figsize (tuple[float, float] (default: (5.0, 5.0))) – Figure size in inches (width, height).

  • dpi (int (default: 150)) – Resolution for raster output formats.

  • background (str | float | tuple[float, float, float] | list[float] (default: 'white')) – Background colour (CSS name, hex string, grey float, or RGB tuple).

  • show (bool | None (default: None)) – Whether to call plt.show() to open an interactive window. Defaults to True when output is None, False when saving to a file. Pass explicitly to override (e.g. show=True to both save and display).

  • colour_by (str | list[str] | None (default: None)) – Key into scene.atom_data to colour atoms by. When None (the default), species-based colouring is used.

  • cmap (str | Callable[[float], Sequence[float]] | list[str | Callable[[float], Sequence[float]]] (default: 'viridis')) – Matplotlib colourmap name (e.g. "viridis"), Colormap object, or callable mapping a float in [0, 1] to an (r, g, b) tuple.

  • colour_range (tuple[float, float] | None | list[tuple[float, float] | None] (default: None)) – Explicit (vmin, vmax) for normalising numerical data. None auto-ranges from the data.

  • **style_kwargs (object) – Any RenderStyle field name as a keyword argument. Unknown names raise TypeError.

Return type:

Figure

Returns:

The matplotlib Figure object.

hofmann.rendering.static.render_legend(scene, output=None, *, legend_style=None, show_outlines=True, outline_colour=(0.15, 0.15, 0.15), outline_width=1.0, polyhedra_shading=1.0, light_direction=(0.0, 0.0, 1.0), dpi=150, background='white', transparent=False, figsize=None)[source]

Render a standalone species legend as a tight matplotlib figure.

Produces a figure containing only the legend — no structure, bonds, cell edges, or axes widget. Useful for composing figures manually in external tools (Inkscape, Illustrator, LaTeX).

The legend entries, colours, and circle sizes are determined by the scene’s atom styles and the legend_style settings, using the same rendering code as the in-scene legend drawn by show_legend=True.

Parameters:
  • scene (StructureScene) – The structure scene (provides species and atom styles).

  • output (str | Path | None (default: None)) – Optional file path to save the figure. The format is inferred from the extension (e.g. ".svg", ".png").

  • legend_style (LegendStyle | None (default: None)) – Visual style for the legend. None uses defaults. See LegendStyle.

  • show_outlines (bool (default: True)) – Whether to draw outlines around legend circles.

  • outline_colour (str | float | tuple[float, float, float] | list[float] (default: (0.15, 0.15, 0.15))) – Colour for circle outlines when show_outlines is True.

  • outline_width (float (default: 1.0)) – Line width for circle outlines in points.

  • polyhedra_shading (float (default: 1.0)) – Shading strength for 3D polyhedron legend icons (0 = flat, 1 = full).

  • light_direction (tuple[float, float, float] (default: (0.0, 0.0, 1.0))) – Direction of the virtual light source for polyhedra face shading, in screen space (x = right, y = up, z = towards viewer). Must have exactly 3 components. Normalised internally before use. The zero vector raises ValueError.

  • dpi (int (default: 150)) – Resolution for raster output formats.

  • background (str | float | tuple[float, float, float] | list[float] (default: 'white')) – Figure background colour.

  • transparent (bool (default: False)) – If True, save with a transparent background. Useful for embedding in documents or web pages with non-white backgrounds.

  • figsize (tuple[float, float] | None (default: None)) – Figure size in inches (width, height). When provided the saved image is this exact size with the legend centred inside; when None (the default) the saved image is tight-cropped to the legend artists. Only affects the saved file — the returned figure always uses a fixed internal canvas.

Return type:

Figure

Returns:

The matplotlib Figure. When output is given the figure is saved and then closed; otherwise it remains open for further manipulation (note that the figure canvas is larger than the cropped output).

Example:

from hofmann import LegendStyle
from hofmann.rendering.static import render_legend

fig = render_legend(scene, "legend.svg")

# Proportional circle sizes:
style = LegendStyle(circle_radius=(3.0, 8.0))
fig = render_legend(scene, "legend.svg", legend_style=style)
hofmann.rendering.interactive.render_mpl_interactive(scene, *, style=None, frame_index=0, figsize=(5.0, 5.0), dpi=150, background='white', colour_by=None, cmap='viridis', colour_range=None, **style_kwargs)[source]

Interactive matplotlib viewer with mouse and keyboard controls.

Opens a matplotlib window where the user can manipulate the view with the mouse and keyboard:

Mouse:

  • Left-drag to rotate the structure.

  • Scroll to zoom in/out.

Keyboard:

  • Arrow keys rotate around the horizontal/vertical axes.

  • , / . roll in the screen plane.

  • + / = / - zoom in/out.

  • Shift+Arrow keys pan the view.

  • p / P increase/decrease perspective strength.

  • d / D increase/decrease viewing distance.

  • b toggle bonds, o toggle outlines, e toggle polyhedra, u toggle unit cell, a toggle axes widget.

  • [ / ] step to the previous/next frame; { / } jump to the first/last frame.

  • f toggle frame indicator, g go to a specific frame, s set frame step size.

  • r reset the view to its initial state.

  • h toggle a help overlay listing all keybindings.

When the window is closed the updated ViewState and RenderStyle are returned, allowing the user to re-use both for static rendering:

view, style = scene.render_mpl_interactive()
scene.view = view
scene.render_mpl("output.svg", style=style)
Parameters:
  • scene (StructureScene) – The StructureScene to render.

  • style (RenderStyle | None (default: None)) – A RenderStyle controlling visual appearance. Any RenderStyle field name may also be passed as a keyword argument to override individual fields.

  • frame_index (int (default: 0)) – Which frame to render initially.

  • figsize (tuple[float, float] (default: (5.0, 5.0))) – Figure size in inches (width, height).

  • dpi (int (default: 150)) – Resolution.

  • background (str | float | tuple[float, float, float] | list[float] (default: 'white')) – Background colour.

  • colour_by (str | list[str] | None (default: None)) – Key (or list of keys) into scene.atom_data to colour atoms by.

  • cmap (str | Callable[[float], Sequence[float]] | list[str | Callable[[float], Sequence[float]]] (default: 'viridis')) – Matplotlib colourmap name, object, or callable. When colour_by is a list, may also be a list of the same length.

  • colour_range (tuple[float, float] | None | list[tuple[float, float] | None] (default: None)) – Explicit (vmin, vmax) for numerical data. When colour_by is a list, may also be a list of the same length.

  • **style_kwargs (object) – Any RenderStyle field name as a keyword argument. Unknown names raise TypeError.

Return type:

tuple[ViewState, RenderStyle]

Returns:

A (ViewState, RenderStyle) tuple reflecting any view and style changes applied during the interactive session.

Colours and defaults

hofmann.Colour: str | float | tuple[float, float, float] | list[float]

A colour specification accepted throughout hofmann.

Can be any of:

  • A CSS colour name or hex string (e.g. "red", "#ff0000").

  • A single float for grey (0.0 = black, 1.0 = white).

  • An RGB tuple or list with values in [0, 1] (e.g. (1.0, 0.0, 0.0)).

See normalise_colour() for conversion to a normalised RGB tuple.

hofmann.normalise_colour(colour)[source]

Convert a colour specification to a normalised (r, g, b) tuple.

Accepts CSS colour names (e.g. "red"), hex strings (e.g. "#FF0000"), grey floats (e.g. 0.7), or RGB tuples (e.g. (1.0, 0.3, 0.3)).

Parameters:

colour (str | float | tuple[float, float, float] | list[float]) – The colour to normalise.

Return type:

tuple[float, float, float]

Returns:

A tuple of three floats in [0, 1].

Raises:

ValueError – If the colour cannot be interpreted.

hofmann.CmapSpec

Type alias for colourmap specifications accepted by the cmap parameter of render methods. See the type definition in hofmann.model for details.

hofmann.ELEMENT_COLOURS: dict[str, tuple[float, float, float]]

Mapping from element symbols to muted, publication-friendly RGB colours. Common elements use hand-picked colours; less common elements use desaturated tones grouped by periodic table region. Values are normalised to the [0, 1] range.

hofmann.COVALENT_RADII: dict[str, float]

Covalent radii in angstroms, from Cordero et al., Dalton Trans. 2008. Used by default_atom_style() for display radii.

hofmann.default_atom_style(element)[source]

Return a default AtomStyle for the given element symbol.

Uses Cordero covalent radii and a muted colour palette. Falls back to grey and a radius of 1.0 for unknown elements.

Parameters:

element (str) – Chemical element symbol (e.g. "C", "Fe").

Return type:

AtomStyle

Returns:

An AtomStyle with default colour and radius.

hofmann.default_bond_specs(species, *, bond_radius=None, bond_colour=None)[source]

Generate BondSpec rules from VESTA bond length cutoffs.

Creates one spec per unique species pair that has a VESTA cutoff entry (including same-element pairs such as C-C where present). Pairs absent from the VESTA data are silently skipped.

Parameters:
  • species (Sequence[str]) – Species labels to generate rules for.

  • bond_radius (float | None (default: None)) – Visual radius of the bond cylinder. Defaults to BondSpec.default_radius when not given.

  • bond_colour (str | float | tuple[float, float, float] | list[float] | None (default: None)) – Default colour for all generated bonds. Defaults to BondSpec.default_colour when not given.

Return type:

list[BondSpec]

Returns:

A list of BondSpec rules, one per unique pair.

Style I/O

class hofmann.StyleSet[source]

A collection of style settings loaded from or saved to a file.

All fields are optional. A StyleSet loaded from a file that only contains "atom_styles" will have bond_specs, polyhedra, and render_style set to None.

atom_styles

Per-species visual styles, keyed by species label.

bond_specs

Bond detection and appearance rules.

polyhedra

Polyhedron rendering rules.

render_style

Global rendering parameters.

Parameters:
__init__(atom_styles=None, bond_specs=None, polyhedra=None, render_style=None)
Parameters:
Return type:

None

hofmann.save_styles(path, *, atom_styles=None, bond_specs=None, polyhedra=None, render_style=None)[source]

Save style settings to a JSON file.

Only sections that are not None are written. The file is human-readable with two-space indentation.

Parameters:
Return type:

None

hofmann.load_styles(path)[source]

Load style settings from a JSON file.

All sections are optional. Unknown top-level keys raise ValueError.

Parameters:

path (str | Path) – Source file path.

Return type:

StyleSet

Returns:

A StyleSet with the parsed sections.

Raises:

ValueError – If the file contains unknown top-level keys.

Bond and polyhedra computation

hofmann.compute_bonds(species, coords, bond_specs, lattice=None)[source]

Compute bonds for a single frame based on bond specification rules.

For each pair of atoms, checks all bond specs in order to find the first matching rule where the interatomic distance falls within [min_length, max_length].

When lattice is provided, bonds across periodic boundaries are found correctly. If all bond lengths are shorter than the inscribed sphere radius of the cell, the minimum image convention (MIC) is used for an efficient O(n²) computation. Otherwise, all 27 images in {-1, 0, 1}^3 are checked iteratively to handle multi-image bonds (e.g. atoms on opposite cell faces at half a lattice parameter apart).

Parameters:
  • species (tuple[str | Composition, ...]) – Species labels, one per atom.

  • coords (ndarray) – Coordinates array of shape (n_atoms, 3).

  • bond_specs (list[BondSpec]) – List of BondSpec rules to apply.

  • lattice (ndarray | None (default: None)) – 3x3 matrix of lattice vectors (row vectors). None for non-periodic scenes.

Return type:

list[Bond]

Returns:

List of Bond objects for all detected bonds.

hofmann.compute_polyhedra(species, coords, bonds, polyhedra_specs)[source]

Compute coordination polyhedra from bonds and declarative specs.

For each atom whose species matches a PolyhedronSpec centre pattern, the bonded neighbours are collected and their convex hull is computed. Adjacent coplanar triangles are then merged into polygonal faces to reduce visual artefacts from triangulation seams.

Specs are applied in order; the first matching spec wins for each atom (consistent with compute_bonds()).

Parameters:
Return type:

list[Polyhedron]

Returns:

List of Polyhedron objects.