Services Layer
OrbitronServicescombines the workspace into a unified API. The struct owns:LoaderService: wraps IO pipelines and enforces size limits (with_max_size).AnalyzerService: centralises distance/angle/dihedral and statistics calculations.analysis/orbitals/slice.rs(re-exported fromanalysis/orbitals/mod.rs): the 2D MO/density slice evaluator —SlicePlane,ScalarField2D,mo_slice_from_basis,density_slice_from_basis,sample_volume_slice— backing the desktop Slice subtab and the TUI slice.ExporterService: writes scenes and trajectories to disk (currently XYZ, PDB, multi-frame XYZ).RendererService: lazily initialised renderer with configurable backend (Backend::Headless,Backend::Gpu).DataSource: trait implemented byLocalDataSource, abstracting file access.
ServicesConfigcontrols GPU usage, the load limits (max_file_size,max_atoms,max_bonds, andmax_memory— the hard cap the capacity gate enforces), verbosity, render backend, and default data root. The CLI propagates command-line flags into this config.streaming::TrajectoryStreamsupplies async frame streaming (used by viewer playback and large files).analysis::bond_orderconverts canonical computed pair and three-center bond orders into one summary without changing scene connectivity.periodic::ReciprocalScenere-exports the reciprocal-space model fromorbitron-periodic, keeping the desktop, TUI, CLI, Python bridge, and vector exporters on one first-zone and path-folding implementation.vaspholds the periodic-figure builders, which readPeriodicElectronicStructurerather than anything program-specific — a Quantum ESPRESSO run goes through them unchanged. The PNG/CSV writers sit behind the default-onvasp-exportfeature (it pullsplotters); the pure arithmetic below does not, so adefault-features = falseconsumer such asviewer/corestill gets it:build_band_curves— one curve per band. This is the band structure;build_band_envelopeismax(E ≤ E_F)/min(E > E_F)per k-point, which steps to the next band down at a Fermi crossing and is discontinuous by construction.build_fat_band_curves/build_fat_band_curves_by_shell— the same curves with a projection weight per point, selected byProjectionSelector { atoms, orbitals }. A bare shell letter expands to that shell’s labels via an explicit table, because VASP spells d(x²−y²) asx2-y2and a “starts with d” rule silently drops one of the five d orbitals.build_pdos_curves— per-element, per-shell DOS curves fromProjectedDensityOfStates.kpath_axis/kpath_ticks— what the x axis actually is (reciprocal distance vs k-point index, since not every reader can produce distances) and the labelled corners to draw.
7.1 Using OrbitronServices (Rust API)
use orbitron_services::{OrbitronServices, ServicesConfig, Backend};
fn main() -> anyhow::Result<()> {
// Configure headless services (suitable for CLI tools or batch jobs).
let config = ServicesConfig {
enable_gpu: false,
max_file_size: 2 * 1024 * 1024 * 1024, // 2 GiB cap
max_atoms: 0, // 0 = unlimited
max_bonds: 0, // 0 = unlimited
max_memory: 0, // 0 = unlimited
verbose: true,
render_backend: Backend::Headless,
data_root: Some(std::env::current_dir()?),
};
let services = OrbitronServices::new(config)?;
let scene = services.load("fixtures/benzene.xyz")?;
// Measurements and derived data
// Atom ids are 1-based, matching each format's native numbering.
let distance = services.distance(&scene, 1, 2)?;
let bbox = services.analyzer().bounding_box(&scene)?;
// Export / render
services.export(&scene, "benzene.pdb")?;
services
.renderer()
.render_to_file(&scene, "benzene.png", 1920, 1080, None)?;
Ok(())
}- Prefer the façade methods (
load,distance,export) for convenience. The sub-services stay accessible via getters if you need finer control (services.loader().load_frequencies). - Swap in an alternative data source via
OrbitronServices::with_data_sourceand a customDataSource; the CLI and Python bridge follow this pattern. - Persistent configuration lives in
config.rs.OrbitronConfigresolves the platform-specific config directory viadirectories::ProjectDirs; both the CLI and GUI load it on start and merge it with per-run flags or environment overrides. - Use
renderer_mut()when you need to mutate renderer options (e.g., toggling lighting presets before drawing headless renders). streaming::TrajectoryStreamintegrates with async runtimes—feed frames into custom analysis loops without blocking the UI. The viewer uses it to stream long trajectories while rendering.
7.2 Resource reports
capacity::ResourceReport keeps source parsing and decoded scene costs as separate stages. Frontends should preserve that split. A raw NWChem output can be expensive to parse but produce a small scene, while a compact bundle can decode into a large trajectory or grid.
The report uses these types:
| Type | Contract |
|---|---|
FileOpenEstimate |
Source bytes, format multiplier, optional count-derived decoded-memory minimum, projected incremental host memory, available-memory target, and separate format/hint confidence |
SceneResourceInputs |
Primary scene counts, a nested render request, total frames, resident records, packed positions, grid points, and requested features |
SceneRenderInputs |
Visible atom and bond instances, an optional viewport, and prepared mesh counts |
SceneRenderEstimate |
Exact instance, mesh, and readback buffers plus logical color and depth attachment bytes |
SceneResourceEstimate |
Approximate scene records, the render estimate, exact packed coordinate and grid payloads, and costs that remain unestimated |
ByteEstimate |
Tagged exact, projected, or range JSON so a frontend cannot mistake a heuristic for a measurement |
UnestimatedCost |
Requested work such as labels, bond inference, surface meshes, electronic structure, and undo history that lacks a defensible byte model |
ReliefOption |
A cheaper path the frontend may offer explicitly, such as disabling labels or retaining bounded trajectory storage |
frame_count means frames available. It does not mean frames resident. Use resident_atom_records and resident_bond_records for eager snapshots and frame caches, and packed_position_count for a packed f32 coordinate attachment. This distinction is required for indexed and packed trajectories: 10,000 available frames with a three-frame cache must not be estimated as 10,000 expanded SceneSnapshot values.
FileKind::estimate_with_hints keeps the calibrated source-size estimate as a floor. Header or bounded-scan counts can raise that projection through atom records, additional-frame positions for sources that advertise a trajectory, and raw volumetric values, but they never lower it. Passing None returns the same value as FileKind::estimate. A prefix_lower_bound hint therefore catches an already-large prefix without pretending it describes the unscanned tail. prefix_observed has a different meaning: an application printed that count in the prefix, but a later job stage may describe another system. Frontends must not present it as a lower bound for the complete file.
Bundle inspection also reports packed_trajectory_bytes separately from total uncompressed attachment bytes. The bundle-derived retained-scene floor adds atom records, resident trajectory positions, selected grid values, and the packed coordinate attachment. Bundle opening applies the bundle multiplier to the larger of stored bytes and that floor because manifest decoding and scene reconstruction create transient copies. Do not apply the same rule to every attachment: a bundled raw source is retained for provenance and import but is not decoded during an ordinary scene open.
Current writers serialize the same fixed-shape facts to resources.json before manifest.json. inspect_resources_path caps the actual uncompressed record at 64 KiB and returns without inflating the manifest or attachments. The measured million-atom record is 352 bytes beside a 114.8 MB manifest. Legacy bundles without the entry retain bounded manifest inspection.
Treat the compact record only as preflight evidence. container::open rederives it from the parsed canonical payload and refuses a mismatch before exposing the bundle. Code that sizes an allocation or consumes an attachment must use the validated full-open path, not the resource-only inspection result.
capacity::estimate_file_open(path, mode) is the shared local-file entry point. It combines file metadata, FileKind classification, and the bounded source inspector, then retains the size multiplier as the floor. The desktop, CLI, Python bridge, and Rust SDK gates all pass that returned FileOpenEstimate to Operation::EstimatedFileOpen. Do not rebuild a file-open operation from byte length alone in a frontend; doing so discards the counts that can expose a small file declaring a very large scene.
The render estimate gets instance and mesh sizes from Rust’s active layouts. Those source, staging, vertex, index, uniform, and readback buffer payloads are exact before allocator or device padding. Viewport color and depth attachments are marked layout_derived: Orbitron knows their logical pixel and sample counts, but Metal, Vulkan, DX12, and WebGPU may allocate more. The largest-buffer field covers buffers only, not textures.
Prepared meshes have exact counts. A requested surface that has not been extracted does not. It remains UnestimatedCost::SurfaceMeshes until the marching-cubes or cartoon result exists, which prevents a grid-size heuristic from masquerading as a known triangle count. Scene record storage uses the backbone’s conservative per-record figures and stays projected. Variable-size properties and other unmeasured work also stay in unestimated_costs; do not fold them into an apparently complete total.
capacity::bundle_preparation_command returns a shell-safe canonical export command only when bounded inspection recognizes a raw source. It returns None for unknown inputs and existing .orbpack files. Resource gates attach that command to ResourceReport::bundle_preparation_command only when the source reaches a warning band. The same report also adds prepare_bundle_near_source to relief_options. CLI and TUI warnings print the command directly. The desktop warning displays it before opening, and Scene Info retains it with a copy control after the scene loads.