Interactive, Terminal-Native Protein Structure Viewer
StrucTTY is a lightweight, terminal-based protein structure visualizer built in C++17. It renders 3D protein structures directly in the terminal using Unicode Braille sub-pixel rendering, providing 8x resolution compared to standard character-based rendering.
StrucTTY supports simultaneous visualization of up to 9 proteins, 9 color modes with 3-band depth fog, and integrates with Foldseek and FoldMason for structural search and multiple structure alignment.
- Braille sub-pixel rendering — each terminal cell maps to a 2×4 logical pixel grid
- Up to 9 proteins rendered simultaneously with independent controls
- 9 color modes —
protein,chain,rainbow,plddt,interface,conservation,align,align-fs,align-near - 3-band depth fog — near (bright), mid (normal), far (dark with hue retention) for depth perception
- Secondary structure visualization — helix cylinders and sheet ribbons
- Foldseek integration — load
.m8/_reportresults (-fsr), navigate hits, and take targets from a Foldseek DB, a local directory, or automatic download (-fst) - FoldMason integration — MSA superposition with conservation coloring
- MSA conservation scoring — Shannon entropy from FASTA/A3M alignments
- Interface detection — inter-chain contact residue highlighting
- Alignment visualization — structural alignment region highlighting
- Screenshot export — PNG output via stb_image_write
- Chain selection — filter specific chains per protein
| Dependency | Version |
|---|---|
| C++ compiler | GCC ≥ 7.1 or Clang ≥ 5.0 (C++17) |
| CMake | ≥ 3.15 |
Supported platforms: Linux, macOS
git clone --recurse-submodules https://github.com/steineggerlab/StrucTTY.git
cd StrucTTY
mkdir build && cd build
cmake ../ -DCMAKE_BUILD_TYPE=Release
make -j $(nproc)The output binary will be generated at
build/StrucTTY.
The structures used below are distributed separately. Unpack them in the
repository root, next to build/:
cd StrucTTY
curl -L -o example.zip https://github.com/user-attachments/files/31204019/structty_example.zip
unzip -d example example.zip && rm example.zip./StrucTTY ../example/1NPL-assembly1.cif --mode chainSingle_structure.mp4
./StrucTTY ../example/3HGM-assembly1.cif --mode chain
./StrucTTY ../example/3HGM-assembly1.cif --mode chain -sSecondary_structure_visualization.mp4
./StrucTTY ../example/3A0C-assembly1.cif # protein (default)
./StrucTTY ../example/3A0C-assembly1.cif --mode chain
./StrucTTY ../example/3A0C-assembly1.cif --mode rainbow
./StrucTTY ../example/3A0C-assembly1_colabfold.pdb --mode plddt
./StrucTTY ../example/3A0C-assembly1.cif --mode interface
./StrucTTY ../example/3A0C-assembly1.cif --mode conservation \
--msa ../example/msa_result/query.a3mColor_modes.mp4
./StrucTTY ../example/1CJK-assembly1.cif \
../example/1NPL-assembly1.cif \
../example/3A0C-assembly1.cif \
../example/3HGM-assembly1.cif \
../example/3OAG-assembly1.cif \
../example/9FL9-assembly1.cif \
../example/AF-A0A233SAX3-F1-model_v6.cif \
../example/9N47-assembly1.cif \
../example/8KGM-assembly1.cifMultiple_structures.mp4
./StrucTTY ../example/9N47-assembly1.cif -m chain
./StrucTTY ../example/9N47-assembly1.cif -c ../example/chainfile_9N47.tsv -m chainChain_selection.mp4
The same hit under both alignment modes — align-fs colours only what Foldseek
aligned, align-near colours whatever ends up close in space:
./StrucTTY ../example/foldseek_result/DB1/3cna-assembly1.cif \
-fst ../example/foldseek_result/DB2/ \
-fsr ../example/foldseek_result/result \
-m align-fs -s./StrucTTY ../example/foldseek_result/DB1/3cna-assembly1.cif \
-fst ../example/foldseek_result/DB2/ \
-fsr ../example/foldseek_result/result \
-m align-near -sIn the recording, the right pane is align-fs and the left one is align-near.
Foldseek_hit_navigation_2.mp4
Pass the query directory instead of one structure to walk every query in the
result with ]/[:
./StrucTTY ../example/foldseek_result/DB1/ \
-fst ../example/foldseek_result/DB2/ \
-fsr ../example/foldseek_result/result \
-m align-fs -s./StrucTTY ../example/3A0C-assembly1.cif ../example/L7RCY6.pdb
./StrucTTY ../example/3A0C-assembly1.cif ../example/L7RCY6.pdb \
-fm ../example/foldmason_result/foldmason.json -m align./StrucTTY <query...> [OPTIONS]
| Option | Description |
|---|---|
-m, --mode <MODE> |
Color mode: protein (default), chain, rainbow, plddt, interface, conservation, align, align-fs, align-near |
-c, --chains <FILE> |
Chain selection file (TSV: index + chain IDs) |
-s, --structure |
Show secondary structure (helix/sheet) |
--msa <FILE> |
MSA file for conservation scoring (FASTA/A3M) |
-fst, --foldseek-target <PATH> |
Target source for Foldseek hits: Foldseek DB, structure directory, structure file, or auto (download from public DBs) |
-fsr, --foldseek-result <FILE> |
Foldseek result: .m8 (12/17/21/29 columns) or multimer _report (14 columns) |
-fm, --foldmason <FILE> |
FoldMason result (JSON or FASTA MSA) |
-n, --nopanel |
Hide info panel |
-fst and -fsr must be given together — one without the other is an error.
The kind of every input is detected automatically (no format flags):
| Kind | What it is | Accepted as |
|---|---|---|
| Structure file | .pdb / .cif / .ent (+ .gz) |
query, -fst |
| Structure directory | a directory of those files | query, -fst |
| Foldseek DB | base path of a DB built from structures (needs <db>_ca) |
query, -fst |
| Foldseek result | .m8 (12/17/21/29 columns) or multimer _report (14 columns) |
-fsr |
A _report (14 columns) enters the multimer path. The query is where the per-complex chains are
read from, so it must be a Foldseek query DB or a directory of structures.
With -fst <directory>, hits are looked up by accession. Foldseek splits multimers per chain
(1dci-assembly1_B-2), so the trailing _<chain> is stripped until a file matches
(1dci-assembly1.cif) and that chain alone is drawn — a plain directory of structures works as a
target, no createdb needed.
Sequence FASTA is not supported. It carries no 3D coordinates, and foldseek createdb --prostt5-model predicts 3Di (_ss) without writing any _ca, so a sequence-derived DB cannot be
rendered either. Both cases fail before rendering starts, with the reason printed.
| Key | Action |
|---|---|
0 |
Control all proteins |
1–9 |
Control individual protein |
W / A / S / D |
Move up / left / down / right |
X / Y / Z |
Rotate around X / Y / Z axis |
R / F |
Zoom in / out |
N / P |
Next hit / Previous hit |
[ / ] |
Next query / Previous query |
Q |
Quit |
Mouse hover displays residue information in the info panel.
| Mode | Description |
|---|---|
protein |
One color per protein (9 distinct colors, cycling) |
chain |
One color per chain (15 colors) |
rainbow |
N→C gradient (20-step hue spectrum) |
plddt |
AlphaFold confidence: blue (≥90), cyan (70–90), yellow (50–70), orange (<50) |
interface |
Inter-chain contacts (CA–CA < 8 Å): magenta vs. dim |
conservation |
MSA Shannon entropy: blue (variable) → red (conserved) |
align |
Structurally aligned regions: bright vs. dim gray. Uses the alignment when the result has one, otherwise falls back to distance |
align-fs |
Only what Foldseek aligned (qaln/taln columns). Never falls back — errors out if the result carries no alignment |
align-near |
Distance only: residues with a counterpart within the cutoff, whatever the result says |
The panel's Align: line names the colour source actually used: aln-string
(Foldseek alignment), msa-col (FoldMason MSA columns) or nearest-nbr
(distance). The Superpose: line in the hit block is a different thing — how the
structures were laid on top of each other (aln-string, kabsch-qaln, ...) —
and does not change with the colour mode.
The alignment-based modes need
qaln/taln, i.e. a 17/21/29-column.m8. Foldseek's default 12-column output has none. Generate a usable result with--format-output query,target,fident,alnlen,mismatch,gapopen,qstart,qend,tstart,tend,evalue,bits,lddt,qtmscore,ttmscore,qaln,taln(the search itself must run with-a).foldseek ... --view-structtydoes this for you. Multimer_reportfiles carry no alignment strings at all, so they only work withalign-near.
All modes support 3-band depth fog: near (vivid), mid (normal), far (dark, hue-retaining).
StrucTTY reads Foldseek easy-search output (.m8 format) with support for 12, 17, 21, and 29 column formats. Features include:
- Interactive hit navigation with automatic structure downloading
- Direct Foldseek DB reading (
-fst <DB>) read Cα coordinates directly from Foldseek_cadatabase, eliminating network dependency. Uses hit-based selective scanning for minimal memory usage (~152KB for 1000 hits, even on AFDB50) - Structural superposition using U/T rotation-translation matrices
- Alignment string visualization (
qaln/taln) - Multi-database support: PDB, AlphaFold DB, ESMAtlas, CATH, BFVD, and more
- Multi-query navigation (
]/[) across queries in a single.m8. The query can be a Foldseek DB or a plain directory — each accession in the result is looked up inside it, so a standalone run reproduces whatfoldseek --view-structtyshows - With a structure file as the query, hits are filtered to that file — one
.m8covering a whole query directory no longer walks you through other queries' hits. Foldseek indexes a multimer per chain, so]/[steps through the query's chains andN/Pthrough that chain's hits; the panel showsQ[3/6][2 / 14] - Multimer (complex-level) report viewing with per-complex superposition
StrucTTY is embedded directly into Foldseek as a static library (add_subdirectory(lib/structty)), so no external binary or PATH lookup is required — Foldseek calls structty::run() in-process. The viewer opens automatically once the search finishes, reading the query and target structures directly from the search's temporary databases (folder/tar/gz inputs supported). Temporary DBs are kept alive for the viewer and cleaned up after it closes. Supported workflows: easy-search and easy-multimersearch.
Automatic launch after a search — add the --view-structty flag (it takes no value):
foldseek easy-search query.cif targetDir result.m8 tmp --view-structty \
-a --format-output "query,target,fident,alnlen,mismatch,gapopen,qstart,qend,tstart,tend,evalue,bits,lddt,qtmscore,ttmscore,qaln,taln"The viewer builds that 17-column layout internally either way; passing
-a --format-output keeps a copy in result.m8, so the same hits can be
reopened later with -fsr and still show lDDT, TM scores and the aligned
regions. Without it the file keeps Foldseek's 12-column default, which carries
no alignment — align-fs then refuses to run and align falls back to
distance.
Multimer (complex-level) search — the viewer works out of the box, since the per-complex report (--multimer-report-mode 1) is the default; setting --multimer-report-mode 0 skips the launch. The viewer reads the _report file the workflow already writes, so it stays after the viewer closes:
foldseek easy-multimersearch queryDir targetDir result tmp --view-structtyThis writes result_report, the 14-column file the viewer reads. Both sides must
be complexes — a single-chain query or target yields an empty report, and the
viewer then refuses it.
First produce a result that carries the alignment. The two columns that matter
are qaln and taln; -a makes the search keep the backtraces they come from:
foldseek easy-search query.pdb targetDir result.m8 tmp \
-a --format-output "query,target,fident,alnlen,mismatch,gapopen,qstart,qend,tstart,tend,evalue,bits,lddt,qtmscore,ttmscore,qaln,taln"Add --alignment-type 1 to align with TM-align instead of 3Di+AA — slower, but
it usually reports a tighter set of aligned residues.
For a result that already exists as a Foldseek database, convert it instead of searching again:
foldseek convertalis queryDB targetDB resultDB result.m8 \
--format-output "query,target,fident,alnlen,mismatch,gapopen,qstart,qend,tstart,tend,evalue,bits,lddt,qtmscore,ttmscore,qaln,taln"# Read target Cα coordinates straight from a Foldseek DB (offline)
./StrucTTY query.pdb -fst /path/to/targetDB -fsr result.m8
# Look targets up by hit accession in a local structure directory
./StrucTTY query.pdb -fst /path/to/pdbs/ -fsr result.m8
# Download hit structures from public DBs (PDB, AFDB, ESMAtlas, CATH, BFVD, ...)
./StrucTTY query.pdb -fst auto -fsr result.m8
# Query from a Foldseek DB — multi-query navigation with ]/[
./StrucTTY /path/to/queryDB -fst /path/to/targetDB -fsr result.m8 -m align-fs
# Colour by distance instead, whatever the result says (works on 12-column files)
./StrucTTY query.pdb -fst /path/to/targetDB -fsr result.m8 -m align-near
# Multimer: 14-column _report, chains read per complex from the query DB or directory.
# A _report carries no alignment strings, so align-near is the only align mode.
./StrucTTY ../example/foldseek_result/DB1/ \
-fst ../example/foldseek_result/DB2/ \
-fsr ../example/foldseek_result/result_multimer_report -m align-nearStrucTTY loads FoldMason MSA results (JSON with Cα coordinates or FASTA) for:
- Kabsch-based structural superposition
- Column-wise conservation scoring
- Gap-aware alignment visualization
Load FASTA or A3M multiple sequence alignments to compute per-residue conservation scores via Shannon entropy, visualized with the conservation color mode.
StrucTTY renders interactively even for large complexes. The table below measures load time, time-to-first-frame (TTFF), per-frame render time, and input-to-frame latency across structures of increasing size:
| Structure | Cα | Load (ms) | TTFF (ms) | Frame mean (ms) | Frame p95 (ms) | Latency mean (ms) | Latency p95 (ms) |
|---|---|---|---|---|---|---|---|
| 1CRN | 46 | 5.4 | 44.6 | <0.5 | <0.5 | <0.5 | <0.5 |
| 1STP | 121 | 12.4 | 106.0 | <0.5 | <0.5 | <0.5 | <0.5 |
| 3BIK | 446 | 47.0 | 205.8 | 0.01 | <0.5 | 0.01 | <0.5 |
| 6VXX | 2916 | 255.0 | 894.4 | 2.97 | 3.00 | 2.99 | 3.00 |
| 4V4Q | 11463 | 3336.8 | 6199.2 | 13.36 | 14.00 | 13.68 | 14.80 |
- Frame time stays under the 16 ms (60 fps) budget even at 11,463 Cα — interaction remains smooth for structures spanning three orders of magnitude in size.
- Input latency tracks frame time closely, so rotation/zoom feels responsive with no perceptible input lag.
- Load and TTFF scale with atom count; the immediate z-test rasterizer keeps per-frame cost roughly linear in Cα count.
Benchmarks are reproducible via the built-in benchmark mode, which replays a fixed key script and logs per-frame timings to a structty_bench_*.csv file.
| Library | License | Purpose |
|---|---|---|
| Gemmi | MPL-2.0 | mmCIF/PDB file parsing |
| stb_image_write | MIT / public domain | PNG screenshot encoding |
See THIRD_PARTY_NOTICES.md for detailed license information.
This project is licensed under the MIT License.
Developed by Luna Sung-eun Jang, Soo Young Cha — Steinegger Lab

