Author: Daniel M. Kaminski · Lublin, Poland
https://dkami.umcs.pl/tal/ · https://dkami.umcs.pl/cavity-detector-in-cif-v1-82/
Tal is a sophisticated desktop application for crystallographic visualization and analysis. Built with .NET 8 and MonoGame, it offers a high-performance 3D rendering environment for exploring molecular structures, analyzing intermolecular interactions, and performing advanced computational tasks like cavity detection and Hirshfeld surface analysis.
The application uses a flexible docking interface, allowing you to customize the layout of various tool windows around the central 3D viewport.
Operations related to opening, saving, and exporting data are found in the File menu (MainForm.Menu.cs / MainForm.Events.Menu.cs).
Opens a structure file. Tal supports:
*.cif) — multi-block via CifDivider / CifParser.ParseMultiBlock*.res, *.ins)*.xyz)*.pdb, *.ent)*.mol, *.sdf, *.sd)*.mol2, *.ml2)*.gjf, *.com, *.gau)
If a file contains multiple structures (e.g. multi-record CIF), Tal prompts to open all, only the first, or none.
Loaded structures are added to StructureStorage / Structure List.
Recent files are managed by the singleton RecentFilesManager and persisted to %LocalAppData%\Tal\RecentFiles.json.
MaxEntries).
Both menu items call the same handler OnSaveAs: a save dialog is always shown.
The currently selected structure from the Structure List is written via StructureWriter.Save(), dispatched by extension.
| Extension | Writer | Notes |
|---|---|---|
.cif | CifWriter | IUCr CIF with metadata (cell, symmetry, refinement, ADP, disorder). |
.res, .ins | ResWriter | SHELX; uses SymmetryHelper (no guessed operations). |
.xyz | XyzWriter | Cartesian XYZ. |
.pdb, .ent | PdbWriter | HETATM; CRYST1 from cell. |
.mol, .sdf, .sd | MolWriter | V2000 molfile (bonds currently not exported). |
.mol2, .ml2 | Mol2Writer | Tripos MOL2; optional CRYSIN. |
.gjf, .com, .gau | GaussianWriter | Default route #p B3LYP/6-31G(d) Opt. |
.d12 | CrystalWriter | CRYSTAL17 input; may require standard space-group setting. |
Before writing .d12, Tal calls SpaceGroupTransformer.GetTransformToStandard().
If the structure is non-standard, a dialog is shown:
StructureStorage unchanged) and save.
Opens ScreenshotDialog: captures the active MonoGame viewport at a chosen supersampled resolution.
| Control | Description |
|---|---|
cmbScale / Scale | Multiplier: 1×, 2×, 3×, 4×, 6×, 8× (labels include original / HD / 4K / max). Default often 2×. |
| Resolution preview | Live “→ W × H px”; shows “(max)” when clamped. |
| Full frame | Default — 3D + labels + color scale. |
| Scene only | Pure 3D, no overlay UI. |
| Supersample | Extra AA inside the renderer (slower). |
Final size: ResultW = round(baseWidth × Scale), clamped to MAX_DIMENSION = 8192 px per axis.
Base size = current backbuffer of the active document.
Exports the scene (atoms, bonds, unit cell, visible cavities, polyhedra, planes) to a *.pov file via PovRayGenerator.
Uses current camera, lighting, background, and display flags. See section 13.
Close — closes the active 3D document window.Exit — shuts down the application; layout and Flags settings are saved on exit.| Command | Shortcut | Handler |
|---|---|---|
| Undo | Ctrl+Z | OnUndo |
| Redo | Ctrl+Y | OnRedo |
| Cut | Ctrl+X | OnCut |
| Copy | Ctrl+C | OnCopy |
| Paste | Ctrl+V | OnPaste |
| Select All | Ctrl+A | OnSelectAll |
For atom/molecule selection prefer the Selection menu and picking modes.
Reset Layout — restores the default docking layout of tool windows (OnResetLayout).📋 Structure List — toggles the structure list dock window.🎨 Display Options — toggles DisplayOptionsWindow (moved here from the Display menu).🐞 Debug — Symmetry — opens the debug / symmetry log window (moved here from Help).Contents (F1) — opens this help.html documentation.About — shows Flags.VERSION (currently 3.2 beta), author, and third-party credits (.NET 8, MonoGame, DockPanel Suite, SharpDX).The Selection menu, picking toolbar, mouse modes, and the right-click context menu control interaction with atoms and molecules.
The picking strip combo box (MainForm.Toolbars.cs) offers:
| Mode | Behaviour |
|---|---|
| Rotate (default) | Left-drag rotate; right-drag zoom; middle-drag pan. |
| Pick Atoms | Click atoms to select/deselect (measurements, planes, individual colours). |
| Lasso Atoms | Drag a rectangle; atoms inside become selected. |
| Expand Contacts | Click an atom to grow its short-contact environment (symmetry/translation images), analogous to Mercury-style expansion. Option “Select whole molecule (Expand Contacts)” lives in Short Contacts settings. |
| Toggle Labels | Click to toggle label visibility on individual atoms. |
| Move Labels | Drag atom labels to reposition them. |
| Measure Distances | Click pairs of atoms for distance measurements. |
| Measure Angles | Three-atom angle measurements. |
| Measure Torsions | Four-atom torsion measurements. |
| Reveal Symmetry-Generated Molecules | Reveal molecules generated by symmetry (see also MainForm.RevealSymmetry.Helpers). |
Also on the picking strip: Clear Measurements, and Show Labels for: All atoms / Not hydrogen atoms / Selected atoms / None.
Select Atoms — all visible atoms in the current structure.Select Molecules — atoms of a molecule in a packing view.Select Unit Cell — unit-cell frame.Invert Selection / Clear Selection.Built in MainForm.ContextMenu.cs, updated from the atom under the cursor:
Hide selected / Hide reverse selectedShow allHide this atom / Hide this molecule / Hide this symmetry equivalentHide short contacts / Remove hanging contactsThese call into AtomVisibilityManager (see section 16). After bulk hides, use Remove hanging contacts so contact lines are not drawn to invisible atoms.
MeanPlaneCalculator (namespace Tal.Core.PowderDiffraction) exposes:
CollectSelectedAtomIndices(Molecule) — indices with selected == true.CountSelectedAtoms(Molecule) — count of selected atoms.Used by the Crystal Planes tool when building mean planes.
The Display menu, style toolbar, view toolbar, and docked Display Options window control appearance.
Global style combo (exact labels from code):
Per-atom overrides remain available via Atom Display Settings / colour tools.
Opens the atom colouring window. Modes include:
Opens LabelsWindow (🏷️ Labels Settings): show/hide, font, shadow, default offset, optional property beside the name. Individual repositioning: picking mode Move Labels.
Show Hydrogens — OnShowHydrogens.Show Unit Cell — OnShowUnitCell.Reset View — OnResetView (camera to default).▶ Animate — auto-rotate (see also AutoRotateWindow).a, b, c, a*, b*, c*.Persp. / Ortho. — toggle perspective vs orthographic.BackgroundColorWindow — solid colour or two-colour gradient; presets or custom colours.
LightingWindow — primary and secondary directional lights (direction, diffuse, ambient, specular colour/power/intensity); optional per-pixel lighting.
Window title 🎨 Atom Display Settings (AtomDisplayWindow):
Show Hydrogen as Sphere; Show Disks.Buttons: Default, Reset, Apply. Probability is the source of truth for ellipsoid scale (χ² table).
Edits the persistent per-element palette (atomic number index) via AtomPropertiesWindow / related helpers.
Overrides are stored in Flags.AtomColorOverrides and applied on startup. Reset Selected / Reset All restore defaults.
Opens ShortContactsWindow (“Define Short Contacts”):
Intermolecular / Intramolecular (separated by > 3 bonds).Select whole molecule (Expand Contacts).Detected contacts are drawn as dashed lines. Calculate → Contacts... reuses this path (OnCalcContacts → OnShortContacts).
SceneQualityWindow (labels from Designer; defaults from Flags):
Flags.SSAASamples = 2.MSAAOnSceneRT = 4.AnisotropyLevel = 4.Flags.ProgressiveAASamples (default 4).DisplayOptionsWindow quick toggles:
Depth cue details: DepthCueWindow. Stereo and Z-clipping are experimental depending on build.
The Symmetry Elements window visualizes the symmetry operations of the crystal structure in the style of Mercury and the ITA (International Tables for Crystallography) conventions. It shows rotation axes, screw axes, mirror planes, glide planes, inversion centers, and rotoinversion axes directly in the 3D viewport.
The window is opened from Display → 🔷 Symmetry Elements... or with the
keyboard shortcut Ctrl+Shift+E (OnSymmetryElements in
MainForm.Events.Menu.cs). It is a dockable DockContent
(SymmetryElementsWindow) and can be placed anywhere in the layout, but
is shown by default on the left dock with the standard tool-window width.
Atom Color and
Atom Display Settings: each change writes the new value into
Flags.Instance, bumps VisualRevision (which resets
Progressive AA), invalidates the renderer's element cache, and triggers
RefreshAllDocuments().
The Show Symmetry Elements checkbox at the top of the window controls
Flags.ShowSymmetryElements. When it is off, the renderer skips the entire
pass and nothing is drawn. This is the only global toggle; every other control
in the window affects one specific kind of element.
The window groups every symmetry element into a two-column grid: the left column is a
checkbox that controls visibility, the right column is a small color swatch that opens a
standard ColorDialog. Each row is independent: you can hide, for example,
only the 3₂ screw axes while keeping 3₁ visible, and each one
has its own color.
| Group | Rows | Flags (visibility / color) |
|---|---|---|
| Rotation axes |
2, 3, 4, 6
|
ShowRotationAxis2/3/4/6 ·
SymmetryAxis2Color* … SymmetryAxis6Color*
|
| Screw axes |
2₁, 3₁, 3₂, 4₁,
4₂, 4₃, 6₁, 6₂,
6₃, 6₄, 6₅
|
ShowScrew21 … ShowScrew65 ·
SymmetryScrew21Color* … SymmetryScrew65Color*
|
| Rotoinversion axes | −3, −4, −6 |
ShowRotoinv3/4/6 ·
SymmetryRotoinv3Color* … SymmetryRotoinv6Color*
|
| Planes |
m (mirror) · a, b, c,
n, d, g (glide — one row per family)
|
ShowMirrorPlanes, ShowGlidePlanes ·
SymmetryMirrorColor*, SymmetryGlideColor*
|
| Centers | −1 (inversion centers) |
ShowInversionCenters ·
SymmetryCenterColor*
|
3₁ vs 3₂, 6₃ vs 6₁) are
different symmetry operations — they represent different translation components
along the same rotation axis. Mercury-style tools color them separately so that a
complex packing with several screw families is readable at a glance. Tal follows this
convention and gives each subscript its own checkbox and swatch.
Elements are drawn in the style of the International Tables for Crystallography:
2₁, 3₁, 4₁,
6₁ have dense dashes; 3₂, 4₂,
6₂ have wider dashes; 6₃ has the widest. This matches
the "gęsty vs rzadszy" convention from the on-line reference style.
−4
axis can have its inversion point anywhere along its line, e.g. at z = ¼).
a/b/c/n/d/g) is derived from that vector,
not from the translation alone.
5 × arrow length, which keeps the
marker from clipping into the wall and makes the direction of the axis obvious at a
glance. Earlier versions drew a full rod through the cell with no gap; that is what
the changelog calls "długa belka".
The bottom group of the window controls the sizes and opacities. These are the same parameters for every element of that kind — the per-element controls are only for visibility and color.
| Slider | Flag | Range | What it affects |
|---|---|---|---|
| Axis Thickness | SymmetryAxisThickness |
0.001 – 0.200 Å | The radius of the cylindrical rod used for every axis and for the edges of planes. |
| Marker Size | SymmetryMarkerSize |
0.05 – 2.00 | The size of the ITA markers (triangle / rhombus / hexagon / ring) relative to the axis direction. |
| Mirror Plane α | SymmetryMirrorAlpha |
0 – 100 % | The fill alpha of mirror planes (the polygon face). |
| Glide Plane α | SymmetryGlideAlpha |
0 – 100 % | The fill alpha of glide planes. |
| Center Radius | SymmetryCenterRadius |
0.01 – 2.00 Å | The radius of the sphere used for inversion centers (and the length unit of its crosshair). |
All five sliders are clamped to their declared range, so an old tal_settings.json
with values outside the range (for example a leftover 0.185 for axis
thickness) cannot crash the window — it is silently clamped to the nearest legal value.
Colors and sizes are read live from Flags.Instance every frame by
the renderer CellSymmetryRenderer. The pipeline is:
Flags field, calls
Flags.MarkDirty(), and calls the shared
RefreshView() helper.
RefreshView() bumps Flags.VisualRevision, calls
MainForm.InvalidateSymmetryRenderers() (which sets a rebuild flag in
every renderer), and calls MainForm.RefreshAllDocuments().
CellSymmetryRenderer.Draw recomputes the color for
each element via GetColorForElement(kind, order, subscript), which
reads the current Flags values. The rod thickness, marker size,
inversion radius and plane alphas are also read from Flags inside the
drawing code.
_elements
list from the current Molecule if the geometry needs to change (for
example after a different symmetry operation became visible). For simple color
changes, the rebuild is skipped and only the colors change.
This makes the behavior identical to what you see in Atom Color and
Atom Display Settings:
no Apply button, no OK/Close required, no
need to reopen the window
. The change appears as soon as the next frame is
rendered.
The window has two buttons at the bottom:
Molecule.SymmetryOperations.
tal_settings.json; changes are persisted on
exit along with every other flag.
Every element copy that intersects the unit cell [0,1]³ is generated.
The pipeline is:
Molecule.SymmetryOperations. If the CIF (or RES) file only lists
generators, the closure expands them up to the full group (max 192 operations).
det = +1 with trace −1, 0, 1, 2 is a
rotation of order 2/3/4/6; det = −1 with trace 1 is a
mirror plane; det = −1 with trace −3 is an inversion
center; and the remaining det = −1 cases are rotoinversion axes
−3, −4, −6.
L ∈ {−2 … 2}³, the intersection of the element with the unit cell
is computed in fractional coordinates, then converted to Cartesian
using the crystal's fractional-to-Cartesian matrix.
w = (1/n)·Σ Rᵏ·t, projected onto the axis direction and quantized
to 1/n. The rotation sense (right-hand rule in Cartesian space)
decides whether a 3-fold becomes 3₁ or 3₂.
w = (t + R·t) / 2. A single half-translation along
a/b/a/b/c;
two half-translations give n; quarter-translations give
d; anything else is g.
Flags.ShowSymmetryElements as its master switch:
unchecking it disables everything in the window but does not close the window.
0 → 2 along a),
the symmetry elements are still computed only for the base cell — this is
intentional, because drawing every copy in a supercell would clutter the view
without adding information.
tal_settings.json and are saved
automatically when the application exits. There is no per-project persistence.
| Action | Shortcut / path |
|---|---|
| Open the window | Display → 🔷 Symmetry Elements... · Ctrl+Shift+E |
| Close the window | Standard docking close (click the × in the tab, or View → Reset Layout to restore the default layout) |
The Calculate menu (MainForm.Menu.cs). Several entries are fully wired; a few are still stubs.
Not implemented. OnCalcCentroids only sets the status bar.
Reserved for the geometric centroid of selected atoms.
Opens PlanesWindow (dock left). Define planes from selected atoms (mean plane via MeanPlaneCalculator)
or Miller indices (hkl). Visibility, colour, transparency; distance-to-plane measurements. See section 9.
Opens PackingWindow (“📦 Packing”). Controls fractional extent of the displayed block:
+0.5, 0→2, 0→3 per axis; All +0.5, All 0→2, All 0→3; Reset.Packing checkbox sets Flags.DrawPacking and syncs Display Options when present.Typical presets in code: asymmetric-style 0–0.5, 0–1 unit cell, 0–2 / 0–3 supercells.
Alias of Display → Short Contacts (OnCalcContacts → OnShortContacts). See §4.10 and Expand Contacts picking mode.
Opens PowderDiffractionWindow when an active document with atoms exists. See section 8.
Not implemented. OnCalcMolecularShell only updates the status bar.
Not implemented. OnCalcGraphSets only updates the status bar (reserved for H-bond graph-set analysis).
Opens CavitySettingsWindow (title 🕳️ Cavity Settings). Full algorithm and parameters: section 6.
UI highlights: Enable Cavity Display; Resolution (Å); VdW Multiplier; Smoothing; Transparency;
Use thermal motion (Uij/Uiso); Detect / Clear / Export Report;
display modes Hidden / Normal Cavity / Property Map / Topology Map / Channel Paths / Sphere Fits;
property types Charge, Hydrophobicity, Atomic Number, VdW Radius, Occupancy.
Transforms the active structure between equivalent settings (e.g. P2₁/c ↔ P2₁/n) via SpaceGroupTransformer.
Updates cell, coordinates, ADP tensors, and symmetry in place. See section 13.
Opens HydrogenNormalizationWindow (🧪 Hydrogen Normalization).
Loads the active molecule, then docks the tool window. Uses HydrogenNormalizer
(neutron-standard X–H lengths, optional skip of bridging H, Apply / Reset / cache of originals).
Details and Hirshfeld auto-path: section 7.2 and the dedicated section below if present.
Tal includes a powerful and robust engine for the detection and analysis of voids and cavities in crystal structures. This is a key feature for studying porosity, host–guest chemistry, and gas storage potential.
This is the main control panel for all cavity-related operations, opened from
Calculate → Cavity Settings... (Ctrl+Shift+C).
Controls in CavitySettingsWindow (Designer labels) and corresponding Flags / detector fields:
chkEnableCavity) — master switch for cavity visualization.
numResolution, Flags.Resolution, default often 0.10–0.15 Å) —
voxel size of the SDF grid. Smaller is more accurate and much more expensive (voxel count ~ 1/res³).
numVdwMultiplier, Flags.VdWValue, default 1.00) —
scales every atom’s van der Waals radius. See section 15.
chkUseThermalMotion, Flags.UseThermalMotion) —
when enabled, the effective atomic shape is an ellipsoid from ADP data instead of a pure sphere.
See section 6.2. The thermal term is the 1σ (RMS) amplitude of the
ADP; it is independent of the ellipsoid probability used for the ORTEP display
(see ADP conventions).
trackTransparency, Flags.transparency) — cavity mesh opacity.
numSmoothing, Flags.smuth, default 3) —
Gaussian / Laplacian smoothing iterations before Marching Cubes.
CavityDetector / static fields, not as dedicated
controls on the current settings form):
ProbeRadius (default 0.0 Å) — added to every effective radius; Connolly-style probe.MinCavityVolume (default 0.1 ų) — cavities below this volume are discarded.ScanMargin = 2 Å — periodic neighbour margin for atom generation.Once cavities have been detected, you can choose how to visualize them:
Charge, Hydrophobicity, Atomic Number,
VdW Radius, Occupancy.
Tal's cavity detector is a sophisticated implementation designed for speed and accuracy, even for large and complex structures.
ScanMargin = 2 Å of
the cell boundary). Every atom is stored with its effective radius:
r_eff(i) = r_vdw(i) · VdWValue + ProbeRadius
This radius is baked into the atom and into the SDF that is computed from it. There is
no separate "base SDF" cached across VdW values — see
section 15.
ScanMargin on each side. For every voxel the algorithm computes
the signed distance to the surface of any atom:
d_i = distance from the voxel to the surface of atom i
= |p − c_i| − r_eff(i) (spherical case)
SDF(p) = min_i d_i
SDF is negative inside an atom and positive outside. It
is truncated at MaxDistFine = 5 Å — voxels further than 5 Å from any atom
are set to that value, which is more than enough for the flood-fill to distinguish
"empty" from "occupied".
Use thermal motion (Uij/Uiso) is enabled, the distance is not
computed against a sphere but against an ellipsoid whose principal
half-axes are:
semi-axis_i = r_vdw(i) · VdWValue + ProbeRadius + √|λ_i|
where √|λ_i| is the i-th row length of the Un matrix produced
by the Eigen decomposition of the ADP tensor (see
section 6.2). The half-axes are stored per atom and used
inside the SDF hot loop.
Un' = Un · R_cartᵀ Ue' = Ue · R_cartᵀ
R_cart = A · R_frac · A⁻¹
where A is the fractional → Cartesian matrix
(x_cart = A · x_frac, column convention) and R_frac is the
3 × 3 linear part of the symmetry operation (translations do not rotate the ADP).
This is valid for every crystal system, including hexagonal, trigonal and rhombohedral
settings; no diagonal-only or sign-only shortcut is used on this path.
Un' as the ellipsoid axes. Because Un' = Un · R_cartᵀ,
each row i has length √|λ_i| (the 1σ half-axis is
preserved) and direction R_cart · e_i (the eigenvector is rotated).
Transformation is ignored by this path
(ReExpandSymmetry = false).
Transformation = R_cart, so the rendered quadric is
R_cart · Un · R_cartᵀ · prob — the same form as the old
S · Un · S for diagonal sign-only operations, generalized to full
R_cart.
TransformUnSignOnly +
Transformation = diag(sign1)) is used only when the cell matrix
A is not invertible; it is bit-compatible with the historical
CalculateSymmetryEquivalents. See the header of
Molecule.Symmetry.cs for the full derivation.
WallXMin/Max, WallYMin/Max, WallZMin/Max). This
is what tells you later whether a cavity is closed or part of a percolating channel.
Smoothing parameter (see section 6.8).
Un produced by the Eigen decomposition has the form
Un = −D · Eᵀ (the overall sign is an implementation convention and does not affect the row lengths), where E is the matrix of eigenvectors (as columns)
and D = diag(√|λ₁|, √|λ₂|, √|λ₃|). Therefore the row
i of Un has length √|λ_i|, which is the physical
half-axis of the thermal ellipsoid in the direction of eigenvector e_i. Tal
reads those row lengths once per atom, stores them as
atomRx, atomRy, atomRz together with the orthonormalized eigenvectors, and
uses the ellipsoid quadratic form
q(p) = ex²·invRx² + ey²·invRy² + ez²·invRz²
to test inside/outside and to estimate the distance to the surface. Voxels with
q ≤ 1 are inside the ellipsoid.
r_vdw. The thermal-motion term √|λ_i| and the
ProbeRadius are added as constants, independent of the multiplier. This is
intentional: the multiplier is a probe of the contact radius, not of the thermal
motion amplitude.
Un' = Un · R_cartᵀ and
Transformation = R_cart
(set in Molecule.Symmetry.cs).
The two consumers read different fields from that record:
Un' (half-axes = row lengths, directions = rotated eigenvectors).
It never reads Transformation
(ReExpandSymmetry = false).
Transformation · Un · Scale(probability),
which expands to
R_cart · Un · R_cartᵀ · prob
— the generalisation of the old diagonal
S · Un · S.
Un' = R · Un · Rᵀ with
T = I (Draw OK, SDF destroys row lengths — bad voids in rhombohedral/hexagonal cells)
and
Un' = Un · Rᵀ with
T = I (SDF OK, Draw misses the left
R — inverted principal axes on screen).
See the header of Molecule.Symmetry.cs, section 2.
√|λi| of the Cartesian ADP tensor,
i.e. the RMS displacement (1σ; the ellipsoid encloses ≈ 20% of the probability). It does
not depend on the ellipsoid probability set for the ORTEP display (default 50%,
scale factor 1.538). The two are different quantities: the displayed ellipsoid is a
multiple of the 1σ ellipsoid, whereas the cavity radius is the 1σ amplitude
plus a constant. Along a principal axis with RMS amplitude σ, the distance
between the cavity boundary and an ellipsoid drawn with scale factor s is
gap = VdWValue·r_vdw + ProbeRadius − (s − 1)·σ
(positive: the cavity boundary lies outside the drawn ellipsoid). For carbon at a VdW
multiplier of 0.1 and s = 1.538 this is 0.17 Å − 0.538·σ. It vanishes
at σ ≈ 0.32 Å: for smaller amplitudes the cavity is larger than the drawn ellipsoid, for larger
ones the ellipsoid protrudes. A tight visual fit at one multiplier is therefore a coincidence of
these numbers and not a property of the model. For an exact comparison set the ellipsoid
probability to about 20% (s ≈ 1.0): the cavity boundary is then the drawn ellipsoid
shifted outwards by the constant VdWValue·r_vdw + ProbeRadius.
Un' = Un · R_cartᵀ Ue' = Ue · R_cartᵀ R_cart = A · R_frac · A⁻¹
where A is the fractional → Cartesian matrix
(x_cart = A · x_frac, column convention) and R_frac is the
3 × 3 linear part of the symmetry operation (translations do not rotate the ADP).
This holds for all crystal systems, including hexagonal, trigonal and rhombohedral
settings. The cavity detector uses these symmetry-equivalent atoms as they are
(the symmetry operations are not applied a second time), so the ellipsoids that define
the cavity have the same orientation as the ellipsoids drawn in the 3D view.
Ue' = R_cart · Ue · R_cartᵀ was used by older builds and by the
SpaceGroupTransformer path. The cavity pipeline uses the
row convention Un · R_cartᵀ so that the eigenvalues
(the physical 1σ half-axes) are preserved bit-for-bit while the eigenvectors are
rotated. Do not mix the two conventions when comparing numbers between tools.
√Uiso. The CIF reader raises
Uiso to at least 0.05 Ų (0.22 Å); the RES reader replaces only missing
values (below 0.001 Ų) by 0.05 Ų.
The number of voxels in the supercell grid grows cubically with the resolution:
N_voxels ≈ (cell_size / resolution)³ · (1 + 2 · margin_fraction)³
Tal enforces a hard limit Flags.MAX_VOXELS on the total voxel count. If the
grid exceeds this limit, detection is aborted with an error message and a suggested coarser
resolution. Two additional thresholds trigger warnings:
VoxelGrid stores resolutions as short, so anything larger
cannot be represented. Detection is aborted.
The memory footprint is roughly 4 bytes × N_voxels for the density grid, plus
about the same again for the periodic copy, gradient arrays, and the voxel mask. A 512³
grid (≈ 134 M voxels) already needs ~1.5 GB of RAM, which is why the default resolution is
capped around 0.10–0.15 Å for typical organic crystals.
Both the VdW multiplier and the anisotropic (ADP) mode make every atom larger and therefore
reduce the free space. The reduction is often
much larger than the relative growth
of the atoms
, and the number of cavities usually changes as well. This section gives
a simple model that predicts the size of the effect from two numbers that Tal already reports
(void volume V and void surface S), and explains when a void splits
or disappears. Throughout, κADP = V(ADP = YES) / V(ADP = NO);
this ratio is not the same quantity as the κ = V(1.5)/V(1.0) of the VdW sweep
(section 15.6).
Let d(p) be the distance from a point p to the nearest atom surface
(this is the SDF of section 6.2). A point belongs to the void when d(p) > 0.
If every atom is inflated by the same amount δ, the point stays in the void only
when d(p) > δ. In other words, the new void is the old void
eroded by a ball of radius δ: every wall of the void moves inwards by
δ. In ADP mode δ is not the same for every wall: it is the
displacement amplitude of the nearest atom in the direction of the wall normal
(see "What is δ for an ellipsoid?" below).
Because every wall moves inwards by δ, the volume lost per unit of
δ is exactly the area of the wall. For the eroded void this gives the exact relation
dV/dδ = − S(δ) V(δ) = V₀ − ∫₀^δ S(t) dt
where S(δ) is the void surface after the erosion. To first order in δ:
κ_ADP ≈ 1 − δ·S₀/V₀ = 1 − δ/ℓ ℓ = V₀/S₀
The characteristic length ℓ = V/S of the void measured without ADP decides
everything: a void is robust when δ ≪ ℓ and collapses when δ is
comparable to ℓ. For simple shapes with inscribed radius ρ the
erosion can be written exactly:
| Void shape | ℓ = V/S | κADP(δ) | Disappears at |
|---|---|---|---|
| Compact pocket (sphere, radius ρ) | ρ / 3 | (1 − δ/ρ)³ | δ = ρ |
| Channel (cylinder, radius ρ) | ρ / 2 | (1 − δ/ρ)² | δ = ρ |
| Layer (slab, half-thickness ρ) | ρ | 1 − δ/ρ | δ = ρ |
All three are of the form (1 − δ/ρ)m with m = 3, 2, 1 for
pockets, channels and layers. Real voids are mixtures of these. A void of large volume but small
ℓ (thin, branched, low sphericity) loses most of its volume at a very small
δ, even if its equivalent-sphere radius is large. Note that a void of inscribed
radius ρ (the "max sphere radius" of the sphere fit) disappears completely at
δ = ρ, regardless of its volume.
A neck of free width w (the diameter of the sphere that can pass through it)
closes when its two walls have both moved by δ, i.e. when
δ ≥ w/2. A void network therefore splits at the bottleneck radius
δc = wmin/2 of the narrowest neck on its connecting path
(MinWidth of the channel analysis, section 15.10, is a local diameter, so δc = MinWidth/2).
Splitting changes the number of cavities but, taken alone, hardly changes
the total volume (a neck contributes very little volume). The total volume falls
additionally through fragmentation only in two ways:
Min cavity volume are discarded, and
ℓ
and vanish quickly (δ ≥ ρ).
Consequently, the number of cavities, the total volume and
κADP depend on Min cavity volume. Always quote that
parameter together with κADP. The effects of volume loss and of
connectivity change are two observables of the same erosion; they are not two
independent factors that multiply each other.
An atom with the displacement tensor U (Cartesian, Ų) is represented by the
1σ ellipsoid with half-axes √λi. A wall with outward normal
n is moved by the extent of this ellipsoid in the direction
n, that is, by
δ(n) = √(nᵀ·U·n) √λ_min ≤ δ(n) ≤ √λ_max
So it is the vibration towards the void that matters, not the mean amplitude
√Ueq. Typical values of √Ueq for C, N, O atoms
are ≈ 0.12–0.16 Å near 100 K (Ueq ≈ 0.015–0.025 Ų) and
≈ 0.20–0.25 Å at room temperature (Ueq ≈ 0.04–0.06 Ų).
Here U is the 1σ tensor of section 6.2; a thermal term scaled by a factor
s (e.g. 1.538 for ellipsoids drawn at 50%) would scale δ by the same factor.
The following didactic model contains only the volume effect and no necks. It is not a
molecular crystal. Take an FCC lattice with a = 3.615 Å (4 atoms per cell,
Vcell = a³ = 47.24 ų, nearest-neighbour distance
dnn = a/√2 = 2.556 Å), a base radius
rbase = 1.00 Å (deliberately smaller than a real VdW radius, so that
free space exists) and an isotropic increase δ. As long as the spheres do not overlap
(δ < δtouch = dnn/2 − rbase = 0.278 Å):
V_free(δ) = a³ − (16π/3)·(r_base + δ)³
| δ (Å) | radius r (Å) | V_atom (ų) | 4·V_atom (ų) | V_free (ų) | % of cell | κ = V_free(δ)/V_free(0) |
|---|---|---|---|---|---|---|
| 0.00 | 1.000 | 4.19 | 16.76 | 30.49 | 64.5% | 1.000 |
| 0.05 | 1.050 | 4.85 | 19.40 | 27.85 | 58.9% | 0.913 |
| 0.10 | 1.100 | 5.58 | 22.30 | 24.94 | 52.8% | 0.818 |
| 0.15 | 1.150 | 6.37 | 25.48 | 21.76 | 46.1% | 0.714 |
| 0.20 | 1.200 | 7.24 | 28.95 | 18.29 | 38.7% | 0.600 |
| 0.25 | 1.250 | 8.18 | 32.72 | 14.52 | 30.7% | 0.476 |
| 0.278 | 1.278 | 8.75 | 34.98 | 12.26 | 26.0% | 0.402 |
At contact the spheres fill 74.0% of the cell (the close-packed value), so the free volume is
26.0%, which is a useful check of the table. The model illustrates how fast the free volume
falls when the radius grows by only δ/rbase = 10–25%.
The numbers are specific to this lattice and are not transferable to a real crystal
(real VdW radii are larger, so the same δ is a smaller relative change; real
voids have a different ℓ). Also note that this lattice has no necks to close:
for a point probe the free space of FCC stays connected through the triangular windows up to
r = dnn/√3 = 1.476 Å, i.e. well beyond the contact.
Fragmentation is therefore described by Effect 2 above, not by this model.
The packing coefficient of Kitaigorodskii, k = Vmolecules / Vcell
(for hard spheres at most 0.74), is a static measure: it counts the cell volume occupied by the
VdW envelopes of the molecules. Since 1 − k is the whole space not covered by the VdW
envelopes, including the narrow interstices that Tal does not report as cavities, the
void fill fraction of Tal (probe 0, VdW multiplier 1.0) is at most
1 − k. The two numbers are not expected to be equal.
κADP adds a different piece of information: how much of the reported void
lies within a distance of about δ of the walls. By section 6.4, this depends on
ℓ = V/S of the void, hence on its shape (low sphericity means a small
ℓ). A high κADP (close to 1) means that the voids are
compact and survive the thermal inflation; a low value means that the reported volume
consists largely of thin regions near the walls.
κADP is a descriptor of this particular model. It depends on the
temperature of the data collection (ADPs grow with temperature), on Min cavity volume
and on the grid resolution, so only values obtained under comparable conditions should be
compared. A low κADP is a reason to examine the structure more
closely (for example, for sensitivity to temperature or to guest inclusion), but it is not a proof
of such sensitivity. For the classical packing analysis, see e.g. E. V. Peresypkina,
V. A. Blatov, Acta Cryst. B56 (2000) 501–511 and 1035–1045 (molecular coordination
numbers, topology of molecular packings); these papers are background, not a basis of the
algorithm.
Consider a molecular crystal (P2₁/n, cell 11.80 × 10.39 × 19.46 Å, β = 103.73°,
Vcell = 2317.08 ų, 224 atoms in the cell, data collected at 293 K), detected twice
with identical parameters (resolution 0.15 Å, VdW multiplier 1.0, probe radius 0.0,
Min cavity volume = 0.1 ų, the default). The only difference is the
Use Anisotropic VdW flag.
| Quantity | ADP = NO (spheres at mean positions) |
ADP = YES (1σ ellipsoids) |
Change |
|---|---|---|---|
| Number of cavities | 1 | 6 | ×6 |
| Total void volume (ų) | 323.03 | 67.66 | −79% |
| Total void surface (Ų) | 1390.64 | 372.95 | −73% |
| Characteristic length ℓ = V/S (Å) | 0.232 | 0.181 | −22% |
| Void fill fraction | 13.94% | 2.92% | −79% |
| I_frag (cavities per 56 atoms) | 0.018 | 0.107 | ×6 |
| Mean sphericity | 0.164 | 0.399 | +143% |
| κADP = V(YES)/V(NO) | — | — | 0.209 |
ℓ = 0.23 Å
(a compact sphere of that size would have ℓ = R/3 ≈ 1.42 Å). The
estimate for a single sphere, (1 − δ/R)³ with δ = 0.15 Å,
would predict κADP ≈ 0.90. The observed value is 0.21. The
ℓ-based estimate is the appropriate one here.
ℓ = 0.232 Å we obtain
κADP ≈ 1 − δ/ℓ = 0.57, 0.35 and 0.14
for δ = 0.10, 0.15 and 0.20 Å, respectively. Conversely, the observed
κADP = 0.21 corresponds to an effective
δeff = (1 − κADP)·ℓ ≈ 0.18 Å, which is in the range
of the RMS amplitudes of C, N, O atoms expected for the data collected near room temperature.
Because δeff is comparable with ℓ, the first-order formula is only an
order-of-magnitude check here (the higher-order, curvature terms and the vanishing of small
pieces are not included).
S/V parts of the void disappear first, and the survivors are compact pockets.
It does not mean that the original void became rounder.
2δ.
V and S from the report.ℓ = V/S.δ ≈ √Ueq of the atoms lining the void (see above).δ ≪ ℓ, the void is robust. If δ is comparable to or larger than
ℓ, expect a strong decrease of volume and a split into several cavities.
Cavities whose max sphere radius is smaller than δ will disappear.
The two runs answer different questions.
√λi
encloses ≈ 20% of the probability density of the displacement. The ORTEP ellipsoids in the
3D view are usually drawn at 50% (1.54σ); the cavity detector does not follow that setting
(see the ADP conventions in section 6.2). Using 50% ellipsoids for the cavities would increase
δ by a factor 1.54 and lower κADP accordingly.
Uiso.
In ADP mode such atoms are spheres of half-axis √Uiso; the CIF reader
raises Uiso to at least 0.05 Ų (see the ADP conventions in section 6.2),
so for low-temperature data the hydrogen atoms may be inflated slightly.
√|λi|.
An atom with a negative eigenvalue (a non-positive-definite ADP, typically a sign of a
poor refinement) is therefore silently given a positive amplitude. Check the ADPs
of the atoms that line a cavity.
rvdw·VdWValue + ProbeRadius + √|λi|. This is exact along the
principal axes. In other directions it is slightly smaller than the exact offset of the
ellipsoid (the difference is negligible for √|λ| ≪ rvdw).
The iso-surface q = 1 is exact, but the distance estimated from the quadratic
form is not a Euclidean distance, so sphere-fit radii and channel widths are approximate
in ADP mode.
2δ ≈ 0.3 Å are
sampled by only two voxels at a resolution of 0.15 Å. The number of cavities
and the volumes of small pieces can therefore depend on the grid; for important results
repeat the run with a finer resolution (e.g. 0.10 and 0.075 Å) and compare.
After a successful detection, the Export Report button writes a complete text report. It is deliberately self-contained: everything needed to reproduce or interpret the numbers is in the file. Sections:
Smoothing is applied in two stages, both controlled by the Smoothing slider:
SnapTolerance
of each other are merged. The tolerance is 0.0004–0.0008 Å depending on the
level.
Smoothing level 0 disables both stages. Levels 1–5 follow the presets shown in section 4. Higher levels produce smoother surfaces but also systematically shrink the cavity volume by 5–15%, so for quantitative work it is recommended to use a low smoothing level (0 or 1) and only enable smoothing for publication figures.
Clear Cavities removes all detected cavities and their meshes, but it does
not clear the cached atoms or the voxel grid — a subsequent detection at
the same parameters reuses the cached data and is nearly instant. To fully reset the
detector (free all memory, drop the atom cache, drop the spatial index), use
Calculate → Cavity Detection → Force Reset. This is also the recommended
action when switching to a different crystal structure.
The same crystal structure can be expressed in several equivalent settings of the
same space group — for example, P21/n and P21/c are the two
standard monoclinic settings of space group #14. They describe the same crystal, with
the same unit-cell volume and the same number of atoms per cell, but with different
cell parameters and different symmetry-operation strings. Because Tal can transform
between settings in place (Calculate → Transform space group...), a
natural question is:
does the cavity detector give the same answer for both
settings?
The short answer is yes — but only if the ADP tensors of the symmetry
equivalents are rotated correctly. This section describes the consistency test and
what to expect from it.
When the atoms carry anisotropic displacement parameters (ADPs), the cavity detector
replaces each atom with its 1σ thermal ellipsoid. The shape and orientation of
that ellipsoid are stored as the 3×3 tensor Un, and the detector reads
the rows of Un as the three half-axes of the ellipsoid
(see section 6.2, "Anisotropic VdW — exact math").
If the transformation between two space-group settings rotates the tensor in the
wrong convention — for example, full rotation R·U·RT instead
of the row-wise U·RT that the detector expects — the row
lengths no longer match √λi, the ellipsoids come out
physically wrong, and the cavity volumes differ between settings. The test below
detects exactly this class of error.
Calculate → Cavity Settings → Detect)
with Thermal motion = YES, at a chosen resolution (for example
0.15 Å). Export the single-run report.
Calculate → Transform space group...) and confirm that:
| Quantity | Expected agreement | Comment |
|---|---|---|
| Unit-cell volume Vcell | exact | Both settings describe the same crystal; V is invariant. |
| Atoms in asymmetric unit | exact | Transformation must not add or remove atoms. |
| Symmetry operations count | exact | Same group number, same order. |
| Number of cavities | exact (typically 1 in both) | A single symmetry setting cannot create or destroy cavities unless the geometry really changed. |
| Total void volume V |
≤ 1 % at resolution 0.15 Å; ≤ 0.5 % at resolution 0.06 Å |
The key metric. It must converge as the resolution improves. Any stable offset above 1 % indicates a rotation convention bug in the transformation of ADP tensors. |
| Total void surface S | 5–10 % difference is normal | Marching Cubes samples the same iso-surface on two different grids. The surface area is a local quantity, so it depends on the orientation of the grid relative to the cavity walls. Do not use S alone to judge consistency. |
| Sphericity | same tolerance as S | Sphericity is derived from V and S, so it inherits the rasterization difference of S. |
| Void fill fraction | ≤ 1 % at 0.15 Å | Follows V directly, since Vcell is invariant. |
| Molecule count in cell | exact (must be Z) | Independent of cavity detection. If it differs between settings, there is a separate bug in the molecule-grouping routine (see "Known issues" below). |
The table below shows the full set of measurements that were taken on the same crystal of 488_auto (space group #14, Z = 4, 38 atoms in the asymmetric unit, 152 atoms per cell) in the two settings P21/n and P21/c. The test was performed twice at each of two resolutions (0.15 Å and 0.06 Å), once with Thermal motion = YES and once with Thermal motion = NO.
| Resolution | Thermal motion | VP21/n (ų) | VP21/c (ų) | ΔV | Interpretation |
|---|---|---|---|---|---|
| 0.15 Å | YES | 174.94 | 190.92 | +9.1 % | ADP rotation wrong — before fix. |
| 0.06 Å | YES | 173.05 | 189.69 | +9.6 % | ADP rotation wrong — difference does not decrease with resolution, proving the problem is not numerical. |
| 0.06 Å | NO | 516.65 | 516.36 | −0.06 % | Spheres only. Positions are correct; only the ADP path was broken. |
| 0.15 Å | YES | 192.47 | 190.92 | −0.81 % | ADP rotation fixed — after fix. |
| 0.06 Å | YES | 190.20 | 189.69 | −0.25 % | ADP rotation fixed — difference now decreases with resolution, proving numerical convergence. |
Two things are worth pointing out in this example:
Even after the ADP rotation is correct, two differences between the two settings remain and are expected:
This advanced tool, opened from the Advanced menu, is essential for
understanding and quantifying intermolecular interactions in molecular crystals.
It is based on the Hirshfeld partitioning scheme, which defines a molecule's space
in a crystal based on the electron density.
Molecule.atom[], or on any presentation state of the main 3D view.
The pipeline builds its own fresh unit cell from
Molecule.AsymetricPart and Molecule.SymmetryOperations,
runs the molecule finder on that fresh cell to detect distinct molecules, marks
the chosen target as host and collects all surrounding atoms (guest atoms
in the base cell + periodic copies of host and guests) within a neighbour cutoff.
The resulting surface is therefore identical regardless of what you see in the
main viewport. You can open the window and click Generate at any
time — no Packing setup needed.
Hirshfeld Surface Analysis window from the
Advanced menu.
Target molecule dropdown is filled
with the detected molecules (each entry shows the number of atoms).
d_e, d_i,
d_norm, charge, hydrophobicity) and the
contact-type decomposition.
Hirshfeld surface analysis, by convention (CrystalExplorer, Tonto and all crystallographic tools), requires neutron-normalized hydrogen positions. X-ray diffraction locates the maximum of the electron density of an H atom, which is systematically shifted toward the heavy atom relative to the true nuclear position. Using X-ray H positions in a Hirshfeld analysis introduces a bias of typically 0.10–0.20 Å on every X–H bond, which distorts both the surface and the fingerprint decomposition. Tal therefore always normalizes H before generating a Hirshfeld surface — silently, without a dialog.
Each hydrogen atom is placed at its neutron bond length along the
direction defined by the heavy-atom coordinate and the current H coordinate.
The bond lengths used are tabulated in HydrogenNormalizer.NeutronLengths:
| Heavy atom | Bond | Neutron length (Å) |
|---|---|---|
| Boron (5) | B–H | 1.180 |
| Carbon (6) | C–H | 1.083 |
| Nitrogen (7) | N–H | 1.009 |
| Oxygen (8) | O–H | 0.983 |
| Fluorine (9) | F–H | 0.950 |
| Phosphorus (15) | P–H | 1.420 |
| Sulfur (16) | S–H | 1.340 |
| Chlorine (17) | Cl–H | 1.280 |
When Generate is pressed, the pipeline calls
Molecule.CheckHydrogenState, which uses the pure function
HydrogenNormalizer.Check to analyze every H in the asymmetric part
without modifying anything. Each H is classified into one of five states:
0.005 Å of its neutron target. The pipeline logs a success
message and skips the normalization step.
Molecule.ApplyHydrogenNormalization, which
1.3 Å cutoff and a
minimum-image fractional
difference
, so H and heavy atoms in different unit cells are matched
correctly.
"bridging".
cartShift = unit · (target − len), where unit is
the normalized direction from the heavy atom to the current H. This shift
is then converted to a fractional delta via
Molecule.CartDeltaToFracDelta and added to the H's fractional
coordinates. The H stays in the same copy of the unit cell as its
heavy neighbor (the fractional coordinates are never wrapped), so
cross-boundary molecules remain intact.
Molecule.RebuildFromAsymetricPart. This regenerates the
symmetry equivalents, the 3D atom array, the bond graph, the hydrogen bond
graph, and the MoleculeShift.
The pipeline then closes any open fingerprint window (its per-vertex data is now stale), clears the previous Hirshfeld surface (it was built on X-ray H), and continues with the normal detection algorithm.
HydrogenNormalizer._originalCache keyed by the
Molecule instance. The user can reopen the
Calculate → Normalize Hydrogens... window and press
Reset at any time to restore the original X-ray H positions in
the 3D view. The Reset is a pure operation on the asymmetric part followed by
the same RebuildFromAsymetricPart used by the normalizer, so the
two operations commute.
RebuildFromAsymetricPart
invalidates the entire Hirshfeld surface state (mesh, per-vertex arrays, vertex
bins, contact decomposition). The pipeline therefore always clears the old
surface before proceeding, and the fingerprint window is closed if it is open.
Do not close the Hydrogen Normalization window during a Generate run — the
pipeline has already updated the underlying molecule and re-reading it mid-run
would produce a stale table.
w(r) = 0.5 isosurface of the Hirshfeld weight
function, which is the ratio of the electron density of a promolecule (the
selected molecule) to the total electron density of the crystal (procrystal).
The surface can be colored by various properties.
d_i: The distance from the surface point to the nearest
nucleus inside the surface.
d_e: The distance from the surface point to the nearest
nucleus outside the surface.
d_norm: A normalized contact distance, calculated as
d_norm = (d_i − r_ivdW) / r_ivdW +
(d_e − r_evdW) / r_evdW
.
Areas with d_norm < 0 (typically colored red) are
regions of strong interaction (e.g., hydrogen bonds). Blue areas
represent longer, weaker contacts.
charge field
from the source file. The color ramp matches the CrystalExplorer convention:
green (negative), yellow (neutral),
red (positive). This is a powerful visualization for
identifying electrophilic and nucleophilic regions on the surface.
Hydrophobicity
field from the source file. The color ramp is: blue
(hydrophilic), white (neutral), brown
(hydrophobic). Useful for studying host–guest chemistry and protein–ligand
interactions.
charge or Hydrophobicity values, the corresponding
coloring mode will show a uniform color (yellow for charge, white for
hydrophobicity), and a warning is written to the debug log.
The promolecule density ρ_target(r) and the surrounding density
ρ_outer(r) are computed from a Slater-Type Orbital
basis. Each atom in the crystal is represented by the sum of the squared radial
orbitals of its electronic configuration, with the coefficients and exponents
taken from the tabulated Roothaan–Hartree–Fock STO basis of Koga and coworkers.
For an orbital with principal quantum number n and exponent
ξ, the normalized radial part is
R_n(r) = N · r^(n-1) · exp(−ξ·r)
N = (2ξ)^(n+1/2) / sqrt( (2n)! )
The spherical density of a single shell is the sum of the squared contractions over all orbitals of that shell:
ρ_shell(r) = Σ_orbital occ(orbital) · | Σ_term c_term · R_term(r) |²
where occ is the occupancy of the orbital in the atom's
configuration (alpha and beta spin summed). The total density of the atom is
ρ_atom(r) = (ρ_s + ρ_p + ρ_d + ρ_f) / (4π)
Tal ships with three JSON files that contain the complete STO basis for every
element that appears in the neutral, cation and anion datasets. The files are
located next to the executable (or in one of the search paths listed in
StoDatabase.EnsureLoaded):
| File | Content | Reference |
|---|---|---|
sto_atoms_neutral.json |
Neutral atoms, Z = 1–95 (through Am). | T. Koga, K. Kanayama, S. Watanabe, A. J. Thakkar, Analytical Hartree–Fock wave functions in Slater-type basis sets for the neutral atoms He–Lr , Int. J. Quantum Chem. 71 (1999) 491–503. |
sto_atoms_cation.json |
Cations with charge state +1. | T. Koga, H. Tatewaki, A. J. Thakkar, Analytical Hartree–Fock wave functions in Slater-type basis sets for the mono-cations of the atoms Li–Lr , Phys. Rev. A 68 (2003) 032701. |
sto_atoms_anion.json |
Anions with charge state −1. | T. Koga, A. J. Thakkar, Analytical Hartree–Fock wave functions in Slater-type basis sets for the mono-anions of the atoms H–Lu , J. Phys. B 29 (1996) 2973. |
Each JSON entry contains the atom symbol, total, kinetic and potential energy,
the config string (e.g. "CARBON 1S(2)2S(2)2P(2), 3P"), and the
four shells s, p, d, f,
each of which is a list of terms {n, ξ, coefs[]}. The shell
also carries the array of per-orbital occupancies OccAlpha and
OccBeta (parsed on the fly by ElectronConfigParser).
Because evaluating the sum of squared contractions at every voxel is expensive,
the density of each symbol is precomputed once per session into a
StoDensityLut. The LUT is a 1-D array of ρ(r) sampled on a uniform
grid with step 0.03 Å up to a hard limit of 15 Å.
Three global parameters control the size and shape of the LUT:
| Parameter | Meaning | Default |
|---|---|---|
DefaultStep |
Grid step (Å) of the radial LUT. | 0.03 |
ExpCutoff |
rMax = ExpCutoff / ξmin — the LUT is
truncated where exp(−ξmin·r) < exp(−ExpCutoff).
|
10.0 |
MaxRMax |
Hard upper bound on rMax, in Å. |
15.0 |
These three values can be changed at runtime from the Advanced panel (see 7.5). Any change requires a rebuild of the LUT cache, which is done via the "Rebuild LUT cache & Regenerate" button. Because the LUT is shared by all atoms of the same symbol, the cache must be cleared before a regeneration picks up the new parameters.
exp(−10) ≈ 4.5·10⁻⁵ is well below
the density threshold that affects the Hirshfeld weight function
w(r) = ρ_target / (ρ_target + ρ_outer). Truncating the LUT earlier
would distort the surface near the boundary of the promolecule, while truncating
later would only waste CPU cycles on voxels whose contribution to
w(r) is numerically invisible. The ExpCutoff = 10 value
is a compromise validated against the same molecule computed with the
full-range 1e-12 basis.
The right-side control panel contains an Advanced checkbox. Ticking it reveals a small group with three numeric up-down controls and a rebuild button:
| Control | Meaning | Range |
|---|---|---|
DEFAULT_STEP |
Grid step (Å) of the radial LUT (see 7.4). | 0.005 – 0.100 |
EXP_CUTOFF |
LUT truncation exponent (see 7.4). | 5.0 – 40.0 |
MAX_RMAX |
Hard upper bound on the LUT radius, in Å (see 7.4). | 5.0 – 50.0 |
Rebuild LUT cache & Regenerate
— writes the three values into StoDensityLut, clears the
per-symbol LUT cache in StoDatabase, prebuilds LUTs for
the common symbols (H, C, N, O, F, P, S, Cl, Br, I), and re-runs
Generate with the new settings.
|
||
DEFAULT_STEP controls the accuracy of the linear
interpolation between LUT samples. Halving it doubles the LUT memory
(still small: 2–6 KB per element) and reduces the interpolation error by
roughly a factor of four.
EXP_CUTOFF is the main speed/quality knob. Lower
values (8–10) give very fast runs but eventually start clipping the
density tails of heavy atoms; higher values (12–15) restore the tails
at the cost of roughly a linear increase in the number of voxels
touched by FillDensitySto.
MAX_RMAX is a safety net. GetMaxRadius returns
ExpCutoff / ξmin; for very small exponents (heavy
atoms, low-lying valence orbitals) this can exceed 30 Å. The hard limit
prevents a single heavy atom from allocating a LUT larger than needed
for the current scene.
This is the cornerstone of Hirshfeld analysis. The fingerprint plot is a 2D
histogram of the d_i versus d_e values for every point
on the Hirshfeld surface.
The main 3D view for the Hirshfeld analysis is the HirshfeldDocument —
a DockContent that hosts a MonoGame viewport and a right-side control
panel. It is created from the Hirshfeld Surface Analysis menu item
and reused on subsequent invocations.
_panel, fill). Renders the
molecule (or isolated target), the Hirshfeld isosurface, optional wireframe,
axes and unit cell. Background color follows
Flags.Instance.color1.
_controlsHost, 300 px
wide, docked Right). A FlowLayoutPanel with all
controls, sorted top-down.
| Control | Description |
|---|---|
_lblMoleculeTitle |
Section header: "Target molecule:". |
_cmbMolecule |
Combo box listing molecules detected in the active document via
HirshfeldTargetSelector.EnumerateMolecules. Each entry is of
the form "Molecule #N (X atoms)".
|
_btnUseSelection |
Replaces the combo selection with the molecule that the currently selected atoms belong to. If the user selected atoms from more than one molecule (or no atoms at all), a message box is shown. |
_lblDensityModelTitle / _cmbDensityModel |
Density model dropdown: Spherical (isotropic) or
Anisotropic (ADP). The anisotropic mode requires valid ADP
tensors. The combo is initialised to SelectedIndex = 0,
so the window never opens with an empty combo.
|
| Control | Parameter | Range / Default |
|---|---|---|
_numVoxelSize |
Voxel size (Å) | 0.01 – 0.20, default 0.10. Smaller → higher resolution, more memory and time. |
_numSigmaMult |
Sigma multiplier |
0.80 – 2.00, default 1.00. Multiplies the per-element
Gaussian σ. 1.0 gives the sharpest, most physical surface.
Note: in STO mode this control has no effect — the STO basis carries its own exponents. It only affects the Gaussian fallback used for atoms that are missing an STO entry. |
_numIsoLevel |
Iso level for w(r) | 0.30 – 0.70, default 0.50. Standard Hirshfeld definition. |
_numMargin |
Grid margin (Å) | 1.00 – 8.00, default 3.00. Extra space added around the target's bounding sphere. If the surface appears artificially clipped near the outer boundary of the grid, increase the margin (typical values for organic crystals: 3–5 Å). |
| Control | Description |
|---|---|
_cmbPropertyMode |
Surface coloring mode. Six options are available (index → enum):
SelectedIndex = 1 (d_norm).
|
_trackTransparency + _lblTransparencyValue |
Surface alpha (40 – 255, default 200). The label shows
"N / 255".
|
_trackDNormScale + _lblDNormScaleValue |
d_norm scale slider
(0 – 100 → Flags.HirshfeldDNormScale = 0.2 – 3.0).
Controls how fast the surface color saturates when
|d_norm| grows.
|
_chkShowMesh |
Master switch for the isosurface mesh. |
_chkShowWireframe |
Draws the triangle edges of the mesh as thin dark lines. Skipped automatically if the mesh contains more than 200,000 triangles. |
_chkShowMolecule |
Draw the source molecule behind the surface. |
_chkShowAxes |
Draw the world XYZ axes (length 20 units, red/green/blue). |
_chkShowCell |
Draw the unit cell edges as orange lines. |
_lblZoomInfo |
Current zoom factor as "Zoom: X.XX× (mouse wheel)". The
mouse wheel applies a multiplicative step of 1.05 / 0.95, clamped to
0.2 – 8.0.
|
DEFAULT_STEP = 0.03,
EXP_CUTOFF = 10.0, MAX_RMAX = 15.0
| Button | Action |
|---|---|
_btnGenerate |
Runs the pipeline (GenerateAsync). If H are not yet
neutron-normalized, the pipeline silently normalizes them first (see
7.2). Disabled while a run is in progress. Cancellation is supported
via an internal CancellationTokenSource.
|
_btnClear |
Disposes the current surface, clears the smoothing cache
(HirshfeldSmoothing.ClearCache), and resets the
diagnostics.
|
_btnSavePly |
Saves the current surface as an ASCII PLY file (vertices, normals, per-vertex RGB). Useful for MeshLab, Blender, CloudCompare. |
_btnExportPng |
Renders the current view into a RenderTarget2D with
SSAA = 2 and saves it as PNG.
|
_btnFingerprint |
Opens the HirshfeldFingerprintWindow and pushes the raw
per-vertex d_e/d_i arrays plus the atom-name
arrays. Registers callbacks OnRegionClicked,
OnVertexBinsUpdated, and
OnContactFilterChanged.
|
_btnShowDiagnostics |
Pushes HirshfeldAtomDistanceDiagnostics to the
DebugWindow. The report contains grid info, target/outer
statistics, a histogram and a cutoff analysis table.
|
_progressBar: ProgressBarStyle.Continuous, 0 – 100.
Updated through a thread-safe BeginInvoke adapter.
_lblStatus: 4-line status text (monospace). Shows "Ready",
"Generating…", final vertex/triangle counts, and the current blur state.
NormalizeHydrogensForHirshfeldSilently runs first (see 7.2).
If any H needed normalization, the pipeline rebuilds the 3D scene, closes
the fingerprint window, and clears the previous surface before proceeding.
HirshfeldScene.Build(targetMolId, neighborCutoff). If the target
molecule is not found, the operation aborts.
SceneAtom is wrapped either as an AtomStoParams
(if the STO database is loaded and a LUT exists for the element) or, as a
fallback, as an AtomGaussParams via
HirshfeldParameters.BuildFromAtomSimple. The log line
"Total: STO=N, Gaussian=M" shows how many atoms went through
each path.
n = ceil(2·(r_max + margin) / voxelSize). Fails gracefully if
n³ exceeds Flags.MAX_VOXELS.
ProcessorCount − 1); the atoms are distributed round-robin
across the threads so that load is even. The hot loop calls
StoDensityLut.SampleSq for STO atoms and
HirshfeldExpLut.FastExpNeg for the Gaussian fallback.
W = RhoPro / (RhoPro + RhoProm_Outer) with a strict fallback
of 0 for empty voxels. The distribution is written to the debug log as
w(r) [after compute].
HirshfeldMarchingCubes.Extract, which delegates to
HirshfeldMarchingCubesGenerator.Extract. It reads the flat
W array, subtracts isoLevel inline, and produces
a triangle mesh. Duplicate vertices are merged with a 27-neighbour hash
grid.
TargetMoleculeId, TargetAtomCount,
OuterAtomCount, Center,
UsedAnisotropic, and calls
ComputeBoundingRadius().
HirshfeldProperties.Compute populates DE,
DI, DNorm, Charge,
Hydrophobicity, and stores the nearest-atom indices and names.
The original values are also cloned into DEOriginal,
DIOriginal, DNormOriginal.
MoleculeShift sentinel is updated; the fingerprint window (if
open) is refreshed with the new analysis.
_zoomFactor
(scene scale), not the camera zoom.
Ctrl+S — triggers SavePng().
The surface vertices are stored in "raw" Cartesian coordinates (no
−MoleculeShift). At render time the document subtracts the
current MoleculeShift from each vertex, so the surface
always aligns with the atoms rendered by the main viewport. When the user
changes the presentation mode (and hence the shift), the buffers are rebuilt
automatically — the change is detected via the _lastMoleculeShift
sentinel field.
The Hirshfeld surface is produced by a self-contained pipeline that does
not depend on the main document's presentation state. It is
built on top of the same Molecule data used by the rest of Tal, but
with its own cell reconstruction step.
Central coordinator of the pipeline. Given a Molecule and a target
molecule ID, it reconstructs a compact unit cell, runs the molecule finder,
marks the host atoms and collects all outer (guest) atoms within a neighbour
cutoff.
Molecule,
targetMoleculeId, cutoff (Å). Default cutoff =
HirshfeldParameters.CUT_OFF = 10 Å.
TargetAtoms,
OuterAtoms, Fragments, TargetCenter,
TargetBBoxMin/Max, TargetRadius,
TargetMoleculeId, LastCutoff,
LastNeighborCount.
SymmetryAtomIndex,
CellShiftX/Y/Z, FracPosition,
CartPosition, AtomicNumber,
IsTarget, IsAnisotropic, Un (ADP
matrix), Occupancy, Name,
MoleculeId, SourceAsymIndex, and (v14)
Charge and Hydrophobicity.
Independent representation of the unit cell, built exclusively
from Molecule.AsymetricPart and
Molecule.SymmetryOperations. It deliberately does not read
Molecule.SymetryEquivalents or Molecule.atom[], because
those are mutated by the presentation-mode code.
DEDUP_TOL_FRAC = 0.005.
ops[k], k ≠ 0, find the anchor atom
(first non-hydrogen of ops[0]) and its counterpart in
ops[k]. Compute a single integer translation
from the anchor to its counterpart, then apply that same translation
to all atoms of ops[k]. This keeps symmetry-generated
molecules intact.
Charge and
Hydrophobicity values are copied from AtomSimple
so they propagate through the whole pipeline.
Detects the surface and returns a HirshfeldSurface. Its main entry
point is GenerateAsync, which drives the nine-step algorithm
described above. Important public properties:
| Member | Description |
|---|---|
Surface |
The produced HirshfeldSurface. |
Grid |
The internal HirshfeldGrid with flat W, RhoPro,
RhoProm_Outer arrays.
|
Scene |
The HirshfeldScene used to build the surface. |
BlurSigma, BlurIterations |
Blur parameters. Both default to 0 and are not exposed in the current UI — the surface is generated without any grid-level blur. |
Wraps a VoxelScanner.VoxelGrid and holds flat
density arrays:
float[] W; // w(r) = rho_pro / (rho_pro + rho_outer)
float[] RhoPro; // promolecule (target)
float[] RhoProm_Outer; // surrounding atoms
Indexing is idx = i + Nx·(j + Ny·k) — the deepest loop index
k gives sequential memory access.
ComputeRelativeDensity is parallelized and iterates linearly, with
the historical fallback of 0 for empty voxels.
HirshfeldParameters.BuildFromAtomSimple converts an atom into an
AtomGaussParams:
ρ(r) = Z_eff / (√(2π)σ)³ · exp(−|r−r₀|² / (2σ²)). The
per-element σ comes from HirshfeldAtomicRadii.GetSigma(Z),
multiplied by the user's Sigma multiplier.
Un is
row-normalized to get the three eigenvectors; the row lengths before
normalization are the principal half-axes. Each half-axis is
combined with the electronic σ as
a_k = sqrt(λ_k² + σ_e²).
CUT_OFF × max(σ or a_k) with
CUT_OFF = 10.
This class is only used by the Gaussian fallback path. When the STO database is loaded and contains an entry for the element, the density comes from the LUT instead, and none of these parameters are involved.
Hot loop replacement for MathF.Exp(-x). A static LUT of 4096
entries over x ∈ [0, 25] with linear interpolation. Maximum
relative error around 1e-4, invisible in the final rendering. Used only by the
Gaussian fallback path.
A dedicated copy of Tal's standard Marching Cubes generator, adapted for flat
density arrays and Hirshfeld iso conventions. It never modifies the input array;
the iso offset is subtracted inline. Duplicate vertices are merged with a
27-neighbour spatial hash with tolerance 0.0001.
For each mesh vertex, computes:
d_e = min distance to any outer atom
d_i = min distance to any target atom
d_norm = soft-min average of (d_e − r_vdW) / r_vdW + (d_i − r_vdW) / r_vdW
charge = gaussian-weighted average of atom.charge
hydro = gaussian-weighted average of atom.Hydrophobicity
The d_norm value uses a soft-min over the K = 8 nearest
neighbours with a Gaussian weight exp(−β·d) and β = 8.
This removes the Voronoi discontinuities that a hard nearest-neighbour approach
produces on the surface. The result is clamped to [−3, +3].
The charge and hydrophobicity values use a plain
Gaussian average over all target and outer atoms with σ = 0.6 Å; the outer atoms
are weighted by an extra factor 0.7. If the source file does not provide these
per-atom values, the corresponding surface field is uniform and a warning is
logged.
A geometry-only Taubin (λ | μ) smoothing applied to the mesh itself. Unlike a
plain Laplacian, the negative μ step compensates for shrinkage, so the surface
volume is approximately preserved. The original vertex positions and the
adjacency list are cached per HirshfeldSurface. In the current
version this class is not wired to a UI slider — it remains available
for programmatic use.
An optional pre-color smoothing step that moves vertices tangentially along the mesh and slightly along their normals to remove the "staircase" artefacts of Marching Cubes without losing the physical shape. The maximum displacement is clamped to 5% of the grid unit to avoid overshooting. In the current version this class is not wired to a UI slider — it remains available for programmatic use.
Simple helper that lists all molecules in a Molecule by their
IndividualMoleculeInCell ID. Used by the _cmbMolecule
dropdown and by UseCurrentSelection().
A separate DockContent that renders a 2D heat map of the
d_e × d_i distribution. Each bin corresponds to a small angular
sector of the surface; the number of vertices in a bin is mapped to a log-scaled
color ramp.
_panel, fill). The heat
map, axes, grid, diagonal and highlighted bins are drawn here with a
SpriteBatch in PointClamp mode.
_controlsHost, 360 px
wide, docked Right). A FlowLayoutPanel with all
controls, sorted top-down.
| Control | Description |
|---|---|
_numFontSize |
Label font size in points. Range 2 – 36, default 4. |
_numBinSize |
Bin size in Å. Range 0.001 – 0.05, default 0.005. Changing it triggers a live re-bucket. |
_numRangeMin |
Axis origin in Å. Range 0.0 – 3.0, default 0.0. Changing it triggers a live re-bucket. |
_btnResetOffsets |
Full reset (identical to RMB double-click): label offsets, font, zoom factor, range, bin size, and marker all return to their original values. |
This is the essential feature that makes the fingerprint usable for real
chemistry. It consists of a CheckedListBox that lists every contact
type present on the surface, a pair of buttons for mass (de)selection, a
read-only TextBox with the full decompose table, and a
Copy table button.
| Control | Description |
|---|---|
_lblDecomposeTitle |
Section header: "Contact types (check = show):". |
_lstContactTypes |
A CheckedListBox that is populated from
analysis.ContactTypePercentages.
Each row shows "C···H (12.5%, 2340 v)" — the contact type,
its area percentage, and the number of vertices. Rows are sorted by
descending percentage.
All items start checked, which means no filter is applied: the
mesh and the heat map show the full surface.
Unchecking a type:
|
_btnSelectAllTypes / _btnClearTypes |
Quick mass-select or mass-deselect all contact types.
Clear selection makes the mesh fully gray and the heat map
empty; Select all restores the full view.
|
_txtDecomposeTable |
A read-only multi-line text box containing the full decompose table
produced by HirshfeldContactDecompose.BuildTable. Columns:
The last row is the total. The table can be copied with the button below
or with Ctrl+C.
|
_btnCopyTable |
Copies the decompose table to the clipboard as plain text. |
RebuildHeatmapFromRaw method re-reads the
raw d_e/d_i arrays and rebuilds the histogram using
only the vertices that pass the filter. This is why the 2D map reacts
instantly to the checklist — it is not just a color overlay on top of a fixed
histogram, the histogram itself is recomputed.
[0.0 Å, 3.4 Å], default bin size
0.005 Å. Both can be changed live from the side panel.
RebuildHeatmapFromRaw() recomputes a full histogram and a
per-vertex (binDe, binDi) table. Vertices outside the range are
tagged (−1, −1).
HirshfeldDocument
via the OnVertexBinsUpdated callback, so the 3D highlighting
stays in sync even after re-bucketing.
binsInRange ≤ MAX_TOTAL_CELLS = 8192 and texture bytes ≤
512 MiB.
Flags.Instance.color1 (same as the 3D viewport).
Texture2D of size
texSize × texSize, drawn with
SamplerState.PointClamp (crisp bins).
white → cyan → blue → magenta/red, driven by
log(1 + count) / log(1 + maxCount) and an
intensity^0.6 compression. Minimum visible intensity is 0.15 so
isolated bins are still visible.
d_e (Å) and d_i (Å).
| Gesture | Effect |
|---|---|
| LMB click on the plot |
Selects a single bin, notifies the document
via OnRegionClicked(binDe, binDi), and stores the pixel
position for the red marker.
|
| LMB drag on the plot |
Rubber-band zoom. The selected rectangle is
converted into a new [rangeMin, rangeMax] pair and a new bin
size.
|
| LMB drag on a label |
Moves the corresponding label individually.
Supported labels: title, d_e (Å),
d_i (Å), tick numbers.
|
| RMB click on the plot | Places a red marker at the cursor and selects the bin below it (same effect as LMB click). |
| RMB double-click anywhere |
Full reset (identical to
Reset offsets + zoom).
|
| Wheel | Zooms the range around its center by a factor of 0.85 (in) or 1.18 (out). Clamped so the visible range never shrinks below 0.1 Å and never exceeds the original range. |
When a bin is selected the window calls
OnRegionClicked(binDe, binDi). The HirshfeldDocument
stores _highlightedBin and rebuilds the vertex buffer. Every vertex
whose (binDe, binDi) matches is colored bright yellow
(255, 255, 0, alpha). When the user changes the bin size or the
range, the new bins are pushed back through OnVertexBinsUpdated so
the highlight remains correct.
_btnSavePng renders the same content into a 2048 × 2048
RenderTarget2D and saves it as PNG. The label offsets, axis ranges,
and highlight state are preserved.
The data container for a computed surface. All vertex-based arrays share the same length.
| Field | Description |
|---|---|
Vertices, Indices, Normals |
Mesh data. Normals are recomputed after smoothing/relaxation. |
DE, DI, DNorm |
Per-vertex geometric properties. |
Charge, Hydrophobicity |
Per-vertex chemical properties, computed as Gaussian-weighted averages over the nearby atoms. |
ChargeMin, ChargeMax |
Range of the charge values actually present on this surface. Used for auto-scaling the color map. |
HydrophobicityMin,
HydrophobicityMax
|
Range of the hydrophobicity values on this surface. |
DEOriginal, DIOriginal,
DNormOriginal
|
Clones of the raw values, kept for reference. |
NearestInnerAtom, NearestOuterAtom |
Indices into the scene's target/outer arrays. |
NearestInnerAtomName,
NearestOuterAtomName
|
The names of those atoms (e.g. "O1", "H12").
|
TargetMoleculeId, TargetAtomCount,
OuterAtomCount
|
Metadata captured at generation time. |
Center, BoundingRadius |
Geometric center and enclosing sphere radius. |
IsoLevel, UsedAnisotropic |
Generation parameters. |
A compact result set that is passed to the fingerprint window. It contains:
FingerprintHistogram —
Dictionary<(int binDe, int binDi), int>.
FingerprintBinSize, FingerprintRangeMin,
FingerprintRangeMax.
ContactTypes — a string[] of length
VertexCount with the type of each contact (e.g.
"C···H").
ContactTypeCounts — a Dictionary<string, int>
of vertex counts per contact type.
ContactTypeAreas — a
Dictionary<string, float> of total surface area per
contact type.
ContactTypePercentages — a
Dictionary<string, float> of area percentages per contact
type.
TotalSurfaceArea — total surface area in Ų.DecomposeTableText — the pre-formatted text table ready for
copy/paste.
VertexBins — a per-vertex (binDe, binDi) table.
A diagnostic object built after each generation. It contains:
TargetCenter, RTarget,
RGrid, Margin, VoxelSize,
GridResX/Y/Z, TotalAtoms.
R_target + delta thresholds (2–8 Å), how many atoms would be
rejected.
The diagnostic is shown in the DebugWindow via the
Show Diagnostics in Debug… button. The formatting is handled by
HirshfeldDiagnosticsFormatter, which builds a list of
(text, System.Drawing.Color) lines.
x y z nx ny nz r g b), then a face list. Compatible with
MeshLab, Blender, CloudCompare.
EXP_CUTOFF = 10 the STO LUT extends to ~10 Å per atom, and
the whole generation runs in 2–5 s on a modern desktop. Increasing the voxel
size to 0.15 Å roughly halves the runtime, and lowering EXP_CUTOFF
to 8 Å roughly doubles it again — but the surface quality degrades quickly below
EXP_CUTOFF ≈ 8.
The Powder Diffraction window simulates the theoretical powder X-ray diffraction pattern for the loaded crystal structure. It is opened from Calculate → Powder Pattern....
The underlying engine (PowderDiffractionEngine) enumerates allowed hkl reflections in the 2θ range, computes the structure factor F(hkl) from the full unit cell, applies Lorentz–polarization and Debye–Waller corrections, groups symmetry-equivalent reflections, and renders the resulting profile in a WinForms plot.
The window is a DockContent composed of a large plot panel (panelPlot, a BufferedPanel) and a bottom control strip (panelControls) divided into group boxes.
| Control | Description |
|---|---|
numericMinTheta | Minimum 2θ in degrees. Range 0–90, default 5.0. |
numericMaxTheta | Maximum 2θ in degrees. Range 0–180, default 90.0. |
| Control | Description |
|---|---|
numericFWHM | Peak full-width at half-maximum (FWHM) in the units of the X axis. Range 0.01–10, default 0.05. |
comboProfile | Profile type: Gaussian, Lorentzian, Pseudo-Voigt (default). |
numericResolution | Number of profile sample points per pixel (profile resolution). Range 1–10, default 2. |
| Control | Description |
|---|---|
numericDebyeWaller | Debye-Waller isotropic B factor in Ų. Range 0–20, default 0.5. |
trackDebyeWaller | Slider linked to the numeric box (value = B × 10). Range 0–200, default 5. |
| Control | Description |
|---|---|
comboSource | X-ray source. The wavelength is derived automatically from the source (the Wavelength setter is intentionally a no-op to prevent desynchronization). |
Available sources (from XRaySource enum):
| Source | Wavelength (Å) | Energy (keV) |
|---|---|---|
| Cu Kα | 1.5406 | 8.05 |
| Mo Kα | 0.7107 | 17.44 |
| Co Kα | 1.7889 | 6.93 |
| Fe Kα | 1.9360 | 6.40 |
| Cr Kα | 2.2910 | 5.41 |
| Ag Kα | 0.5609 | 22.10 |
| Control | Description |
|---|---|
chkShowDScale | Draw a secondary top axis in d-spacing (Å) and switch the hover readout to d. Default: off. |
chkShowBackground | Fill the area under the pattern curve with a translucent brush. Default: on. |
chkLPCorrection | Apply Lorentz-polarization correction. Default: on. |
| Control | Description |
|---|---|
labelStatus | Status text ("Calculating…", "Calculated N peaks", "No peaks in range", errors). |
labelPeakCount | Number of peaks in the current range. |
labelHoverInfo | 2θ or d value under the cursor. |
labelHoverDetail | Nearest peak details: 2θ, d, (hkl), intensity. |
btnExport | Export dialog (CSV data or PNG/JPEG/BMP image). |
btnCopy | Copy the current plot bitmap to the clipboard (as bitmap + PNG bytes). |
SymetryEquivalents if available). The atomic scattering factors fj(s) use the Cromer–Mann 4-Gaussian + constant approximation (International Tables Vol. C, Table 6.1.1.4).
exp(−4 ln 2 · x²) where x = (2θ − 2θk) / FWHM.1 / (1 + 4x²).η · G + (1 − η) · L with η = PseudoVoigtEta (default 0.5).The ExportData() dialog offers two paths:
EncoderParameters), or BMP.btnCopy places the bitmap on the clipboard as both a DataFormats.Bitmap and a raw PNG stream.
The PowderDiffractionSettings class holds all numeric and boolean values as properties with defaults defined in constants (single source of truth). Clone() and ResetToDefaults() are available for programmatic use. The window synchronizes controls to settings via LoadSettingsToUI() and SaveUIToSettings().
PowderDiffractionSettings is a no-op. The wavelength is always derived from the currently selected Source. Code that assigns settings.Wavelength = … is kept for compatibility but has no effect.
Tal can define, edit and visualize two kinds of crystallographic planes:
Opened from Calculate → Planes... or from the main Planes toolbar button. It is a DockContent named PlanesWindow, docked left/right or floatable.
| Control | Description |
|---|---|
listViewPlanes | Details-view list of the current document's planes. Each item shows the plane description; the icon is a 16×16 colored square representing the plane color. The item is greyed out when Visible == false. |
btnNewPlane | Opens PlanePropertiesWindow for a new plane, shown non-modally (Show, not ShowDialog) so that the 3D view remains interactive for atom picking. |
btnEdit | Opens PlanePropertiesWindow for the selected plane. Enabled only when a row is selected. |
btnDelete | Asks for confirmation and removes the plane from the list. |
btnClose | Closes the window. |
The window maintains a color icon cache (_colorImageCache) keyed by ARGB value, so repeated planes of the same color reuse the same ImageList index.
PlanePropertiesWindow is a modal-ish dialog (border fixed, center-parent) with a tab control for the two plane types and a common options group.
Picked atom (default), Picked molecule, Ring.All, Non-hydrogen, Hydrogen.
When the Mean plane tab is active, the window enters plane selection mode in the main form. A 300 ms timer monitors the selection count and refreshes the label. Atom clicks are routed through HandleAtomPick(int atomIndex) according to the current selection mode:
selected flag and updates SelectionOrder and NumberSelected.IndividualMoleculeInCell.RingFinder.FindSmallestRing() with parameters maxRingSize = 12, requirePlanar = true, planarityTolerance = 0.15 Å. If no ring is found, falls back to selecting the single clicked atom.h, k, l. Range −20 to +20. Default all zero; if all three are zero the dialog refuses to save and shows a warning.| Control | Description |
|---|---|
chkShow | Plane visibility. Default: on. |
chkLabel | Show/hide the plane label. Default: on. |
txtLabel | Plane name. Default "mean:" or "hkl (hkl)" if empty. |
chkTransparent | Draw the plane with alpha = 0.35 instead of 0.85. Default: on. |
panelColor | Click to open a ColorDialog (FullOpen). Default red. |
On OK, SaveUIToPlane() copies the UI state to the working plane and calls CalculatePlaneGeometry(). The result is exposed via the Result property.
The least-squares mean plane is computed by MeanPlaneCalculator.CalculateMeanPlane():
Molecule.atom[i].Coordinate is stored with −MoleculeShift applied, the shift is added back so the plane is independent of re-centering.PlaneGeometry.BuildRectangle(center, normal, size), which constructs an orthonormal (u, v) basis inside the plane and returns the four corners center ± u·size ± v·size.
The plane equation coefficients A, B, C, D are stored (Ax + By + Cz + D = 0). The corners are stored in raw Cartesian (without shift), so the draw layer subtracts the current shift at render time.
PlaneGeometry.CalculateHKLPlane() uses the molecule's fractional-to-Cartesian matrix (transposed, to match Molecule.PrepareUnitCell) to convert intercept points into Cartesian space. The intercepts depend on how many Miller indices are zero:
x = 1/h, y = 1/k, or z = 1/l.
PlaneGeometry.ComputeIntersection(p1, p2) returns a PlaneIntersection struct with a point on the line, the normalized direction, and a half-length. The algorithm:
RingFinder finds the smallest ring containing a clicked atom:
maxRingSize and free of duplicate atoms are tested for planarity by PCA (smallest eigenvalue of the covariance matrix).| Property | Meaning |
|---|---|
Id | GUID. |
Name | User-facing name. |
Type | MeanPlane or HKL. |
Color | System.Drawing.Color. |
Visible, ShowLabel, Transparent | Display flags. |
SelectionMode | For mean planes: PickedAtom / PickedMolecule / Ring / All / NonHydrogen / Hydrogen. |
AtomIndices | Indices of atoms used for the mean plane. |
H, K, L | Miller indices (HKL planes). |
A, B, C, D | Plane equation coefficients. |
Center, Corners, Size | Geometry. |
Opened from the Advanced menu, this tool is used for analyzing and visualizing the macroscopic crystal habit (morphology) based on the internal crystal structure.
The morphology is predicted using the Bravais-Friedel-Donnay-Harker (BFDH) method, which relates the importance of a crystal face to its interplanar spacing (dhkl) and the lattice geometry. The tool allows you to:
The main control window is the MorphologyWindow, a DockContent that can be docked left, right, or floated. It is opened from Advanced → Crystal Morphology.... The window is divided into three vertical zones:
ListView listing every face that survived the BFDH filtering.All parameters are exposed as WinForms controls in the top panel:
| Control | Parameter | Description |
|---|---|---|
_numMaxIndex | Max Miller index | Maximum value of |h|, |k|, |l| considered when enumerating candidate planes. Range 1–6, default 3. Increasing this value generates more candidate faces (and a more complex polyhedron) at the cost of computation time. |
_numMinD | Min d-spacing (Å) | Planes with a d-spacing smaller than this value are discarded before any clipping is attempted. Range 0.1–10.0, default 0.5. This is the primary way to control the number of "small" faces that appear on the crystal. |
_numSoftening | Softening strength | Controls the amount of compression applied by the selected softening mode. Range 0.05–20.0, default 1.00 (pure BFDH). The meaning of the value depends on the mode (see below). |
_cmbSofteningMode | Softening mode | Determines how the relative growth rates are compressed. Options: None (pure BFDH), Clamp, Sqrt, Tanh, Linear. Default: None. Selecting a mode auto-fills the softening strength with a suggested value (see the table in section 10.5). |
The face table is a ListView with View.Details, FullRowSelect, and custom owner-drawing (DrawColumnHeader / DrawItem / DrawSubItem). The columns are:
| Column | Description |
|---|---|
| Face | The face name in ASCII (hkl) format (e.g. (100), (1-10), (111)). This column is drawn with a small colored square (12 px) in front of the text; the color is deterministically derived from the face name via MorphologyCrystal.NameToColor (FNV-1a hash of the name mapped to HSV hue). |
| d (Å) | The interplanar spacing of the (hkl) plane. |
| R (rel.) | The relative growth rate, R ∝ 1/d. |
| DistFrac | The fractional distance from the crystal center to the face, after softening. A value of 1.0 corresponds to the largest face; larger values correspond to smaller faces. |
| Mult | The multiplicity of the face, i.e. the number of symmetry-equivalent (hkl) planes in the crystal's space group. This is computed from the real space group operations (not a cubic approximation) via CrystalMorphologyFinder.CountEquivalentPlanesReal, which builds the orbit of the (hkl) vector under the linear part of every symmetry operation and counts the distinct canonical forms. |
| Description | A short human-readable description built from three components: the morphological role (dominant, major, minor, trace), the crystallographic role (basal a/b/c, prism ab/ac/bc, diagonal, octahedral, pyramidal, general), and the relative size (large, medium, small, tiny). |
Clicking a row highlights the corresponding face in the 3D view (all triangles with the same FaceName get a yellow outline). This is implemented via MorphologyDocument.HighlightFace(string faceName), which rebuilds a dedicated VertexBuffer (_highlightVB) containing the face's triangle edges.
The BFDH (Bravais–Friedel–Donnay–Harker) method predicts crystal morphology from the crystal structure by assuming that the growth rate of a face (hkl) is inversely proportional to its interplanar spacing dhkl:
RelativeGrowthRate(hkl) ∝ 1 / d(hkl)
Faces with large dhkl grow slowly and therefore dominate the final crystal habit. The algorithm implemented in CrystalMorphologyFinder proceeds as follows:
maxIndex are generated. The trivial (0,0,0) is rejected, as are all triples whose GCD is greater than 1 (they represent the same plane as the primitive triple). This is done by GenerateUniquePlanes.
CalculateDSpacing. Planes with d < minDSpacing are discarded.
CountEquivalentPlanesReal, which builds the orbit of (hkl) under the linear parts of all symmetry operations and counts distinct canonical forms. If the space group is unavailable or invalid, the code falls back to a cubic approximation (CountEquivalentPlanesCubic), which is clearly marked as a fallback.
RelativeGrowthRate = 1 / d.
RelativeGrowthRate / min(RelativeGrowthRate), so the largest face has DistFrac = 1.
SofteningMode (see section 10.5).
RelativeArea = 1 / DistFrac, normalized to the maximum. Note that this is a visual heuristic — the code explicitly does not multiply by multiplicity, because the physical area of a face depends on the depth of the cut (DistFrac), not on the number of symmetry-equivalent planes.
ClassifyHabitus inspects the relative areas of the {100}, {010}, {001}, {110}, {101}, {011}, and {111} families and returns one of: cubic / octahedral, plate, needle / prismatic, rhombic / dodecahedral, equant (isometric), prismatic, or unknown.
CrystalMorphologyFinder keeps a single-entry cache keyed by the molecule's cell parameters, space group, and the three calculation parameters. If the same molecule is recalculated with the same parameters, the cached CrystalMorphologyResult is returned immediately. This avoids the expensive enumeration + clipping cycle during UI experimentation. Call ClearCache() to invalidate it explicitly.
Pure BFDH (SofteningMode.None) is physically correct but often produces crystals with many very small, almost invisible faces. The softening modes are visual heuristics that compress the DistFrac distribution to produce more aesthetically pleasing or recognizable shapes. They are not physically based models and should be used with that in mind.
| Mode | Formula / Operation | Suggested strength | Effect |
|---|---|---|---|
None | identity | 1.00 | Pure BFDH. No compression. |
Clamp | if DistFrac > s: DistFrac = s | 2.50 | Hard cap on the growth rate. Small faces are pulled in toward the center. |
Sqrt | DistFrac = sqrt(DistFrac) | 1.00 | Square-root compression. Naturally reduces the spread of DistFrac values. |
Tanh | DistFrac = 1 + tanh((DistFrac - 1) · s) / s | 0.50 | Hyperbolic-tangent compression. Most flexible mode; the strength parameter controls the sharpness of the transition. |
Linear | DistFrac = 1 + (DistFrac - 1) / s | 5.00 | Linear compression around 1. Preserves relative proportions between faces. |
When the mode is changed in the combo box, the numeric up-down is automatically updated to the suggested strength for that mode (via GetSuggestedSoftening). The user can of course override this value.
The 3D crystal is displayed in a MorphologyDocument, a DockContent that hosts a MonoGame viewport. It is created automatically the first time "Calculate" is clicked in the Morphology window, and reused on subsequent clicks (the title is updated to reflect the current structure name).
The polyhedron is built by MorphologyCrystal.Build:
ConvexPolyhedronFrac.FromBox) of half-edge 3.0 · scale Å in fractional space.DistFrac ascending (largest faces first).dScaled = DistFrac · ‖g‖ · faceOffset, where ‖g‖ is the norm of the reciprocal-lattice vector of (hkl).ConvexPolyhedronFrac.ClipByPlane). The clipping is a Sutherland–Hodgman-style operation on each triangle, followed by a cap construction from the intersection points (a fan triangulation of the cap polygon, oriented to match the plane normal).MaxSuccessfulClips = 14 successful clips is enforced to keep the polyhedron manageable.Molecule.PrepareUnitCell).NameToColor) using an FNV-1a hash mapped to HSV. The starting cube's "cell-*" faces are rendered in neutral grey.MorphologyCrystal.BuildSimpleTest(), which builds a rhombic dodecahedron-like test shape so the user can verify that the 3D viewer itself is working.
Camera instance; the default position is (0, 0, 100) looking at the origin.GetRotationMatrix() so the Wulff plot can follow it.40 / BoundingRadius) that keeps the crystal nicely framed.Flags.Instance.PerspectiveMode.TrackBar with a live numeric label).SpriteFont1 and respects Flags.LabelScale, Flags.LabelColor, Flags.LabelShadow, and Flags.LabelShadowColor. Faces with duplicate names share a single label at their combined centroid.ColorDialog."Zoom: X.XX× (mouse wheel)".Edges are drawn only along the true boundaries between two different faces. The algorithm is deliberately careful about numerical robustness:
EPS_world = BoundingRadius · 1e-4 (in the crystal's original scale, not after any zoom or scale transform).t ∈ (tEps, 1 − tEps), perpendicular distance < 4 · EPS_world). Vertices that pass are sorted by t and the edge is split into a chain. This removes the visual gaps that appear at T-junctions where two triangles of the same face share a boundary with a third triangle of a different face.GetFaceId: if the triangle has a non-empty FaceName, that name is used; otherwise a synthetic ID is built from the sign of the triangle normal.
When the user selects a row in the face table, MorphologyWindow calls MorphologyDocument.HighlightFace(faceName) with the selected face name (or null to clear). This rebuilds the highlight vertex buffer by:
FaceName equals the requested name.255, 255, 0, 255) line segment for every edge that appears in exactly one triangle of the selected face.DepthStencilState.None so it always appears on top of the crystal, regardless of depth ordering.
The Wulff plot is opened by clicking the Wulff Plot button in the Morphology window. It opens a WulffPlotDocument, a DockContent that renders a stereographic projection of the crystal faces. This is the standard graphical tool in crystallography for visualizing the angular relationships between faces and the symmetry of the crystal.
Each face is represented by its unit normal vector n in Cartesian space. The projection is defined with the +c axis at the center of the plot:
θ = acos(n.z) // polar angle from +c
φ = atan2(n.y, n.x) // azimuthal angle in the ab plane
r = tan(θ / 2) // stereographic radius (gnomonic-scaled)
x_plot = r · cos(φ)
y_plot = r · sin(φ)
The factor tan(θ/2) is the standard Wulff-net stereographic projection (equal-angle). Faces close to +c project near the center; faces in the ab plane (θ = 90°) project onto the outer circle. Lower-hemisphere faces (n.z < 0) are mirrored onto the upper hemisphere for display (their z component is negated before projection), and are drawn as hollow circles; upper-hemisphere faces are drawn as filled circles.
min(screenW, screenH) / 2 · 0.85, multiplied by the current zoom factor. This keeps angular relationships correct regardless of the window aspect ratio.Show Equator is enabled.SpriteFont1, respecting the global label settings from Flags.MorphologyDocument.GetRotationMatrix() and multiplied by the same view matrix used by the 3D renderer (Matrix.CreateLookAt((0,0,100), Zero, Up)) so that the two views are visually consistent.t = d / maxD as (255·t, 100 + 100·(1 − |2t − 1|), 255·(1 − t)).Upper only, Lower only, Both (default). Filters the projected points by the sign of n.z.The only mouse interaction is the wheel: scrolling up multiplies the zoom factor by 1.15, scrolling down by 0.87. The zoom factor is clamped to 0.3–5.0.
Three separate VertexBuffers are maintained and rebuilt whenever the plot needs to be refreshed:
_circleVB — the outer circle (128 segments, LineList)._gridVB — polar grid, radial lines, and equator (LineList)._pointsVB — the face markers, drawn as triangle fans (upper hemisphere) or triangle strips (lower hemisphere rings) (TriangleList).
The plot background is taken from Flags.Instance.color1, matching the 3D viewport so that screenshots and the on-screen plot have consistent backgrounds.
Opened from the Advanced menu, the 🔬 Structure Overlay window is a powerful tool for comparing multiple molecular structures.
This tool is invaluable for comparing different conformations of a flexible molecule, or comparing a calculated structure with an experimental one.
The main control window is the MoleculeOverlayWindow (a DockContent named "🧬 Overlay Manager"), opened from Advanced → Overlay Manager.... It can be docked left, right, or floated, and holds an OverlayManager instance that is the shared data model for the whole overlay feature. The window is laid out as three vertical panels:
ListBox of overlay entries (custom owner-drawn so each entry can be colored with the molecule's actual color).None, Mirror, Inversion) and the status label.
Every entry in the overlay is an instance of OverlayMoleculeData, which holds:
| Property | Meaning |
|---|---|
Id | Unique integer ID within the overlay, assigned sequentially by OverlayManager._nextId. |
Name | User-facing name (e.g. "Molecule 1", "Selection 2", "Structure 3"). |
SourceName | Name of the document the entry was extracted from (used for the tooltip and the source column in the list). |
OriginalAtoms | Immutable copy of the atoms as they were when the entry was created. This is the "source of truth" — all subsequent transformations, mirror operations, and fits are computed from these. |
WorkingAtoms | The current, transformed copy of the atoms. This is what is rendered and what the fitting algorithm modifies in-place. |
OriginalReferencePoints | Array of exactly 3 Vector3 positions taken from the 3 selected atoms at the time the entry was created. Used as the source basis for fitting. |
WorkingReferencePoints | Current positions of the 3 reference points after transformations and fits. |
ReferencePointNames | Names of the 3 reference atoms (for display and debugging). |
ReferencePointIndices | Indices of the 3 reference atoms in the source molecule. |
HasReferencePoints | True if 3 reference points were successfully captured. |
CurrentTransformation | One of TransformationMode.None, Mirror, RotationY, RotationZ, SymmetryCenter. |
Color | The per-molecule tint applied to all atoms (a Vector3 RGB). |
IsVisible | Whether the molecule is rendered in the Overlay View. |
IsStructure | True for entries added via "Structure" (whole crystal). |
IsSelection | True for entries added via "Add Selection" (an arbitrary set of picked atoms). |
The class exposes two important methods: InitializeWorkingData() (deep-copies OriginalAtoms into WorkingAtoms and stores the original ADP matrices) and ResetToOriginal() (reverses the process, restoring the working atoms from the originals while preserving the current color and style).
OverlayManager owns the list of OverlayMoleculeData entries and exposes a small API used by the window. The public surface is:
IReadOnlyList<OverlayMoleculeData> OverlayMolecules — read-only view of the current entries.int TotalAtomCount — sum of WorkingAtoms.Count across all entries.event EventHandler MoleculesChanged — raised whenever the list or any entry's state changes, so the window and any overlay views can refresh.AddSelectedMolecule(doc, mode) — adds the whole molecule containing the selected atoms as a single entry.AddSelectedAtoms(doc, mode) — adds just the selected atoms as a single entry (marked IsSelection = true).AddSelectedStructure(doc, mode) — adds every atom of the document's molecule (including symmetry-generated ones) as a single entry (marked IsStructure = true).RemoveMolecule(id) — removes an entry by ID and raises MoleculesChanged.ClearOverlayMolecules() — removes all entries.ToggleVisibility(id) — flips the IsVisible flag.SetMoleculeColor(id, color) — sets the per-entry color and propagates it to every CustomColor field of the working atoms.SetMoleculeStyle(id, styleIndex) — sets the per-entry style (0 = CPK, 1 = Sticks, 2 = Balls and Sticks, 3 = Ellipsoids).SetTransformationForMolecule(id, mode) — the main transformation entry point (see below).FitMoleculeToPoints(source, sourcePoints, targetPoints) — the fitting entry point (see below).IsBaseMolecule(id) / GetBaseMoleculeId() — helpers for the "base molecule" concept (see section 11.6).
The Overlay Manager exposes three "Add" buttons, each backed by a different method of OverlayManager. All three share the same underlying extraction logic, but differ in which atoms are collected:
AddSelectedMolecule): collects all atoms of the molecule that contains the currently selected atoms. The molecule ID is taken from IndividualMoleculeInCell. If the source document has no molecule numbering (single-molecule file), all atoms are added as one entry. This is the most common case.
AddSelectedAtoms): collects only the currently selected atoms. This produces an entry marked IsSelection = true and is useful for comparing, e.g., just a ring system or a metal coordination sphere. Note: the current version of the Molecule Overlay window does not expose a button for this method — the button is commented out in the source, but the API remains available for programmatic use.
AddSelectedStructure): collects every atom of the source document's molecule, including all symmetry-generated atoms. This produces an entry marked IsStructure = true. Use this when comparing two whole crystal packings.
In all three cases, the first 3 selected atoms are stored as reference points. If fewer than 3 atoms are selected, the entry is still created, but HasReferencePoints is false and the automatic fitting (section 11.5) is skipped.
The core of the overlay feature is the automatic fitting algorithm, implemented as OverlayManager.IterativeFit(sourceMolecule, sourcePoints, targetPoints). Despite the name "iterative", the current implementation is a single-pass, closed-form rigid-body transformation that maps a source reference frame (defined by the 3 source points) onto a target reference frame (defined by the 3 target points).
xs = normalize(P1_source − P0_source)
zs = normalize(cross(xs, P2_source − P0_source))
ys = cross(zs, xs) // already unit length
The three vectors (xs, ys, zs) form a right-handed orthonormal frame anchored at P0_source.
P0_target. Because the construction is identical, the two bases have the same handedness, and the map between them is guaranteed to be a proper rotation (determinant +1, no reflection).
p (in the source frame), its new position is:
v = p − P0_source
a = dot(v, xs), b = dot(v, ys), c = dot(v, zs)
p_new = P0_target + a · xt + b · yt + c · zt
This is the classic "re-express in the source basis, then rebuild in the target basis" trick. It implements a pure rotation + translation, with no scaling and no reflection. The three source points are mapped exactly onto the three target points by construction.
RecalculateBondsForMolecule, which rebuilds the per-atom bond lists, distances, angles, and precomputed bond matrices from the new coordinates. This is necessary because the overlay atoms are rendered with the same bond-drawing code as regular molecules.
P1_source == P0_source (or the equivalent for target), the algorithm logs an error and returns without modifying the molecule. If the three points are collinear, the cross product in step 1 has near-zero length, the basis cannot be built, and the fit is aborted. In practice this means you should always pick three atoms that are not collinear — e.g., three atoms of a ring, or an atom plus two of its substituents.
AddSelectedMolecule (and its siblings) calls AutoFitMolecule at the end. The new molecule is fitted onto the base molecule (the first entry in the list).RbTransformation_CheckedChanged calls AutoFitSelectedMolecule(), which re-fits the molecule onto the base using the (possibly mirrored) reference points.The first entry in the overlay list is special: it is the base molecule (marked with ⭐ BASE in the list). All subsequent molecules are fitted onto the base. To preserve a stable reference, the following operations are disabled for the base molecule when the overlay contains more than one entry:
The only operation that remains enabled for the base is Color: changing the color of the base is purely cosmetic and does not affect any fit.
When the overlay contains exactly one entry, all operations are enabled for that single molecule (it is not yet "base" in any meaningful sense). When the overlay is empty, all operations are disabled.
Before fitting, the user can apply one of three transformations to a non-base molecule. These are exposed as radio buttons in the bottom panel:
| Mode | Operation | Effect |
|---|---|---|
None | identity | No transformation. The molecule is fitted as-is. |
Mirror | (x, y, z) → (x, y, −z) | Reflection across the XY plane. Implemented by ApplyMirrorToMolecule(id), which resets the working atoms from the originals and then flips the Z coordinate of every atom and reference point. This is useful for comparing enantiomers or mirror-related conformations. |
Inversion | (x, y, z) → (−x, −y, −z) | Inversion through the origin (a.k.a. symmetry center). Implemented through the generic SetTransformationForMolecule path with TransformationMode.SymmetryCenter, which applies Matrix.CreateScale(-1) to every working atom. Mathematically, inversion is equivalent to a mirror plus a 180° rotation, and since the 3-point fit optimizes rotation, the net effect on the fitted result is often indistinguishable from a pure mirror. The distinction is kept for clarity and for potential future use with non-centrosymmetric reference frames. |
Transformation is applied before the automatic fit. The workflow is:
RbTransformation_CheckedChanged verifies the molecule is not the base (otherwise it restores the previous radio state and shows a "Cannot transform BASE molecule" status).SetTransformationForMolecule(id, mode) is called. It resets the working atoms from the originals, preserves the current color and style, and applies the appropriate transform matrix.AutoFitSelectedMolecule() is called, which re-fits the (now transformed) molecule onto the base using the reference points.
Each overlay entry has its own color, chosen from a random hue at creation time (via GetRandomColor, which picks a random hue and converts to RGB with a sine-based palette). The color is applied to every atom's CustomColor field, so the whole molecule is rendered in a single, distinct color.
To change the color of the selected molecule, click the colored button in the top panel (its background is the current molecule color). A standard ColorDialog opens; on OK, the color is applied via SetMoleculeColor and the list is refreshed. Color can be changed even for the base molecule — it is the only operation that remains enabled for the base.
The style of an overlay entry can be changed programmatically via SetMoleculeStyle(id, styleIndex), but this is not currently exposed as a UI control in the Overlay Manager window. The available styles are 0 = CPK, 1 = Sticks, 2 = Balls and Sticks (the default), 3 = Ellipsoids.
The Show button in the Overlay Manager opens (or brings to front) the OverlayViewWindow, a DockContent hosting a MonoGame viewport that renders all visible overlay atoms. The window is subscribed to OverlayManager.MoleculesChanged, so any change to the overlay (add, remove, transform, color change, visibility toggle) is reflected immediately.
OverlayViewWindow.UpdateDisplay() collects the WorkingAtoms of every visible entry and passes them to the underlying GameWindow via SetOverlayAtoms(). The atoms are rendered with the same pipeline as regular molecules, so all display styles (CPK, balls & sticks, ellipsoids) and per-atom colors work as expected. Bond detection is done per-molecule (the overlay does not merge bonds across molecules), so an intermolecular contact in the overlay is not drawn as a bond — this is intentional, since the overlay is meant for comparing molecular geometries, not crystal packings.
OverlayViewWindow is created with the current OverlayManager and docked as a document.FixExistingOverlayView() detects it and injects the current manager if the existing one is null. This prevents the classic "Overlay View is empty after reloading the layout" bug.MoleculesChanged and clears its atom buffer.Ctrl+click). Click three atoms that define a unique, non-collinear frame — for example, three atoms of a phenyl ring, or a central atom plus two substituents. You should see them highlighted in yellow.Advanced → Overlay Manager...). The list is initially empty.Show All to toggle visibility of the whole overlay.Opened from the menu (or the corresponding toolbar / context command), the H-Bond Settings window controls how hydrogen bonds are detected and drawn in the 3D view.
Two modes are available:
Min/Max = rvdW(donor) + rvdW(acceptor) ± value
Minimum angle (degrees) — geometry filter. A candidate is accepted only if the angle ∠D–H···A is at least this value (default 63°). Typical strong H-bonds are closer to 180°; lower thresholds also accept weaker / bent interactions.
Tick which element types may act as donors and acceptors (e.g. N, O, F, P). Only checked pairs are considered. H-Bond color sets the drawing colour in the 3D view (default green).
Use Default to restore factory settings, Apply to recalculate without closing the window, or OK to apply and close.
After detection, the Structure Info / H-bond panel lists each bond in a table. Column meanings:
| Column | Meaning |
|---|---|
# | Row number. |
Donor | Donor heavy atom (D), e.g. O1, O2. |
H | Hydrogen atom involved in the bond (if present). |
Acceptor | Acceptor atom (A), e.g. N1, N2. |
D···A | Distance donor–acceptor (Å). |
H···A | Distance hydrogen–acceptor (Å). |
H···A − |
Difference of H···A relative to the VdW (or chosen) criterion (negative = shorter / stronger contact than the reference sum). |
∠D–H···A | Angle at the hydrogen (degrees). |
Intermolecular |
Yes = different molecules; No = intramolecular.
|
Symm.op.1 / Symm.op.2 |
Symmetry operations applied to the first and second partner
(e.g. x,y,z, 1−x,1/2+y,3/2−z).
|
Cell.1 / Cell.2 |
Unit-cell translations of each partner as (tx, ty, tz), e.g. (0,0,0) and (1,0,1). |
Example (illustrative):
| # | Donor | H | Acceptor | D···A | H···A | H···A − | ∠D–H···A | Intermolecular | Symm.op.1 | Symm.op.2 | Cell.1 | Cell.2 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 1 | O1 | H1 | N2 | 2.769 | 1.965 | −0.785 | 166.3 | Yes | x,y,z | 1−x,½+y,3/2−z | (0,0,0) | (1,0,1) |
| 2 | O2 | H2 | N1 | 2.599 | 1.869 | −0.881 | 147.7 | No | x,y,z | x,y,z | (0,0,0) | (0,0,0) |
Row 1 is an intermolecular O–H···N bond to a symmetry-related
molecule in a neighbouring cell. Row 2 is an intramolecular
O–H···N contact within the same asymmetric unit (both partners at
x,y,z, cell (0,0,0)).
Refer to section 4.6 for a detailed description of this window.
Opened from the Window menu, this window shows a detailed log of symmetry operations found in the loaded structure. It's a diagnostic tool for developers and advanced users to troubleshoot issues with symmetry or file parsing.
Tal.Forms.BufferedPanel is a System.Windows.Forms.Panel subclass that enables DoubleBuffered plus AllPaintingInWmPaint, OptimizedDoubleBuffer and UserPaint control styles. It is used wherever smooth custom drawing is required (e.g., the powder diffraction plot).
Opened from Tools → Structure Info. The StructureInfoWindow is a dockable panel (a DockContent) that acts as the central, always-available reference for every numeric and textual property of the currently loaded structure. It replaces the need to dig through the raw file or the debug log to find a specific value: every quantity the parsers extracted (cell parameters, symmetry operations, refinement statistics, atomic coordinates with uncertainties, contacts, H-bonds, measured distances, etc.) is laid out in a clean, scrollable table.
The window is divided into four regions:
panelTop): a read-only txtCurrentStructure showing the identifier of the structure currently loaded into the window (e.g., the CIF data block name or the file name), plus a btnCustomise button reserved for future per-window customization.panelButtons): a vertical stack of section-switching buttons. Exactly one is highlighted (back-coloured with a light blue) at any time to indicate the active section.panelTableScroll): an auto-scrolling container hosting a single TableLayoutPanel (table). The panel is a BufferedPanel, so the table redraws without flicker when the user switches sections.panelBottom): a Close button.Each button populates the table with a dedicated Section object (a small inner class that holds a list of column headers and a list of string rows). The current sections are:
| Button | Columns | Content |
|---|---|---|
Structure |
Property, Value |
General identification (identifier, literature reference, DOI, publication title, journal, audit creation date/method), chemical metadata (formula, compound name, space group), unit cell parameters with e.s.d.'s (a, b, c, α, β, γ, volume, Z/Z', disorder, polymorph), full refinement statistics (R1 observed / all, wR2 all / observed, GooF, reflections used, parameters refined, restraints, shift/su max and mean, Δρ max/min/RMS, weighting scheme), reflection statistics (Reflns total, observed, threshold, resolution d_min and d_max, θ_min, θ_max), and experiment details (F(000), wavelength, radiation type, measurement device, temperature, crystal colour, crystal shape, crystal size, absorption coefficient, absorption correction type, absorption T_min/T_max, density, R-factor if only the legacy value is present). Fields that are not present in the source file are simply omitted from the table. |
Diagram |
Message |
Placeholder for a future graphic schematic of the structure. |
Atoms |
Name, El, x, y, z, σx, σy, σz, Occ, Ueq |
One row per atom in the asymmetric unit. Coordinates are fractional. Ueq is computed on the fly as the trace of the anisotropic Ue matrix divided by 3. |
Bonds |
#, Atom1, Atom2, Length (Å), Error, Occ |
Every unique covalent bond, deduplicated via a sorted (i, j) pair set. The error column is an e.s.d. estimate computed from the two atoms' Coordinate_error vectors projected along the bond direction (see 12.5.3). Occupancy is the minimum of the two atoms' occupancies. |
Contacts |
#, Atom1, Atom2, Length, Length-VdW, Symm.op.1, Symm.op.2, Expanded, X1, Y1, Z1, X2, Y2, Z2, Xfrac1, Yfrac1, Zfrac1, Xfrac2, Yfrac2, Zfrac2 |
Every unique short contact detected by the current Define Short Contacts settings. The Length-VdW column is d − (r_vdw(1) + r_vdw(2)) — negative values indicate closer-than-van-der-Waals contacts. Symmetry operation strings include the cell shift (e.g. x+1,y,z). Rows where Cell.1 or Cell.2 is non-zero are given a warm background to draw attention to the neighbouring cell. |
H-Bonds |
#, Donor, H, Acceptor, D⋯A (Å), H⋯A (Å), Lenght-VdW, ∠D-H⋯A (°), Intermolecular, Symm.op.1, Symm.op.2, Cell.1, Cell.2 |
Every unique D-H⋯A contact. The donor is identified by scanning for a bonded neighbour with Z ∈ {7, 8, 9} (N, O, F); the acceptor comes from the H_BondedAtom list. The H⋯A − VdW column is d(H⋯A) − (r_vdw(H) + r_vdw(A)). The Intermolecular flag is computed as (cell shifts differ) OR (individual molecule indices differ). |
Centroids |
Message |
Placeholder for a list of calculated ring/molecular centroids. |
Planes |
#, Type, hkl / Info, A, B, C, D |
Every plane currently registered in the active document. For HKL planes the info column shows hkl (h k l); for mean planes it shows the selection mode's description. |
Symmetry |
Number, Symm. Op., Description, Detailed Description, Order, Type |
The full list of symmetry operations for the space group, parsed and formatted as fractional-coordinate strings (e.g. -x,1/2+y,1/2-z). Each operation is classified (identity, inversion centre, 2-fold rotation, general) by counting the sign flips in the linear part. |
Distances |
#, Atom 1, Atom 2, Distance (Å), Error |
Every measurement of type 1 (atom-atom distance) and type 4 (atom-plane distance) currently registered by the Measure tool. The arrow (→plane) is used in the second column to indicate atom-plane measurements. |
Angles |
#, Atom 1, Atom 2, Atom 3, Angle (°), Error |
Every A-B-C angle derived from the covalent bond graph: for each atom B with at least two bonded neighbours, every unordered pair (A, C) of its neighbours defines an angle. Duplicates are removed via a sorted triple key. The list is capped at 50,000 entries to keep the UI responsive on very large systems. |
Torsions |
#, Atom 1, Atom 2, Atom 3, Atom 4, Torsion (°), Error |
Every A-B-C-D torsion derived from the covalent bond graph: for each covalent bond B-C, every pair (A, D) of neighbours of B and C (excluding the bond partners themselves) defines a torsion. The dihedral angle is computed with the standard atan2-based formula (see 12.5.4). The list is capped at 50,000 entries. |
All Angles |
#, Atom 1, Atom 2, Atom 3, Angle (°), Error |
Placeholder for a more exhaustive angle list (including non-bonded atoms). |
All Torsions |
#, Atom 1, Atom 2, Atom 3, Atom 4, Torsion (°), Error |
Placeholder for a more exhaustive torsion list. |
For every geometric quantity, the window reports an estimated standard uncertainty (e.s.d.). The formulas are consistent with the ones used by the Measure tool, and propagate the per-atom positional errors stored in Coordinate_error (which already include the cell-parameter contribution):
p1, p2 with errors e1, e2 and d = |p1 − p2|, then
σ_d² = (dx/d)²·(e1x² + e2x²)
+ (dy/d)²·(e1y² + e2y²)
+ (dz/d)²·(e1z² + e2z²)
where (dx, dy, dz) = p1 − p2.
σ_angle ≈ √2 · mean(|e1|, |e2|, |e3|) / min(l1, l2) [rad]
→ σ_angle · 180/π [deg]
clamped to [0.001°, 10°] to avoid absurd values for near-degenerate geometries.
σ_torsion ≈ √2 · mean(|e1|, |e2|, |e3|, |e4|) / l_BC · 180/π
where l_BC is the length of the central bond, clamped to [0.001°, 30°]. This reflects the fact that torsions are most sensitive to errors when the central bond is short.
When an error is too small to be meaningful (or the source file did not provide positional errors), the cell shows an em-dash (—) instead of a number.
The dihedral angle A-B-C-D is computed by:
a = A − B
b = C − B
c = D − C
d = B − C
n1 = normalize(cross(a, b))
n2 = normalize(cross(c, d))
angle = acos(clamp(dot(n1, n2), -1, 1))
if dot(cross(n1, n2), normalize(b)) < 0:
angle = −angle
The sign convention matches IUPAC: a positive torsion is a clockwise rotation of the far bond when looking along the central bond from B to C. The result is in radians internally, and converted to degrees for display.
Section objects in a Dictionary<string, Section>. When the structure changes (a new file is loaded or the active document switches), LoadStructure re-populates every section from the new Molecule, then re-fills the currently visible table.Angles and Torsions sections are special: they are populated lazily, only when the corresponding button is clicked. This avoids the cost of enumerating up to 50,000 rows when the user is only interested in, say, the unit cell parameters.PopulateSections and simply re-displayed.
The window uses the classic crystallographic "value(esd)" notation. For example, a cell parameter a = 5.6789 with a_e = 0.0012 is rendered as 5.6789(12). The number of decimal places is chosen from the magnitude of the e.s.d.: if error < 1, the number of decimals is the position of the first significant digit of the error. Volume is computed from the cell parameters and its e.s.d. is propagated analytically from the partial derivatives with respect to a, b, c, α, β, γ.
All quantities displayed by the Structure Info Window are extracted by the parsers from the source file. The mapping is fully deterministic — a value appears in the table only if the corresponding field is present in the CIF (or RES/INS) file. Missing fields are silently omitted; the window never invents data.
_cell_length_* and _cell_angle_* tags, or from the RES CELL/ZERR cards._cell_volume if present; otherwise computed from a, b, c, α, β, γ._space_group_IT_number, _space_group_name_H-M_alt / _symmetry_space_group_name_H-M; RES reads TITL and LATT._space_group_symop_operation_xyz loop; RES reads the SYMM cards. The full list is displayed verbatim in the Symmetry section._atom_site_* loop (label, type symbol, fractional x/y/z, x/y/z esd's, occupancy, Uiso, ADP type, disorder group, and the six Uij components when _atom_site_adp_type = Uani); RES reads the atom cards with the SFAC mapping._refine_ls_R_factor_gt → R1 observed._refine_ls_R_factor_all → R1 all._refine_ls_wR_factor_ref / _refine_ls_wR_factor_all → wR2 (all / observed)._refine_ls_goodness_of_fit_ref → GooF._refine_ls_number_reflns, _refine_ls_number_parameters, _refine_ls_number_restraints → reflection/parameter/restraint counts._refine_ls_shift/su_max, _refine_ls_shift/su_mean → shift/su max and mean._refine_diff_density_max, _refine_diff_density_min, _refine_diff_density_rms → residual electron density._refine_ls_weighting_scheme and _refine_ls_weighting_details → weighting scheme.REM lines (R1, wR2, GooF, Nref, Npar, Nres, shift/su max/mean, Δρ max/min)._diffrn_reflns_number → total reflections._diffrn_reflns_av_R_equivalents and the _reflns_number_gt / _reflns_number_total tags → observed reflections and threshold._diffrn_reflns_theta_min, _diffrn_reflns_theta_max → θmin, θmax._diffrn_reflns_resolution_high (or _reflns_d_resolution_high), _diffrn_reflns_resolution_low → resolution limits dmin, dmax._reflns_threshold_expression → the threshold column._diffrn_radiation_wavelength, _diffrn_radiation_type → wavelength and radiation type._diffrn_measurement_device_type → measurement device._diffrn_ambient_temperature → temperature (K)._exptl_crystal_colour, _exptl_crystal_description → crystal colour and shape._exptl_crystal_size_max, _exptl_crystal_size_mid, _exptl_crystal_size_min → crystal size._exptl_absorpt_coefficient_mu → absorption coefficient μ._exptl_absorpt_correction_type, _exptl_absorpt_correction_T_min, _exptl_absorpt_correction_T_max → absorption correction type and transmission range._exptl_crystal_density_diffrn → calculated density._chemical_formula_sum, _chemical_formula_weight, _chemical_name_common / _chemical_name_systematic → formula, formula weight and compound name._cell_formula_units_Z, _cell_formula_units_Z' → Z and Z'._exptl_crystal_F_000 or _cell_formula_units_Z × _chemical_formula_weight → F(000) if present._audit_creation_date, _audit_creation_method → audit lines._publ_section_title → publication title._journal_name_full / _journal_name_abbrev → journal name._journal_volume, _journal_page_first, _journal_year → journal reference._journal_doi or _publ_doi → DOI._chemical_melting_point, _chemical_compound_source etc. — read if present; otherwise omitted.CifParser, ResParser) fill a CifData / ResData container. StructureStorage.ConvertCifToStructure / ConvertResToStructure then copies every present field into a StructureData, and finally Molecule.CopyMetadataFromStructure / Molecule.CopyMetadataFromRes mirrors the values into the live Molecule object. The Structure Info Window reads its data exclusively from that Molecule instance — i.e. from the parsed CIF/RES file, never from a runtime-generated value. Anything the file did not contain is not shown.
StructureWriter.Save(structure, path) inspects the file extension and dispatches to the appropriate writer. Returns true on success. Exceptions are caught and logged.
Writes a StructureData to a CIF file using the IUCr data-name rules. It emits: data_ block, audit lines, chemical metadata, cell parameters (with esd where available), _space_group_IT_number, H-M name, symmetry-operation loop (never guessed — via SymmetryHelper.GetOperationStrings), atom-site loop (label, type, fract coords, occupancy, U_iso, adp_type, optional disorder group), and an anisotropic displacement loop when any atom has AdpType == "Uani".
Number formatting uses CultureInfo.InvariantCulture. The file is written as UTF-8 without BOM.
Writes a SHELX-format .res file: TITL, CELL, ZERR, LATT, SYMM cards (identity excluded; inversion sign of LATT inferred from SymmetryHelper.ContainsInversion), SFAC/UNIT, atom lines, HKLF 4, END. Occupancy is written as occ + 10 (SHELX free-variable convention). Anisotropic atoms emit the full 6-element U line with the second-line convention.
Writes atom count, comment line (structure name), and one Element x y z line per atom. Coordinates are written as stored (Cartesian for non-crystal formats). UTF-8 encoding.
Writes HEADER, TITLE, an optional CRYST1 record (when a cell is present, with the H-M name truncated to 11 chars and the space-group number), one HETATM record per atom (serial, 4-char name, "UNK" residue, residue 1, x/y/z, occupancy, B-factor derived from U_iso as B = 8π²·U, element), and END.
Writes an MDL molfile V2000: header (title truncated to 80 chars, " TAL 3D", blank), counts line (atoms, bonds = 0), atom lines in fixed format (%10.4f%10.4f%10.4f %-3s + zeroed flags), and M END.
Writes a Tripos MOL2 file with @<TRIPOS>MOLECULE, @<TRIPOS>ATOM, and optionally @<TRIPOS>CRYSIN when a cell is available (a, b, c, α, β, γ, space-group number, setting 1). Charges are not written (charge type NO_CHARGES).
Writes a Gaussian input file with a %chk= link, a #p B3LYP/6-31G(d) Opt route, the structure title, a default charge/multiplicity line 0 1, and the Cartesian atom block. ASCII encoding.
Writes a CRYSTAL17 geometry + SCF input deck. The writer performs the non-standard-setting check via SpaceGroupTransformer.GetTransformToStandard() and, on user confirmation, transforms a deep copy of the structure (the original in StructureStorage is untouched). The output includes the title line, CRYSTAL, the geometry flag 0 0 0, the space-group number, the cell line, the number of atoms, and one atom line per atom (Z, x, y, z, optional label). The settings block emits OPTGEOM / FULLOPTG / ENDOPT, BASISSET POB-TZVP, DFT B3LYP-D3 XLGRID, SCFDIR, memory sizes, SHRINK 8 8, LEVSHIFT 6 1.0, and TOLINTEG 7 7 7 7 25.
CrystalSpaceGroupValidator.Validate(structure) returns a warning string when the structure is not in the standard setting; otherwise null. The comparison is always done on the normalized H-M name.
PovRayGenerator(Molecule) produces a complete POV-Ray 3.7 scene file.
global_settings { assumed_gamma 2.2; max_trace_level 10 }.right, and an angle converted from XNA's vertical FOV to POV-Ray's horizontal angle via angle = degrees(2·atan(tan(vFov/2)·aspect)). The CAMERA_FOV_STRETCH constant (1.0) is applied after conversion.Flags; a global ambient is declared as #declare GlobalAmbient.color2 (bottom) and color1 (top). A sky_sphere fallback is used if no camera is available.RuntimeIcosphere.BASE_RADIUS. Balls & sticks draw two cylinders per bond (each half colored by its atom). Ellipsoids are drawn as a unit sphere transformed by Transformation · Un · Scale(probability) · Translation(position), with the model scaled by rModel.ShowHydrogenIsoSpheres is false, H atoms are drawn as small isotropic spheres; otherwise as full ellipsoids.Flags.HBondColor; dash/gap lengths are read from flags.AngleZ and AngleXY and the color saturated by _saturationBoost.Presentation == 4, each Polihedrals.VertexesList entry is written as a mesh2 with per-vertex normals, transparent faces, and an inside_vector.Molecule.Planes are written as double-sided mesh2 quads (two triangles) with thin cylinder edges. Corners are shifted by −MoleculeShift to align with the atom coordinates.mesh2. If the draw mode is CavityColorMap and per-vertex colors are available, the mesh uses texture_list and per-face texture indices; otherwise a single transparent pigment is used.Scale(cam.OneOverZoom) · Rotate · Translate is emitted once at the end of the union.IsValidSegment rejects any cylinder shorter than MIN_SEGMENT_LENGTH = 1e-4 to avoid POV-Ray "degenerate cylinder" errors.IsFiniteVector rejects NaN/Inf coordinates.| Constant | Value | Effect |
|---|---|---|
_globalBrightness | 1.00 | Global light scale. |
_lightingContrast | 0.80 | Controls ambient/diffuse/phong factors. |
_phongBoost | 2.70 | Multiplier on the phong value. |
_saturationBoost | 2.30 | Color saturation boost (around the luminance axis). |
_emissionStrength | 0.18 | Self-illumination fraction of the base color. |
Transforms a StructureData from one setting of a space group to another equivalent setting (e.g. P2₁/c ↔ P2₁/n).
TransformTo(structure, targetSettingName) — main entry point. Returns a TransformResult with success flag, applied transform, target setting name, and message.ApplyTransformToStructure(structure, transform, targetName, wrapAtoms = false) — applies the transform in place: fractional coordinates, ADP tensors (U' = R·U·Rᵀ), cell parameters (M' = M·P), symmetry operations (regenerated for the target setting) and the space-group name.ApplyTransform(structure, transform) — low-level version; atoms + ADP + cell only, no symmetry/name update.ResolveTransform(structure, targetSettingName) — finds the transform matrix. It first tries a hard-coded lookup and verifies it against the operation sets; if that fails it falls back to a brute-force search over 3×3 integer matrices with determinant ±1 and 27 candidate shifts.GetStandardName(group) — returns the standard H-M name for a space group number (first entry with orientation 0).GetAvailableSettings(group) — returns all distinct setting names for a group.GetCurrentSetting(structure) — returns the setting name currently stored in the structure (matched by normalized name against the internal table).GetOperationStringsForSetting(group, settingName) — returns the list of operation strings for a given setting.ReadSymm) from the internal SymmetryElements table.null (nothing to do).M' = M · P where P = Q⁻¹, regenerate the symmetry operations, and set the new name.
RefineMoleculePositionsAfterTransform(structure, bonds, maxIterations) performs an iterative relaxation of atoms around their covalent bonds (minimum-image convention), followed by an "attractor" step that pulls smaller connected fragments toward the largest one using integer fractional shifts of up to ±2. Finally, each molecule is translated so that its heaviest non-hydrogen anchor sits near (0.5, 0.5, 0.5) — a whole-molecule translation, not a wrap.
Helper methods: DetectBondsInStructure (minimum-image covalent-bond detection with element-pair thresholds), DetectMoleculesSimple (BFS over bonds), and VerifyRoundTrip (round-trip diagnostic for A→B→A).
The application exposes a set of parsers, each of which populates a data container and then converts to a StructureData:
| Parser | Container | Notes |
|---|---|---|
CifParser | CifData | Full IUCr CIF: cell (with esd), symmetry (operations + group name/number, with fallback detection from the operation set), chemical metadata, refinement, reflections, experiment, publication. ADP (Uiso/Uani) and disorder are handled. ParseMultiBlock returns one CifData per data_ block. |
ResParser | ResData | SHELX RES/INS. Merges continuation lines, parses TITL (extracts the space group via multi-pattern regex), CELL, ZERR, SYMM, SFAC (with optional scattering coefficients), FVAR, PART, TEMP, and REM lines (R1, wR2, GooF, parameters, restraints, Δρ, F(000), shift/su). Uses a two-pass symmetry strategy: validate file operations against the internal database and prefer the database when the file is incomplete. Second pass: recalculates U_iso for geometrically placed hydrogens with AFIX multipliers as U_iso(H) = |multiplier| · U_eq(neighbor), searching within 1.3 Å using the minimum image convention. |
XyzParser | XyzData | Standard XYZ: atom count, comment, element + Cartesian coordinates + optional partial charge. |
PdbParser | PdbData | CRYST1, ATOM/HETATM, ANISOU (converted from 10⁻⁴ Ų to Ų), CONECT. Cartesian coordinates are converted to fractional when a cell is present. |
MolParser | MolData | MDL molfile/SDF V2000. Fixed-width and space-separated fallbacks. Bond block and MDL charge codes. V3000 is detected but not fully supported. |
Mol2Parser | Mol2Data | Tripos MOL2 with MOLECULE, ATOM, BOND, and CRYSIN sections. Sybyl atom types are converted to element symbols. Cartesian coordinates are converted to fractional when CRYSIN is present. |
GaussianParser | GaussianData | Gaussian input: link0 lines, multi-line route section, title, charge/multiplicity, Cartesian geometry. Numeric element tokens, ghost atoms (X, Bq, XX, GH) and freeze flags are handled. Bohr coordinates are converted to Å. |
All parsers call a Clean() method on their container before populating it, and set a completed flag on success.
CifDivider.Divide(content, filePath) splits a multi-block CIF into a list of CifBlock objects (Name, RawData). It preserves empty lines to keep line numbering intact, scans for lines whose trimmed form starts with data_, and falls back to a single block named after the file when no data_ directive is found.
Singleton that maintains the recent-files list persisted in %LocalAppData%\Tal\RecentFiles.json. See section 2.2.
A lightweight struct Complex (double-precision) used by the powder-diffraction structure-factor calculation. It provides the standard arithmetic operators, Magnitude, MagnitudeSquared, Phase, Conjugate, and a FromPolar factory. The BCL System.Numerics.Complex is intentionally not used to avoid name clashes and to keep the operations minimal.
Tal uses a modern rendering pipeline with the following key stages:
Scene Quality settings (e.g., 2x means the RT is twice the width and height of the window).Supersampling is the primary method of anti-aliasing in Tal. By rendering the scene at a higher resolution (e.g., 2x, 4x) and then scaling it down, the jagged edges of polygons are smoothed. The SSAASamples flag determines the multiplier. A value of 2 means the scene is rendered at 2x resolution, a value of 4 at 4x, etc. This technique provides excellent image quality, especially for thin features like bonds and ellipsoid disks.
This technique builds upon SSAA. When the user is not interacting with the scene (i.e., the camera is not moving), the application renders the scene multiple times with a sub-pixel jitter on the camera's projection matrix. The results of these jittered frames are accumulated (averaged) into a single buffer. This effectively multiplies the number of samples per pixel, producing an extremely smooth, high-quality final image. The number of accumulated frames is controlled by the ProgressiveAASamples flag (e.g., 16 frames). This method is perfect for producing publication-quality screenshots.
The VdwParameters static class is the single source of truth for all van der Waals radii and the default color palette used in the application. It contains a comprehensive, hardcoded table of radii for elements 1 through 118, along with a default color for each. The Atom Colors (global)... window allows the user to modify the color palette at runtime, and these overrides are stored in the application's settings file (Flags) and applied on startup.
All application settings, window layouts, and user preferences are managed by the Flags singleton. Key settings are saved to a JSON file on application exit and loaded on startup. This includes window positions, lighting settings, background colors, atom color overrides, and camera parameters. The Flags.Instance.MarkDirty() method is used to flag that settings have changed and need to be saved.
XNA's Matrix.CreatePerspectiveFieldOfView takes a vertical field of view. POV-Ray's angle parameter is the horizontal angle. The conversion used by PovRayGenerator is:
halfV = radians(fovVertical) / 2
halfH = atan(tan(halfV) * aspect)
angle = degrees(2 * halfH)
The POV-Ray right vector must remain constant (1.0) — POV-Ray scales the image itself from the render resolution (+W/+H), and setting right = aspect would squeeze the image horizontally. The render resolution must therefore have the same aspect ratio as the TAL viewport.
Atomic form factors in StructureFactorCalculator use the 4-Gaussian + constant approximation from International Tables for Crystallography, Vol. C, Table 6.1.1.4:
f0(s) = Σ aᵢ·exp(−bᵢ·s²) + c (i = 1..4)
s = sinθ / λ
Coefficients are stored as 9-element arrays { a1, b1, a2, b2, a3, b3, a4, b4, c } keyed by atomic number. Elements without tabulated coefficients fall back to a carbon-like default.
In SHELX RES/INS files, a negative U_iso value on a hydrogen atom encodes an AFIX multiplier: the hydrogen's U_iso is to be derived from its bonded neighbor's equivalent isotropic displacement as
U_iso(H) = |AFIX| · U_eq(neighbor)
ResParser performs a second pass over the parsed atoms. For each hydrogen with afix_multiplier > 0, it finds the nearest non-hydrogen atom within 1.3 Å (minimum-image convention, using the cell's fractional-to-Cartesian matrix), computes U_eq = (U11 + U22 + U33) / 3 for anisotropic neighbors or uses U_iso for isotropic ones, and writes the resulting value into iso. Hydrogens without an AFIX multiplier keep the value given directly by SHELX.
The VdW Scaling analyzer performs a systematic sweep of cavity detection at multiple van der Waals radii. For each scale it collects geometric, electrostatic and channel information, and then derives a set of QSPR descriptors that summarize the structural stability of the framework, its porosity and its potential for host-guest chemistry. This is the tool to reach for when you want to move beyond a single "static" cavity picture and characterize a porous material.
VdWScaleAnalyzer.RunAnalysisAsync(molecule, detector, scales)
(wired from MainForm.RunVdWAnalysisAsync). In the current UI the entry is not a separate top-level
Calculate menu item; single-run cavity reports still refer to it as
“Calculate → VdW Scaling Analysis…”. Prefer launching it from the cavity workflow after a successful detection,
or from a debug/status path that calls RunVdWAnalysisAsync.
2.00, 1.95, 1.90, 1.85, 1.80, 1.75, 1.70, 1.65, 1.60, 1.55,
1.50, 1.45, 1.40, 1.35, 1.30, 1.25, 1.20, 1.15, 1.10, 1.05, 1.00
Larger scales first (aggressive shrinking of the pores), then down to 1.00. This ordering
is important for the "collapse scale" descriptor — the analyzer walks from the most
aggressive scale down to the physical one, and records the first scale at which total
cavity volume drops below the collapse threshold.
ProgressChanged, StatusChanged) or the status bar. Each scale reports its cavity count and (when available) the largest fitted sphere.VdWAnalysisResults object in
VdWScaleAnalyzer.CurrentResults. The report can be saved as TXT or HTML.
r_eff(i, mult) = r_vdw(i) · mult + ProbeRadius
and the difference r_eff(i, mult) − r_eff(i, mult') is
r_vdw(i) · (mult − mult') — different for every element. The SDF cannot be
obtained from another multiplier by a single constant shift; it has to be recomputed.
The sweep is still fast because the scanner's atom cache and spatial index are reused
across scales — only the SDF hot loop runs again.
The result object is the single source of truth for the report. Its main fields are:
| Field | Description |
|---|---|
VdWScales | List of scales that were actually processed. |
CavityResults | Dictionary<int cavityId, List<VdWAnalysisResult>> — one VdWAnalysisResult per (cavity, scale). |
CavityTracks | Dictionary<int cavityId, CavityTrackData> — a per-cavity summary of volume evolution across scales. |
CriticalEvents | List of CriticalEvent (disappearance, channel open/close, topology change) with the scale at which they occur. |
CrystalChannelData | Dictionary<int cavityId, Dictionary<string dir, ChannelPathResult>> — semi-periodic channels detected at VdW = 1.0. |
SpherePacking | Dictionary<int cavityId, SpherePackingResult> — the multi-sphere packing summary of every cavity. |
CellVolume, VoxelVolume, AtomCount, CellMass | Cell parameters captured before the run. |
StartTime, EndTime, TotalProcessingTimeMs | Timing information. |
FragmentationIndex | QSPR I_frag — see section 15.5. |
CollapseScale | QSPR f_collapse — the VdW scale at which the total cavity volume drops below 0.1% of the cell volume. |
ElectrostaticFrustration | QSPR Ω_Q — see section 15.5. |
HydrophobicFrustration | QSPR Ψ_H — see section 15.5. |
MeanPolarizability | QSPR ⟨Z_cav⟩ — mean Z-effective of all cavity voxels. |
MaxDipoleSensitivity | QSPR α_polar — maximum d|dipole|/d(scale). |
For every (cavity, scale) pair the analyzer stores both geometric and electrostatic data
in a single VdWAnalysisResult:
Volume, VolumeError, SurfaceArea, SurfaceAreaError, Sphericity, VolumeToSurfaceRatio, RatioError, TriangleCount, VoxelCount, WallConnections.MeanCharge, ChargeVariance, MeanHydrophobicity, HydrophobicityVariance, MeanZEffective, DipoleMomentX/Y/Z, DipoleMomentMagnitude. The dipole is computed as the charge-weighted first moment of the cavity voxels.HasChannelsAtThisScale, ChannelDirections. This reflects the channel state at VdW = 1.0 (channels are only analyzed at that scale to keep the runtime manageable).
The most important derived quantity is VolumeToSurfaceRatio (V/S),
which behaves like a generalized radius: it grows with the cavity size and shrinks when a
cavity becomes rough or fragmented.
After the sweep, the analyzer builds a CavityTrackData for every cavity ID it
saw at least once. The tracking object contains one CavitySnapshot per scale,
with the volume/surface/electrostatic data frozen at that point. The
DisappearanceScale is the first scale at which the volume drops below
EPSILON after having been positive earlier.
CriticalEvent records all interesting transitions:
Six descriptors summarize the whole sweep:
| Descriptor | Formula (informal) | Interpretation |
|---|---|---|
I_frag | N_cavities_max / N_atoms | Structural fragmentation. Large values (> 0.5) suggest a highly porous, "beads-on-a-string" framework. |
f_collapse | First scale s at which the total volume fraction drops below 0.1%. | Structural stability. Larger values imply the cavities survive at more aggressive VdW radii. |
Ω_Q | Σ Δs · ⟨Var_Q(s)⟩ · ⟨V(s)⟩ | Electrostatic frustration: how heterogeneous the charge distribution is across the whole porous network. |
Ψ_H | Σ Δs · ⟨Var_H(s)⟩ · ⟨S(s)⟩ | Hydrophobic frustration: how heterogeneous the hydrophobicity is on the cavity surface. |
⟨Z_cav⟩ | Mean of MeanZEffective over all cavity snapshots (non-duplicates). | Polarizability capacity. Larger values correlate with stronger dispersion interactions with guests. |
α_polar | max over scales of (dipole(si+1) − dipole(si)) / Δs | Responsiveness to an external electric field. |
The VdW sweep is not just a way to compute f_collapse. The shape of the
total volume curve V(mult) is itself diagnostic and allows you to distinguish
qualitatively different kinds of cavities:
| Type of cavity | κ = V(1.5) / V(1.0) | Shape of V(mult) | Physical interpretation |
|---|---|---|---|
| Large oval cavity | > 0.5 | Smooth, close to (R_0 − δ·mult)³ |
Well-packed framework, cavity survives even at aggressive VdW radii. Stable, good host candidate. |
| Medium cavity | 0.2 – 0.5 | Moderate cubic decline | Real cavity, limited selectivity, likely stable. |
| Narrow slit | 0.05 – 0.2 | Steep, almost linear in the last part | Thin gap between molecules, weak contacts, structure may be fragile with respect to T or P. |
| Sub-VdW slit | < 0.05 | Vertical drop, cavity disappears before mult = 1.05 | Numerical artefact or extremely weak contact. Structure likely has non-optimal packing. |
The volume of a spherical cavity of radius R scales as
V ∝ R³. The VdW multiplier shrinks R by
δ = (mult − 1) · r_vdw, so the expected profile for a compact cavity is a
cubic polynomial in mult. Deviations from that cubic — in particular a
sharp drop followed by a long tail — indicate that the cavity is not a compact
object but a network of narrow necks. This is exactly the kind of qualitative
distinction that the VdW sweep is designed to make visible.
V(mult) as a measure of packing
tightness is closely related to the classical packing coefficient
k = V_molecules / V_cell of Kitaigorodskii, and to the topological
analysis of molecular coordination numbers by Peresypkina and Blatov
(Acta Cryst. B56, 501–511 and 1035–1045, 2000), who showed that organic
molecules pack with an average molecular coordination number of 14 rather than the
12 predicted by the hard-sphere model. The VdW sweep in Tal is a dynamic extension
of that idea: instead of a single packing coefficient, it measures how quickly the
free volume collapses when the atomic radii are inflated.
For each cavity detected at VdW = 1.0 the analyzer builds a
SpherePackingResult. The sphere fits themselves are found by
SphereFitAnalyzer, which operates entirely on the Cavity
object and does not need a graphics device.
If the cavity has fewer than MIN_VOXELS_SMALL = 50 voxels, the analyzer runs a
FindSingleMaximum routine: a coarse grid scan (step =
min(resX,resY,resZ)/20) locates the maximum of the SDF, then a local refinement
over a radius of 2·step voxels finds the true apex. A single
SphereFitResult is created with MaxSphereRadius = SDF_max.
For larger cavities the analyzer enumerates all local maxima of the SDF in parallel and then selects a non-overlapping subset:
min(resX,resY,resZ)/15, test the 6 face neighbours, and refine each candidate over a 3-voxel cube.MIN_SDF_VALUE_LARGE = 0.5 Å.max( (rcand + rsel) · SEPARATION_FACTOR / 10, MIN_DIST_FRAC = 0.05 ) in fractional coordinates. This yields a maximal packing of spheres.SphereFitResult per selected sphere, with the cavity ID, sphere radius, sphere center, fractional coordinates, wall flag, and distance to the cavity center.| Field | Meaning |
|---|---|
MaxSphereRadius, EffectiveDiameter | Radius and diameter of the largest fitted sphere. |
SphereCount | Number of non-overlapping spheres. |
Spheres | List of PackedSphere — one per fitted sphere, with radius, center, fractional coords, wall flag and distance to the cavity center. |
UsableVolume | Sum of the volumes of all fitted spheres. |
PackingEfficiency | UsableVolume / CavityVolume (values around 0.74 indicate a close-packed cavity). |
HasChannels, ChannelInfo | Channel state at VdW = 1.0. |
For every cavity the analyzer cross-checks the fitted spheres against a fixed library of
gases. Each gas is a GuestMolecule with a kinetic diameter, polarizability,
dipole moment, and a flag IsGas = true. The library includes:
For each gas the result is a GasGuestResult:
gasRadius ≤ 0.95 · maxSphereRadius.gasVolume / cavityVolume.0.70 · sizeMatch + 0.30 · polarityMatch, where sizeMatch = 1 − |gasRadius / maxRadius − 0.5|·2.GOOD (score ≥ 0.7), FAIR (score ≥ 0.4), POOR.floor(usableVolume / gasVolume).
The same library (with IsGas = false) is used to score solvents. For each
solvent the analyzer builds a SolventGuestResult whose score combines four
indicators:
The final score is a weighted sum (0.25 size + 0.15 polar + 0.15 charge + 0.15 hydro + 0.10 shape + 0.10 voxel)
and VoxelClusterMatch is a derived 0.5–1.0 confidence that the solvent fits the
shape of the cavity's voxel cloud, not just the largest sphere.
Channel analysis is run at VdW = 1.0 for every cavity that touches at least one cell wall
(WallConnections > 0). The CavityPathFinder performs a
semi-periodic A* search along the three cell axes and stores its results in
cavity.CrystalChannelResults.
| Field | Meaning |
|---|---|
IsConnected | Whether a path through the periodic image was found. |
TotalLength, OriginalLength, SmoothedLength | Path lengths in Å. |
SmoothingError, SmoothingErrorPercent | RMS of the smoothing displacement. |
AverageWidth, MinWidth, MaxWidth | Local diameter, sampled along the path. |
EffectiveDiameter | AverageWidth · 0.8 — a conservative estimate of the "clear" diameter. |
Helicity | Signed angle change of the path direction. Small values (≈ 0°) indicate a straight channel; large values indicate a helical channel. |
CellCrossings | Number of times the path crosses a cell boundary. |
WorldPath, WorldPathXna | Path vertices in Å (System.Numerics and XNA variants). |
MaxStartPoints = 20 candidates are enumerated in expanding shells around the center of the periodic cell. A candidate is accepted only if the local voxel is not a wall and has at least 2 units of free space in the 6 axis directions.distance + distance · (1 − SDF / SDF_max) · SDFWeight so that the path prefers to stay near the center of the cavity.SmoothingIterations, default 3). The RMS displacement is reported as SmoothingError.ChannelPathStatistics object (min/max/avg length, diameter, helicity).WorldPathXna is filled from WorldPath so the renderer can consume it directly.
VdWAnalysisReport.GenerateTxtReport(results) produces a plain-text report;
GenerateHtmlReport(results) produces a self-contained dark-theme HTML report;
SaveReport(results, path) dispatches to the right one by extension. Both
reports are composed of the same sections:
The AtomVisibilityManager is a small static helper class in Tal.DrawMethods that centralizes the "show / hide" operations for atoms. It is not bound to any particular tool window; any command that needs to hide or show atoms should go through it so that all the ancillary structures (contacts, hydrogen bonds, etc.) stay consistent.
The class is stateless: it does not cache any data, it just manipulates the Molecule.Atom.show flag and the atomic contact arrays. This follows the same pattern as ShortContactsFinder and HBondFinder.
| Method | Effect |
|---|---|
HideSelected(molecule) | Hides every atom whose selected flag is true and whose show flag is currently true. Returns the number of atoms that were actually hidden. |
HideReverseSelected(molecule) | Hides every atom that is not selected (the complement). Returns the number of atoms hidden. |
ShowAll(molecule) | Restores the show flag on every atom. |
HideAtom(molecule, atomIndex) | Hides a single atom by index. |
HideMolecule(molecule, atomIndex) | Hides every atom that shares the same IndividualMoleculeInCell value and the same cell shift (CellShiftX/Y/Z) as the given atom. Returns the number of atoms hidden. This is the correct way to hide a whole molecule in a packing view: it does not rely on Cartesian proximity. |
HideSymmetryEquivalent(molecule, atomIndex) | Hides every atom that shares the same SymmetryEquivalentNumberInCell. This is the correct way to hide all copies of an atom generated by the same symmetry operation. |
HideShortContacts(molecule) | Thin wrapper around ShortContactsFinder.ClearAllContacts(molecule). Does not change the show flag of any atom. |
Contacts in Tal are stored twice — once on each of the two atoms that participate in the contact. If one end is later hidden or removed, the other end is left with a dangling reference to an atom that is no longer visible. RemoveHangingContacts(molecule) walks every atom, filters its Contact* arrays, and drops every entry whose target atom is out of range or has show == false. The filtered arrays are:
ContactDistanceContactAtomContactAngleZContactAngleXYPrecomputedContactMatrixContactIsNew
ContactCount is updated to match the new list length. The method returns the total number of contacts removed and writes a single line to the debug log.
RemoveHangingContacts so that the next redraw of short contacts does not attempt to draw lines to invisible atoms. This is not done automatically because the caller may want to preserve the contact list for a subsequent "undo".
CountVisible(molecule) — number of atoms with show == true.CountHidden(molecule) — NumberOfAtoms − CountVisible(molecule).Isolate a single molecule in a packing view:
HideReverseSelected(molecule) first (so that only the picked atom's selection is kept), then HideMolecule(molecule, atomIndex) to hide every copy of that molecule except the selected one.RemoveHangingContacts(molecule) to clean up dangling contacts.Hide all copies of a single symmetry-equivalent atom:
HideSymmetryEquivalent(molecule, atomIndex).Undo all hides:
ShowAll(molecule).
Toolbars are built in MainForm.Toolbars.cs. They sit under the menu strip and provide
style, camera, and picking controls without opening dock windows.
| Control | Values / behaviour |
|---|---|
| Style combo | CPK, Sticks, Balls and Sticks, Ellipsoids, Polyhedrals |
| Control | Behaviour |
|---|---|
▶ Animate | Starts/stops automatic rotation of the structure. |
a b c | View along the real-space cell axes. |
a* b* c* | View along reciprocal axes. |
+X … -Z | Rotate ±1° about the screen/world axes. |
←90 →90 ↑90 ↓90 | Rotate 90°. |
| Arrow pan buttons | Translate the scene left/right/up/down. |
Zoom+ / Zoom- | Change camera zoom. |
Persp. / Ortho. | Toggle perspective vs orthographic projection (CheckOnClick). |
| Mode | Behaviour |
|---|---|
| Rotate | Default navigation (LMB rotate, RMB zoom, MMB pan). |
| Pick Atoms | Toggle atom selection. |
| Lasso Atoms | Rectangular multi-select. |
| Expand Contacts | Grow short-contact shell around the clicked atom. |
| Toggle Labels | Click atoms to show/hide their labels. |
| Move Labels | Drag labels in the viewport. |
| Measure Distances | Two-atom distances. |
| Measure Angles | Three-atom angles. |
| Measure Torsions | Four-atom torsions. |
| Reveal Symmetry-Generated Molecules | Reveal symmetry images of molecules. |
Clear Measurements — removes distance/angle/torsion annotations.👆 Pick and lasso shortcuts where present.
Detection and drawing are implemented by HBondFinder (Tal/Calculate/HBondFinder.cs).
The settings UI is HBondSettingsWindow (title H-Bond Settings), opened via
OnHBondSettings / Display Options → H-Bond related paths.
| Control | Meaning |
|---|---|
| Actual distance | Use absolute D⋯A (or H⋯A) limits in Å (numActualMin / numActualMax). |
| VdW distance | Limits relative to sum of donor and acceptor van der Waals radii. |
| Min/Max = r_vdW ± value | Operators minus / plus with numeric offsets (numVdWMinus, numVdWPlus). |
numAngle. Bonds with a smaller D–H⋯A angle are rejected.Show H-Bonds in 3D viewIntramolecular / IntermolecularDraw H-bond from hydrogen — cylinder starts at H rather than donor heavy atom.Require hydrogen atom to be present — skip putative D⋯A contacts without an explicit H.Flags.HBondColor for the 3D cylinders (solid or dashed; dash geometry from flags).OK — apply and close.Apply — apply without closing.Default — restore built-in criteria.Flags.HBondColor
(see section 13 in the main help).
Menu: Calculate → Normalize Hydrogens... → OnNormalizeHydrogens.
Window: HydrogenNormalizationWindow (title Hydrogen Normalization).
Engine: HydrogenNormalizer.
| Control | Role |
|---|---|
| Normalize checkbox | When checked, the table shows proposed neutron lengths; rebuilds on change. |
| Skip bridging | When checked, bridging hydrogens are left unchanged (default on). |
| Molecule info label | Active molecule summary. |
| Table | Per-hydrogen status / proposed length / parent atom. |
| Apply | Writes normalized coordinates; originals cached for Reset. |
| Reset | Restores coordinates from HydrogenNormalizer cache (enabled only if a cache exists). |
| Close | Closes the dock window. |
Standard distances live in HydrogenNormalizer.NeutronLengths (element-pair keys).
Analysis uses HydrogenNormalizer.Check over the asymmetric unit (and related helpers for Hirshfeld).
Calculate → Normalize Hydrogens... — molecule is loaded before the window is shown.Apply — updates coordinates in the active molecule.Reset to restore the pre-normalization geometry from the session cache.
Dock window DisplayOptionsWindow, toggled from Window → 🎨 Display Options
(OnToggleDisplayOptions).
Packing — same flag as Packing window / Flags.DrawPacking.Short Contact — show short-contact lines.Asymmetric Unit — restrict view to the asymmetric unit when applicable.H-Bond — show hydrogen bonds.Auto centre — keep the structure centred on interaction.Show hydrogensShow cell axesLabel atomsDepth cue — enables depth cueing (see below).Z-ClippingStereoReset — default display flags.Contacts... — opens Short Contacts settings.More Info — structure / info path.Powder pattern — shortcut to powder diffraction.
DepthCueWindow fine-tunes fog / attenuation when Depth cue is enabled.
Opened from display options or related commands; parameters are stored in Flags and applied in the draw pipeline.
AutoRotateWindow complements the toolbar ▶ Animate button with explicit speed / axis options
when available. Animation advances the camera or model rotation each frame while active.
TAL — Crystallographic Application
Copyright © 2026 Daniel M. Kamiński, Lublin, Poland
All rights reserved.
Website:
https://dkami.umcs.pl/tal/
Contact:
daniel.kaminski@umcs.pl
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to use, copy, modify, and distribute the Software for non-commercial, academic, and educational purposes only , subject to the conditions below.
The Software may be used solely for non-commercial purposes, including but not limited to academic research, teaching, and personal study. Commercial use of any kind is strictly prohibited without a separate commercial license from the copyright holder.
“Commercial use” includes, without limitation:
Any distribution, publication, or derivative work that uses or is based on the Software must include the following notice:
TAL - Crystallographic Application Copyright (c) 2026 Daniel M. Kamiński Contact: daniel.kaminski@umcs.pl Website: https://dkami.umcs.pl/tal/
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
For commercial licensing inquiries, please contact:
Daniel M. Kamiński
E-mail: daniel.kaminski@umcs.pl
Lublin, Poland
This Software incorporates third-party libraries distributed under their own licenses (including but not limited to .NET, MonoGame, DockPanel Suite, SharpDX). Those licenses remain in force and are unaffected by this license.
LICENSE.txt in the same folder as the
executable.