') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ', 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ', 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ', 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); })(); GitHub - JerboaBurrow/SimpleFastOpenAtomicVisualiser: Visualise atomic and molecular system structures and trajectories · GitHub
Skip to content

Repository files navigation

SimpleFastOpenAtomicVisualiser (SFOAV)


Pislocybin molecule with 2 triangles per bond (ray-traced).

A simple program to quickly visualise atomic and molecular systems (AMSs).

The code is accelerated using OpenGL for 3D rendering and builds for Linux, Windows, macOS, and Android.

What it is

  • Simple: a trivial interface for loading and displaying AMSs configurations and trajectories. Via command line or GUI.
  • Fast: rendering as fast as e.g. VMD or other 3d-viewers, with many efficiency options for level of detail.
  • Open: open source and GPL licensed, enabling commercial use, adaptation, extension etc.
  • Atomic visualisation: render, colourable, atoms and bonds in 3D space loaded from standard AMSs configuration and trajectory data files.

Quickstart

To render a structure file struct.xyz simply call

sfoav struct.xyz

Important

SFOAV can process .xyz, .extxyz, and DL_POLY CONFIG, REVCON and HISTORY files. If the file name does not match these patterns all types will be attempted.

This will bring up the view centring the atoms in struct.xyz in the first frame (if applicable). The camera is centered on (0, 0, 0) and can be moved in spherical coordinates relative to it. By defaul the atoms are centred at (0, 0, 0). The atoms can also be translated relative to (0, 0, 0).

Note

Reading of structure files is done in a background thread. For large structure files you may be presented with a loading screen. An intel i7-4790K and Kingston A400 SATA SSD is capable of around 500,000 (positions only) atoms per second read.

If the structure file is a trajectory you may scan through its frames moving forward of backward in time using F and B respectively. Or auto-playing/pausing with P.

Note

When reading HISTORY files or XYZ/EXTXYZ with multiple frames, SFOAV will cache the filepositions (not data) of each frame in the background. For large trajectory files this may take some time, but you will always be able to play up to the most recently cached frame.

At runtime the following camera key-controls can be used:

KeyActionNote
WZoom towards the origin.
SZoom away from the origin.
QIncline the view.
EDecline the view.
ARotate the view.
DRotate the view.
SPACEReset to the default view and atom positionsAt (0,0,0), azimuth Pi/2 and inclination Pi.

The following atom key-controls are available:

KeyActionNote
HToggle atom drawing.
LEFTTranslate the atoms in -x
RIGHTTranslate the atoms in +x
UPTranslate the atoms in +z
DOWNTranslate the atoms in -z
.Translate the atoms in -y
/Translate the atoms in +y
1 to 9Toggle element emphasisElements assigned at startup.

Trajectory playback may be controlled by the following key-bindings:

KeyActionNote
FMove forward in timeSets forwarding playing with P.
BMove backward in timeSets backward playing with P.
PPause/Play a trajectory
JDecrease play speedThe minimum is 1 frame per second.
KIncrease play speedThe maximum is 60 frames per second.
RReset to the first trajectory frame

Miscellaneous key bindings are:

KeyActionNote
XToggle drawing the coordinate axes
CToggle drawing the simulation cell
UToggle user interface windows
GScreen grab
VStart or finish a video recording
ESCQuit

To enable MSAA at 16x

sfoav struct.xyz -msaa 16

To draw bonds between atoms 1.5 Angstroms apart

sfoav struct.xyz -bondCutOff 1.5

Lua scripting

It is possible to write Lua scripts to manipulate visualisation in SFOAV. By supplying a path as --script PATH.lua to a Lua file, SFOAV will run the file each frame update. The console exports the following methods in the sfoav library.

Warning

Lua indexes from 1, but all sfoav library functions index from 0.

MethodArgumentsReturn/Effect
setAtomColourAtom index and an RGB/RGBA colour, in [0, 1]
getAtomColourAtom indexThe atoms RGBA colour
bondAtom index a, Atom index bBond atoms a and b
unbondAtom index a, Atom index bUnbond atoms a and b
getAtomsBondsAtom index aA table of all atom indices bonded to a
getAtomAtom index aThe Atom structure for a
atomCountThe number of atoms
getAtomsNeighboursAtom index x, cutoff distanceThe neighbours of a within the cutoff
setTextText stringSet the text display
getFrameThe current frame number (from 0)
startRecordingStart video recording if not already recording
stopRecordingStop video recording if already recording
playPlay the trajectory
pausePause playing the trajectory
cameraPositionBool for spherical coordinatesGet the camera's position
setCameraPositionr, theta, phi spherical coordinatesSet the camera's position
rotateCameradphi, the azimuthal incrementRotate the camera
zoomCameradr, move the camera to or from the focusZoom the camera
inclineCameradtheta, the inclination incrementIncline the camera
setCameraFieldOfViewfov, degreesSet the field of view
getCameraFieldOfViewGet the field of view in degrees
exitExit sfoav
-- Set atom 0 to a random colour.sfoav.setAtomColour(0, math.random(), math.random(), math.random())

Or a more complex example to render only the neighbours of atom 0 within 4 Angstroms

-- Get atom 0's neighbours up to 4 Angstroms.neighbours=sfoav.getAtomsNeighbours(0, 4.0)
-- Fade all atoms.fori=1, sfoav.atomCount() dor, g, b, a=sfoav.getAtomColour(i-1)
sfoav.setAtomColour(i-1, r, g, b, 0.0)
end-- Unfade all neighbours to atom 0 within 4 Angstroms.fori=1, #neighboursdor, g, b, a=sfoav.getAtomColour(neighbours[i]["index"])
sfoav.setAtomColour(neighbours[i]["index"], r, g, b, 1.0);
end

Another example for automated video rendering

if (record==nil) then-- Will only run once.record=truesfoav.inclineCamera(3.14*0.25)
-- Begins recording.sfoav.startRecording()
-- Begins trajectory playing.sfoav.play()
endif (recordandsfoav.getFrame() ==200) then-- At frame 200 finish recording and exit when done.sfoav.stopRecording()
sfoav.pause()
-- Exit will occur after frames are written.sfoav.exit()
-- Ensure no duplicate calls.record=falseend

Video

On macOS and Windows one release exists using jo_mpeg to write mp4 files.

On Linux two releases exist, the standalone sfoav which uses jo_mpeg for video writing, and the FFmpeg enabled version sfoav-ffmpeg which requires additional runtime dependencies (FFmpeg). The FFmpeg video quality is generally superior.

Videos are written out with the filename as the current timestamp.

Video frames are recorded at 60 fps whenever the camera or atoms are updated e.g. a new trajectory frame, new colours, camera moved etc.

Frames are written in the background which will impact visualisation frame rate.

For FFmpeg the -codec option accepts strings as seen via ffmpeg -codecs e.g. -codec nvenc_h264 -qp 21 -preset lossless for Nvidia's H264 encoder. Quality may be controlled by -preset, as well as (depending on the codec) the -cp, -crf, and -qp arguments where 0 is best and 51 is worst. Other FFmpeg options are -bitrate and -maxBFrames and -gopSize.

Performance

For a system with an intel i7-4790K, Kingston A400 SATA SSD, a GTX 1080 ti, and 16 GB available RAM. SFOAV is capable of rendering at least 5,000,000 static atoms at 60 frames per second with 16x MSAA and with a moveable camera. At this scale moving the atoms will run cause drops to 30 fps, and frame increments will cost ~5 seconds.

Transparency sorting is on by default, if there are transparent atoms/bonds. This is expensive for the CPU on camera movements or atom/bond changes. This can be disabled with -noTransparencySorting, but will render atoms/bonds out of order.

MIT version

An MIT version prior to the inclusion of FFmpeg and the GPL v2 license can be found here https://github.com/JerboaBurrow/SimpleFastOpenAtomicVisualiser/releases/tag/v0-0.0.7


Features (in development)

  • High level viewing
    • Play/pause/step through time.
    • Atom emphasis.
    • Molecule/atom group emphasis.
    • Atom trajectory paths.
    • "Atom/molecule" cam.
    • Simulation cell.
    • Coordinate axes.
    • Dynamic bonds.
    • User supplied emphasis file.
    • Atom/molecule info hover.
    • Imgui UI??.
  • Input
    • Atom position file formats.
      • XYZ/EXTXYZ.
      • CONFIG/REVCON/HISTORY.
    • Atom connectivity file formats.
  • Output
    • Screenshot to png.
    • Render to mp4.
    • Headless context (e.g. for commandline HPC use).
  • Atom (bill-boarded) imposter spheres.
  • Impostor bonds.
  • Bond tube meshes.
  • Atom ball meshes (of various triangle counts/LOD)
    • Tetrasphere (4, 16, 64, ...).
    • Hexosphere (6, ...).
    • Octasphere (8, 32, 128, ...).
    • Icosphere (20, 80, 320, ...).
    • Dodecasphere (36).
    • TTP sphere (14, 56, 224, ...).
    • VDW scaled atom balls.
  • Colouring
    • User supplied editable colour sets.
    • CPK.
    • Velocity colouring.
    • Force colouring.
  • LOD
    • Overrideable LOD per atom.
    • Automatic LOD scaling.
    • Configurable maximum level of detail (LOD).
  • Desktop app (linux, windows, and macOS).
  • Android app??.

Out of scope

To keep it simple the following features won't be implemented.

  • Structures and environments.
  • System editing (atom positions, types, bonds).
  • Simulation.
  • Every structure format under the sun.

Contributions

Please follow this workflow:

  1. Open an issue to discuss your proposed changes.
  2. Fork the repository.
  3. Make a feature branch in your fork git branch <name_of_feature>.
  4. Open a pull request into main.

Contributions will be rebased and merged. Try to keep commits in a PR to 1 if possible. More is acceptable if it improve clarity.


Please consider attributing SimpleFastOpenAtomicVisualiser, Jerboa (https://jerboa.app), 2025.

About

Visualise atomic and molecular system structures and trajectories

Topics

Resources

Stars

15 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages