diff --git a/README.md b/README.md
index 0ac60a18d27686a1c9e0550cdd91e84b653c6de9..c7a0e683f1167095371c524669fdecb88be37d88 100644
--- a/README.md
+++ b/README.md
@@ -51,17 +51,34 @@ This project builds on experience from previous solutions:
     npm run watch-extra
 
 ### Build/watch mol-viewer
-Build:
+**Build**
 
     npm run build
     npm run build-viewer
 
-Watch:
+**Watch**
 
     npm run watch
     npm run watch-extra
     npm run watch-viewer
 
+**Run**
+
+If not installed previously:
+
+    npm install -g http-server
+
+...or a similar solution.
+
+From the root of the project:
+
+    http-server -p PORT-NUMBER
+
+and navigate to `build/viewer`
+
+
+
+
 ## Contributing
 Just open an issue or make a pull request. All contributions are welcome.
 
diff --git a/src/apps/structure-info/model.ts b/src/apps/structure-info/model.ts
index 9c7f0d9a9876f39c78cd5887f9696ef377c1ea4b..c06c66e4eeb80a6655db82a9511f0a6c1c1cdcde 100644
--- a/src/apps/structure-info/model.ts
+++ b/src/apps/structure-info/model.ts
@@ -72,20 +72,44 @@ export function printSecStructure(model: Model) {
     }
 }
 
-export function printBonds(structure: Structure) {
-    for (const unit of structure.units) {
-        if (!Unit.isAtomic(unit)) continue;
+export function printLinks(structure: Structure, showIntra: boolean, showInter: boolean) {
+    if (showIntra) {
+        console.log('\nIntra Unit Links\n=============');
+        for (const unit of structure.units) {
+            if (!Unit.isAtomic(unit)) continue;
+
+            const elements = unit.elements;
+            const { a, b } = unit.links;
+            const { model } = unit;
+
+            if (!a.length) continue;
+
+            for (let bI = 0, _bI = a.length; bI < _bI; bI++) {
+                const x = a[bI], y = b[bI];
+                if (x >= y) continue;
+                console.log(`${atomLabel(model, elements[x])} -- ${atomLabel(model, elements[y])}`);
+            }
+        }
+    }
 
-        const elements = unit.elements;
-        const { a, b } = unit.bonds;
-        const { model }  = unit;
+    if (showInter) {
+        console.log('\nInter Unit Links\n=============');
+        const links = structure.links;
+        for (const unit of structure.units) {
+            if (!Unit.isAtomic(unit)) continue;
 
-        if (!a.length) continue;
+            for (const pairLinks of links.getLinkedUnits(unit)) {
+                if (!pairLinks.areUnitsOrdered || pairLinks.bondCount === 0) continue;
 
-        for (let bI = 0, _bI = a.length; bI < _bI; bI++) {
-            const x = a[bI], y = b[bI];
-            if (x >= y) continue;
-            console.log(`${atomLabel(model, elements[x])} -- ${atomLabel(model, elements[y])}`);
+                const { unitA, unitB } = pairLinks;
+                console.log(`${pairLinks.unitA.id} - ${pairLinks.unitB.id}: ${pairLinks.bondCount} bond(s)`);
+
+                for (const aI of pairLinks.linkedElementIndices) {
+                    for (const link of pairLinks.getBonds(aI)) {
+                        console.log(`${atomLabel(unitA.model, unitA.elements[aI])} -- ${atomLabel(unitB.model, unitB.elements[link.indexB])}`);
+                    }
+                }
+            }
         }
     }
 }
@@ -101,6 +125,18 @@ export function printSequence(model: Model) {
     console.log();
 }
 
+export function printModRes(model: Model) {
+    console.log('\nModified Residues\n=============');
+    const map = model.properties.modifiedResidueNameMap;
+    const { label_comp_id, _rowCount } = model.atomicHierarchy.residues;
+    for (let i = 0; i < _rowCount; i++) {
+        const comp_id = label_comp_id.value(i);
+        if (!map.has(comp_id)) continue;
+        console.log(`[${i}] ${map.get(comp_id)} -> ${comp_id}`);
+    }
+    console.log();
+}
+
 export function printRings(structure: Structure) {
     console.log('\nRings\n=============');
     for (const unit of structure.units) {
@@ -156,11 +192,12 @@ export function printIHMModels(model: Model) {
 async function run(mmcif: mmCIF_Database) {
     const models = await Model.create({ kind: 'mmCIF', data: mmcif }).run();
     const structure = Structure.ofModel(models[0]);
-    printSequence(models[0]);
+    //printSequence(models[0]);
     //printIHMModels(models[0]);
     printUnits(structure);
-    printRings(structure);
-    //printBonds(structure);
+    //printRings(structure);
+    printLinks(structure, true, true);
+    //printModRes(models[0]);
     //printSecStructure(models[0]);
 }
 
@@ -175,13 +212,13 @@ async function runFile(filename: string) {
 }
 
 const parser = new argparse.ArgumentParser({
-  addHelp: true,
-  description: 'Print info about a structure, mainly to test and showcase the mol-model module'
+    addHelp: true,
+    description: 'Print info about a structure, mainly to test and showcase the mol-model module'
 });
-parser.addArgument([ '--download', '-d' ], {
+parser.addArgument(['--download', '-d'], {
     help: 'Pdb entry id'
 });
-parser.addArgument([ '--file', '-f' ], {
+parser.addArgument(['--file', '-f'], {
     help: 'filename'
 });
 interface Args {
diff --git a/src/apps/viewer/index.tsx b/src/apps/viewer/index.tsx
index f828e597af544499d74c9c85afc8dea6e4bd7fd7..b4d3823d0bc0d8f2fe473fc73cf2e33b5b4ba2f6 100644
--- a/src/apps/viewer/index.tsx
+++ b/src/apps/viewer/index.tsx
@@ -22,6 +22,7 @@ import { EntityTree } from 'mol-app/ui/entity/tree';
 import { EntityTreeController } from 'mol-app/controller/entity/tree';
 import { TransformListController } from 'mol-app/controller/transform/list';
 import { TransformList } from 'mol-app/ui/transform/list';
+import { SequenceView } from 'mol-app/ui/visualization/sequence-view';
 
 const elm = document.getElementById('app')
 if (!elm) throw new Error('Can not find element with id "app".')
@@ -45,6 +46,14 @@ targets[LayoutRegion.Bottom].components.push({
     isStatic: true
 });
 
+targets[LayoutRegion.Top].components.push({
+    key: 'molstar-sequence-view',
+    controller: ctx.components.sequenceView,
+    region: LayoutRegion.Top,
+    view: SequenceView,
+    isStatic: true
+});
+
 targets[LayoutRegion.Main].components.push({
     key: 'molstar-background-jobs',
     controller: new JobsController(ctx, 'Background'),
diff --git a/src/helpers.d.ts b/src/helpers.d.ts
index 3dbdd2dc79bbbf6f3878bba7ea672785785c6490..69dca25924d4e2fc6dd927672674506e1ad935ff 100644
--- a/src/helpers.d.ts
+++ b/src/helpers.d.ts
@@ -13,4 +13,5 @@ declare module Helpers {
     export type NumberArray = TypedArray | number[]
     export type UintArray = Uint8Array | Uint16Array | Uint32Array | number[]
     export type ValueOf<T> = T[keyof T]
+    export type ArrayCtor<T> = { new(size: number): { [i: number]: T, length: number } }
 }
\ No newline at end of file
diff --git a/src/mol-app/context/context.ts b/src/mol-app/context/context.ts
index 92a317aad2dc583dcf9f1f9051a6295c26536596..5c8fee555e120226afad3fc1ecf5e58c0ea95655 100644
--- a/src/mol-app/context/context.ts
+++ b/src/mol-app/context/context.ts
@@ -15,6 +15,7 @@ import { Stage } from 'mol-view/stage';
 import { AnyTransform } from 'mol-view/state/transform';
 import { BehaviorSubject } from 'rxjs';
 import { AnyEntity } from 'mol-view/state/entity';
+import { SequenceViewController } from '../controller/visualization/sequence-view';
 
 export class Settings {
     private settings = new Map<string, any>();
@@ -35,11 +36,16 @@ export class Context {
     logger = new Logger(this);
     performance = new PerformanceMonitor();
 
-    stage = new Stage();
+    stage = new Stage(this);
     viewport = new ViewportController(this);
     layout: LayoutController;
     settings = new Settings();
 
+    // TODO: this is a temporary solution
+    components = {
+        sequenceView: new SequenceViewController(this)
+    };
+
     currentEntity = new BehaviorSubject(undefined) as BehaviorSubject<AnyEntity | undefined>
     currentTransforms = new BehaviorSubject([] as AnyTransform[])
 
diff --git a/src/mol-app/controller/visualization/sequence-view.ts b/src/mol-app/controller/visualization/sequence-view.ts
new file mode 100644
index 0000000000000000000000000000000000000000..17423f9806beef2b22a3eb3c4a4117eed3bf1534
--- /dev/null
+++ b/src/mol-app/controller/visualization/sequence-view.ts
@@ -0,0 +1,21 @@
+/**
+ * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author David Sehnal <david.sehnal@gmail.com>
+ */
+
+import { shallowClone } from 'mol-util';
+import { Context } from '../../context/context'
+import { Controller } from '../controller';
+import { Structure } from 'mol-model/structure';
+
+export const DefaultSequenceViewState = {
+    structure: void 0 as (Structure | undefined)
+}
+export type SequenceViewState = typeof DefaultSequenceViewState
+
+export class SequenceViewController extends Controller<SequenceViewState> {
+    constructor(context: Context) {
+        super(context, shallowClone(DefaultSequenceViewState));
+    }
+}
\ No newline at end of file
diff --git a/src/mol-app/event/basic.ts b/src/mol-app/event/basic.ts
index 99cf366f9c83e66e05af729e3224d74ec7f252de..b87ea5c14eec10f8c2a7ff7161cd569221611bde 100644
--- a/src/mol-app/event/basic.ts
+++ b/src/mol-app/event/basic.ts
@@ -11,6 +11,7 @@ import { Dispatcher } from '../service/dispatcher'
 import { LayoutState } from '../controller/layout';
 import { ViewportOptions } from '../controller/visualization/viewport';
 import { Job } from '../service/job';
+import { Element } from 'mol-model/structure'
 
 const Lane = Dispatcher.Lane;
 
@@ -31,3 +32,7 @@ export namespace LayoutEvents {
     export const SetState = Event.create<Partial<LayoutState>>('lm.cmd.Layout.SetState', Lane.Slow);
     export const SetViewportOptions = Event.create<ViewportOptions>('bs.cmd.Layout.SetViewportOptions', Lane.Slow);
 }
+
+export namespace InteractivityEvents {
+    export const HighlightElementLoci = Event.create<Element.Loci | undefined>('bs.Interactivity.HighlightElementLoci', Lane.Slow);
+}
diff --git a/src/mol-app/skin/components/sequence-view.scss b/src/mol-app/skin/components/sequence-view.scss
new file mode 100644
index 0000000000000000000000000000000000000000..b8bf891675cec72eb8cb18bdd57e5fd6d63a4c7f
--- /dev/null
+++ b/src/mol-app/skin/components/sequence-view.scss
@@ -0,0 +1,9 @@
+.molstar-sequence-view-wrap {
+    position: absolute;
+    right: 0;
+    top: 0;
+    left: 0;
+    bottom: 0;
+    overflow: hidden;
+    overflow-x: scroll;
+}
\ No newline at end of file
diff --git a/src/mol-app/skin/layout/common.scss b/src/mol-app/skin/layout/common.scss
index b84b69214b3468b2d165104c9a20c6ff0cf542c3..219de13b1086c2c6ecd7d3ac1d27b5007b1e97c8 100644
--- a/src/mol-app/skin/layout/common.scss
+++ b/src/mol-app/skin/layout/common.scss
@@ -23,7 +23,7 @@
     overflow: hidden;
 }
 
-.molstar-layout-main, .molstar-layout-bottom {
+.molstar-layout-main, .molstar-layout-bottom, .molstar-layout-top {
     .molstar-layout-static {
         left: 0;
         right: 0;
diff --git a/src/mol-app/skin/ui.scss b/src/mol-app/skin/ui.scss
index 41e78fc7465ee379df02eb3565bcac082c6c4584..cd572232a6843ab134fca6b60e97568d8a4730ee 100644
--- a/src/mol-app/skin/ui.scss
+++ b/src/mol-app/skin/ui.scss
@@ -35,4 +35,5 @@
 @import 'components/misc';
 @import 'components/panel';
 @import 'components/slider';
-@import 'components/viewport';
\ No newline at end of file
+@import 'components/viewport';
+@import 'components/sequence-view';
\ No newline at end of file
diff --git a/src/mol-app/ui/visualization/sequence-view.tsx b/src/mol-app/ui/visualization/sequence-view.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..21bef90b8afe67226ea4cf62b799778664043b73
--- /dev/null
+++ b/src/mol-app/ui/visualization/sequence-view.tsx
@@ -0,0 +1,85 @@
+/**
+ * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author David Sehnal <david.sehnal@gmail.com>
+ */
+
+import * as React from 'react'
+import { View } from '../view';
+import { SequenceViewController } from '../../controller/visualization/sequence-view';
+import { Structure, StructureSequence, Queries, Selection } from 'mol-model/structure';
+import { Context } from '../../context/context';
+import { InteractivityEvents } from '../../event/basic';
+import { SyncRuntimeContext } from 'mol-task/execution/synchronous';
+
+export class SequenceView extends View<SequenceViewController, {}, {}> {
+    render() {
+        const s = this.controller.latestState.structure;
+        if (!s) return <div className='molstar-sequence-view-wrap'>No structure available.</div>;
+
+        const seqs = Structure.getModels(s)[0].sequence.sequences;
+        return <div className='molstar-sequence-view-wrap'>
+            {seqs.map((seq, i) => <EntitySequence key={i} ctx={this.controller.context} seq={seq} structure={s} /> )}
+        </div>;
+    }
+}
+
+function createQuery(entityId: string, label_seq_id: number) {
+    return Queries.generators.atoms({
+        entityTest: l => Queries.props.entity.id(l) === entityId,
+        residueTest: l => Queries.props.residue.label_seq_id(l) === label_seq_id
+    });
+}
+
+// TODO: this is really ineffective and should be done using a canvas.
+class EntitySequence extends React.Component<{ ctx: Context, seq: StructureSequence.Entity, structure: Structure }> {
+
+    async raiseInteractityEvent(seqId?: number) {
+        if (typeof seqId === 'undefined') {
+            InteractivityEvents.HighlightElementLoci.dispatch(this.props.ctx, void 0);
+            return;
+        }
+
+        const query = createQuery(this.props.seq.entityId, seqId);
+        const loci = Selection.toLoci(await query(this.props.structure, SyncRuntimeContext));
+        if (loci.elements.length === 0) InteractivityEvents.HighlightElementLoci.dispatch(this.props.ctx, void 0);
+        else InteractivityEvents.HighlightElementLoci.dispatch(this.props.ctx, loci);
+    }
+
+
+    render() {
+        const { ctx, seq } = this.props;
+        const { offset, sequence } = seq.sequence;
+
+        const elems: JSX.Element[] = [];
+        for (let i = 0, _i = sequence.length; i < _i; i++) {
+            elems[elems.length] = <ResidueView ctx={ctx} seqId={offset + i} letter={sequence[i]} parent={this} key={i} />;
+        }
+
+        return <div style={{ wordWrap: 'break-word' }}>
+            <span style={{ fontWeight: 'bold' }}>{this.props.seq.entityId}:{offset}&nbsp;</span>
+            {elems}
+        </div>;
+    }
+}
+
+class ResidueView extends React.Component<{ ctx: Context, seqId: number, letter: string, parent: EntitySequence }, { isHighlighted: boolean }> {
+    state = { isHighlighted: false }
+
+    mouseEnter = () => {
+        this.setState({ isHighlighted: true });
+        this.props.parent.raiseInteractityEvent(this.props.seqId);
+    }
+
+    mouseLeave = () => {
+        this.setState({ isHighlighted: false });
+        this.props.parent.raiseInteractityEvent();
+    }
+
+    render() {
+        return <span onMouseEnter={this.mouseEnter} onMouseLeave={this.mouseLeave}
+            style={{ cursor: 'pointer', backgroundColor: this.state.isHighlighted ? 'yellow' : void 0 }}>
+            {this.props.letter}
+        </span>;
+    }
+}
\ No newline at end of file
diff --git a/src/mol-data/int/_spec/sorted-array.spec.ts b/src/mol-data/int/_spec/sorted-array.spec.ts
index 2cd82af32ce9046bb8c2b7a3a71779ea68084071..c5ca33cf506892bc90d9da503a241d0fc0f2bfe4 100644
--- a/src/mol-data/int/_spec/sorted-array.spec.ts
+++ b/src/mol-data/int/_spec/sorted-array.spec.ts
@@ -59,6 +59,10 @@ describe('sortedArray', () => {
         compareArrays(SortedArray.deduplicate(SortedArray.ofSortedArray([1, 2, 3])), [1, 2, 3]);
     });
 
+    it('indicesOf', () => {
+        compareArrays(SortedArray.indicesOf(SortedArray.ofSortedArray([10, 11, 12]), SortedArray.ofSortedArray([10, 12, 14])), [0, 2]);
+    })
+
     // console.log(Interval.findPredecessorIndexInInterval(Interval.ofBounds(0, 3), 2, Interval.ofBounds(0, 3)))
     // console.log(SortedArray.findPredecessorIndexInInterval(SortedArray.ofSortedArray([0, 1, 2]), 2, Interval.ofBounds(0, 3)))
 });
\ No newline at end of file
diff --git a/src/mol-data/int/impl/sorted-array.ts b/src/mol-data/int/impl/sorted-array.ts
index 4a5476c16c46de1194639194442ba6b904339a81..09b787726d0f33f11fe4422525c18c56948e2ce2 100644
--- a/src/mol-data/int/impl/sorted-array.ts
+++ b/src/mol-data/int/impl/sorted-array.ts
@@ -4,7 +4,7 @@
  * @author David Sehnal <david.sehnal@gmail.com>
  */
 
-import { sortArray, hash3, hash4 } from '../../util'
+import { sortArray, hash3, hash4, createRangeArray } from '../../util'
 import Interval from '../interval'
 
 type Nums = ArrayLike<number>
@@ -289,6 +289,39 @@ export function deduplicate(xs: Nums) {
     return ret;
 }
 
+export function indicesOf(a: Nums, b: Nums): Nums {
+    if (a === b) return ofSortedArray(createRangeArray(0, a.length - 1));
+
+    const { startI: sI, startJ: sJ, endI, endJ } = getSuitableIntersectionRange(a, b);
+    let i = sI, j = sJ;
+    let commonCount = 0;
+    while (i < endI && j < endJ) {
+        const x = a[i], y = b[j];
+        if (x < y) { i++; }
+        else if (x > y) { j++; }
+        else { i++; j++; commonCount++; }
+    }
+
+    const lenA = a.length;
+    // no common elements
+    if (!commonCount) return Empty;
+    // A is subset of B ==> A
+    if (commonCount === lenA) return ofSortedArray(createRangeArray(0, a.length - 1));
+
+    const indices = new Int32Array(commonCount);
+    let offset = 0;
+    i = sI;
+    j = sJ;
+    while (i < endI && j < endJ) {
+        const x = a[i], y = b[j];
+        if (x < y) { i++; }
+        else if (x > y) { j++; }
+        else { indices[offset++] = i; i++; j++; }
+    }
+
+    return ofSortedArray(indices);
+}
+
 const _maxIntRangeRet = { startI: 0, startJ: 0, endI: 0, endJ: 0 };
 // for small sets, just gets the whole range, for large sets does a bunch of binary searches
 function getSuitableIntersectionRange(a: Nums, b: Nums) {
diff --git a/src/mol-data/int/sorted-array.ts b/src/mol-data/int/sorted-array.ts
index 928e383ddf1a083f9a652428131074e4d5a8c735..2c92aa329d3dc16ebd1e1bdfd977b11426d9db09 100644
--- a/src/mol-data/int/sorted-array.ts
+++ b/src/mol-data/int/sorted-array.ts
@@ -41,7 +41,9 @@ namespace SortedArray {
     export const findPredecessorIndexInInterval: (array: SortedArray, x: number, bounds: Interval) => number = Impl.findPredecessorIndexInInterval as any;
     export const findRange: (array: SortedArray, min: number, max: number) => Interval = Impl.findRange as any;
 
-    export const deduplicate: (arrat: SortedArray) => SortedArray = Impl.deduplicate as any;
+    export const deduplicate: (array: SortedArray) => SortedArray = Impl.deduplicate as any;
+    /** Returns indices of xs in the array. E.g. indicesOf([10, 11, 12], [10, 12]) ==> [0, 2] */
+    export const indicesOf: (array: SortedArray, xs: SortedArray) => SortedArray = Impl.indicesOf as any;
 }
 
 interface SortedArray extends ArrayLike<number> { '@type': 'int-sorted-array' }
diff --git a/src/mol-data/util/array.ts b/src/mol-data/util/array.ts
index 93707e7978d657144d2b487a4c665c5f8eaf0198..94311c38c2ad057768411f1c65c9762244c341e5 100644
--- a/src/mol-data/util/array.ts
+++ b/src/mol-data/util/array.ts
@@ -22,4 +22,28 @@ export function iterableToArray<T>(it: IterableIterator<T>): T[] {
         ret[ret.length] = value;
     }
     return ret;
+}
+
+/** Fills the array so that array[0] = start and array[array.length - 1] = end */
+export function createRangeArray(start: number, end: number, ctor?: Helpers.ArrayCtor<number>) {
+    const len = end - start + 1;
+    const array = ctor ? new ctor(len) : new Int32Array(len);
+    for (let i = 0; i < len; i++) {
+        array[i] = i + start;
+    }
+    return array;
+}
+
+export function arrayPickIndices<T>(array: ArrayLike<T>, indices: ArrayLike<number>) {
+    const ret = new (arrayGetCtor(array))(indices.length);
+    for (let i = 0, _i = indices.length; i < _i; i++) {
+        ret[i] = array[indices[i]];
+    }
+    return ret;
+}
+
+export function arrayGetCtor<T>(data: ArrayLike<T>): Helpers.ArrayCtor<T> {
+    const ret = (data as any).constructor;
+    if (!ret) throw new Error('data does not define a constructor and it should');
+    return ret;
 }
\ No newline at end of file
diff --git a/src/mol-geo/representation/structure/bond.ts b/src/mol-geo/representation/structure/bond.ts
index 28327d08dbf9b90a64c4e2f610196030a56efacd..492731141fe21febd63d55f474b82e5d77985818 100644
--- a/src/mol-geo/representation/structure/bond.ts
+++ b/src/mol-geo/representation/structure/bond.ts
@@ -10,7 +10,7 @@
 import { ValueCell } from 'mol-util/value-cell'
 
 import { RenderObject, createMeshRenderObject, MeshRenderObject } from 'mol-gl/render-object'
-import { Unit, Element, Bond } from 'mol-model/structure';
+import { Unit, Element, Link } from 'mol-model/structure';
 import { UnitsRepresentation, DefaultStructureProps } from './index';
 import { Task } from 'mol-task'
 import { createTransforms } from './utils';
@@ -31,7 +31,7 @@ function createBondMesh(unit: Unit, mesh?: Mesh) {
         if (!Unit.isAtomic(unit)) return Mesh.createEmpty(mesh)
 
         const elements = unit.elements;
-        const bonds = unit.bonds
+        const bonds = unit.links
         const { edgeCount, a, b } = bonds
 
         if (!edgeCount) return Mesh.createEmpty(mesh)
@@ -98,7 +98,7 @@ export default function BondUnitsRepresentation(): UnitsRepresentation<BondProps
                 currentGroup = group
 
                 const unit = group.units[0]
-                const elementCount = Unit.isAtomic(unit) ? unit.bonds.edgeCount * 2 : 0
+                const elementCount = Unit.isAtomic(unit) ? unit.links.edgeCount * 2 : 0
                 const instanceCount = group.units.length
 
                 mesh = await createBondMesh(unit).runAsChild(ctx, 'Computing bond mesh')
@@ -167,11 +167,11 @@ export default function BondUnitsRepresentation(): UnitsRepresentation<BondProps
             const { objectId, instanceId, elementId } = pickingId
             const unit = currentGroup.units[instanceId]
             if (cylinders.id === objectId && Unit.isAtomic(unit)) {
-                return Bond.Loci([{
+                return Link.Loci([{
                     aUnit: unit,
-                    aIndex: unit.bonds.a[elementId],
+                    aIndex: unit.links.a[elementId],
                     bUnit: unit,
-                    bIndex: unit.bonds.b[elementId]
+                    bIndex: unit.links.b[elementId]
                 }])
             }
             return null
@@ -182,7 +182,7 @@ export default function BondUnitsRepresentation(): UnitsRepresentation<BondProps
             const unit = group.units[0]
             if (!Unit.isAtomic(unit)) return
 
-            const elementCount = unit.bonds.edgeCount * 2
+            const elementCount = unit.links.edgeCount * 2
             const instanceCount = group.units.length
 
             let changed = false
@@ -190,11 +190,11 @@ export default function BondUnitsRepresentation(): UnitsRepresentation<BondProps
             if (isEveryLoci(loci)) {
                 applyMarkerAction(array, 0, elementCount * instanceCount, action)
                 changed = true
-            } else if (Bond.isLoci(loci)) {
-                for (const b of loci.bonds) {
+            } else if (Link.isLoci(loci)) {
+                for (const b of loci.links) {
                     const unitIdx = Unit.findUnitById(b.aUnit.id, group.units)
                     if (unitIdx !== -1) {
-                        const _idx = unit.bonds.getEdgeIndex(b.aIndex, b.bIndex)
+                        const _idx = unit.links.getEdgeIndex(b.aIndex, b.bIndex)
                         if (_idx !== -1) {
                             const idx = _idx
                             if (applyMarkerAction(array, idx, idx + 1, action) && !changed) {
diff --git a/src/mol-math/graph.ts b/src/mol-math/graph.ts
index ce0429662fe04ce07a6a24a56e8a25c188bb4cf1..f1e24c2f39e5cbaef4ae6e7c6f8e5d1ed5dc9f05 100644
--- a/src/mol-math/graph.ts
+++ b/src/mol-math/graph.ts
@@ -4,4 +4,4 @@
  * @author David Sehnal <david.sehnal@gmail.com>
  */
 
-export * from './graph/int/graph'
\ No newline at end of file
+export * from './graph/int-adjacency-graph'
\ No newline at end of file
diff --git a/src/mol-math/graph/_spec/int-graph.spec.ts b/src/mol-math/graph/_spec/int-graph.spec.ts
index 3d75dddb0296d749b60261249cfd666ddb1d8a35..5d0315e658ef3e3662a08149f8d10b18cfe770ba 100644
--- a/src/mol-math/graph/_spec/int-graph.spec.ts
+++ b/src/mol-math/graph/_spec/int-graph.spec.ts
@@ -4,7 +4,7 @@
  * @author David Sehnal <david.sehnal@gmail.com>
  */
 
-import { IntGraph } from '../int/graph';
+import { IntAdjacencyGraph } from '../int-adjacency-graph';
 
 describe('IntGraph', () => {
     const vc = 3;
@@ -12,7 +12,7 @@ describe('IntGraph', () => {
     const ys = [1, 2, 0];
     const _prop = [10, 11, 12];
 
-    const builder = new IntGraph.EdgeBuilder(vc, xs, ys);
+    const builder = new IntAdjacencyGraph.EdgeBuilder(vc, xs, ys);
     const prop: number[] = new Array(builder.slotCount);
     for (let i = 0; i < builder.edgeCount; i++) {
         builder.addNextEdge();
@@ -28,9 +28,16 @@ describe('IntGraph', () => {
     });
 
     it('triangle-propAndEdgeIndex', () => {
-        const prop = graph.prop;
+        const prop = graph.edgeProps.prop;
         expect(prop[graph.getEdgeIndex(0, 1)]).toBe(10);
         expect(prop[graph.getEdgeIndex(1, 2)]).toBe(11);
         expect(prop[graph.getEdgeIndex(2, 0)]).toBe(12);
     });
+
+    it('induce', () => {
+        const induced = IntAdjacencyGraph.induceByVertices(graph, [1, 2]);
+        expect(induced.vertexCount).toBe(2);
+        expect(induced.edgeCount).toBe(1);
+        expect(induced.edgeProps.prop[induced.getEdgeIndex(0, 1)]).toBe(11);
+    })
 });
\ No newline at end of file
diff --git a/src/mol-math/graph/int/graph.ts b/src/mol-math/graph/int-adjacency-graph.ts
similarity index 60%
rename from src/mol-math/graph/int/graph.ts
rename to src/mol-math/graph/int-adjacency-graph.ts
index bc2666a2c064bca1f63193ee27709df9019b3e8c..8b5af369dbd1e71e6e166311c67d61bb11036d77 100644
--- a/src/mol-math/graph/int/graph.ts
+++ b/src/mol-math/graph/int-adjacency-graph.ts
@@ -4,6 +4,8 @@
  * @author David Sehnal <david.sehnal@gmail.com>
  */
 
+import { arrayPickIndices } from 'mol-data/util';
+
 /**
  * Represent a graph using vertex adjacency list.
  *
@@ -12,12 +14,13 @@
  *
  * Edge properties are indexed same as in the arrays a and b.
  */
-type IntGraph<EdgeProperties extends object = { }> = {
+interface IntAdjacencyGraph<EdgeProps extends IntAdjacencyGraph.EdgePropsBase = {}> {
     readonly offset: ArrayLike<number>,
     readonly a: ArrayLike<number>,
     readonly b: ArrayLike<number>,
     readonly vertexCount: number,
     readonly edgeCount: number,
+    readonly edgeProps: Readonly<EdgeProps>
 
     /**
      * Get the edge index between i-th and j-th vertex.
@@ -28,11 +31,14 @@ type IntGraph<EdgeProperties extends object = { }> = {
      */
     getEdgeIndex(i: number, j: number): number,
     getVertexEdgeCount(i: number): number
-} & EdgeProperties
+}
 
-namespace IntGraph {
-    class Impl implements IntGraph<any> {
+namespace IntAdjacencyGraph {
+    export type EdgePropsBase = { [name: string]: ArrayLike<any> }
+
+    class IntGraphImpl implements IntAdjacencyGraph<any> {
         readonly vertexCount: number;
+        readonly edgeProps: object;
 
         getEdgeIndex(i: number, j: number): number {
             let a, b;
@@ -48,18 +54,14 @@ namespace IntGraph {
             return this.offset[i + 1] - this.offset[i];
         }
 
-        constructor(public offset: ArrayLike<number>, public a: ArrayLike<number>, public b: ArrayLike<number>, public edgeCount: number, props?: any) {
+        constructor(public offset: ArrayLike<number>, public a: ArrayLike<number>, public b: ArrayLike<number>, public edgeCount: number, edgeProps?: any) {
             this.vertexCount = offset.length - 1;
-            if (props) {
-                for (const p of Object.keys(props)) {
-                    (this as any)[p] = props[p];
-                }
-            }
+            this.edgeProps = edgeProps || {};
         }
     }
 
-    export function create<EdgeProps extends object = { }>(offset: ArrayLike<number>, a: ArrayLike<number>, b: ArrayLike<number>, edgeCount: number, edgeProps?: EdgeProps): IntGraph<EdgeProps> {
-        return new Impl(offset, a, b, edgeCount, edgeProps) as IntGraph<EdgeProps>;
+    export function create<EdgeProps extends IntAdjacencyGraph.EdgePropsBase = {}>(offset: ArrayLike<number>, a: ArrayLike<number>, b: ArrayLike<number>, edgeCount: number, edgeProps?: EdgeProps): IntAdjacencyGraph<EdgeProps> {
+        return new IntGraphImpl(offset, a, b, edgeCount, edgeProps) as IntAdjacencyGraph<EdgeProps>;
     }
 
     export class EdgeBuilder {
@@ -75,7 +77,7 @@ namespace IntGraph {
         a: Int32Array;
         b: Int32Array;
 
-        createGraph<EdgeProps extends object = { }>(edgeProps?: EdgeProps) {
+        createGraph<EdgeProps extends IntAdjacencyGraph.EdgePropsBase = {}>(edgeProps?: EdgeProps) {
             return create(this.offsets, this.a, this.b, this.edgeCount, edgeProps);
         }
 
@@ -132,6 +134,47 @@ namespace IntGraph {
             this.b = new Int32Array(offset);
         }
     }
+
+    export function induceByVertices<P extends IntAdjacencyGraph.EdgePropsBase>(graph: IntAdjacencyGraph<P>, vertexIndices: ArrayLike<number>): IntAdjacencyGraph<P> {
+        const { b, offset, vertexCount, edgeProps } = graph;
+        const vertexMap = new Int32Array(vertexCount);
+        for (let i = 0, _i = vertexIndices.length; i < _i; i++) vertexMap[vertexIndices[i]] = i + 1;
+
+        let newEdgeCount = 0;
+        for (let i = 0; i < vertexCount; i++) {
+            if (vertexMap[i] === 0) continue;
+            for (let j = offset[i], _j = offset[i + 1]; j < _j; j++) {
+                if (b[j] > i && vertexMap[b[j]] !== 0) newEdgeCount++;
+            }
+        }
+
+        const newOffsets = new Int32Array(vertexIndices.length + 1);
+        const edgeIndices = new Int32Array(2 * newEdgeCount);
+        const newA = new Int32Array(2 * newEdgeCount);
+        const newB = new Int32Array(2 * newEdgeCount);
+        let eo = 0, vo = 0;
+        for (let i = 0; i < vertexCount; i++) {
+            if (vertexMap[i] === 0) continue;
+            const aa = vertexMap[i] - 1;
+            for (let j = offset[i], _j = offset[i + 1]; j < _j; j++) {
+                const bb = vertexMap[b[j]];
+                if (bb === 0) continue;
+
+                newA[eo] = aa;
+                newB[eo] = bb - 1;
+                edgeIndices[eo] = j;
+                eo++;
+            }
+            newOffsets[++vo] = eo;
+        }
+
+        const newEdgeProps: P = {} as any;
+        for (const key of Object.keys(edgeProps)) {
+            newEdgeProps[key] = arrayPickIndices(edgeProps[key], edgeIndices);
+        }
+
+        return create(newOffsets, newA, newB, newEdgeCount, newEdgeProps);
+    }
 }
 
-export { IntGraph }
\ No newline at end of file
+export { IntAdjacencyGraph }
\ No newline at end of file
diff --git a/src/mol-model/loci.ts b/src/mol-model/loci.ts
index 3d10cb93063593a236d649b6a2911b492c00d300..586ee66d308c5835340cb233bcfa78125f32487b 100644
--- a/src/mol-model/loci.ts
+++ b/src/mol-model/loci.ts
@@ -5,7 +5,7 @@
  */
 
 import { Element } from './structure'
-import { Bond } from './structure/structure/unit/bonds'
+import { Link } from './structure/structure/unit/links'
 
 /** A Loci that includes every loci */
 export const EveryLoci = { kind: 'every-loci' as 'every-loci' }
@@ -14,4 +14,4 @@ export function isEveryLoci(x: any): x is EveryLoci {
     return !!x && x.kind === 'every-loci';
 }
 
-export type Loci =  Element.Loci | Bond.Loci | EveryLoci
\ No newline at end of file
+export type Loci =  Element.Loci | Link.Loci | EveryLoci
\ No newline at end of file
diff --git a/src/mol-model/sequence/constants.ts b/src/mol-model/sequence/constants.ts
index 192d137f5bbd93804bbcdfe509d8f4cf20aacc02..17796f5996821e804b370738c42af8c7facd73d4 100644
--- a/src/mol-model/sequence/constants.ts
+++ b/src/mol-model/sequence/constants.ts
@@ -7,10 +7,12 @@
 export type AminoAlphabet =
     | 'H' | 'R' | 'K' | 'I' | 'F' | 'L' | 'W' | 'A' | 'M' | 'P' | 'C' | 'N' | 'V' | 'G' | 'S' | 'Q' | 'Y' | 'D' | 'E' | 'T' | 'U' | 'O'
     | 'X' /** = Unknown */
+    | '-' /** = Gap */
 
 export type NuclecicAlphabet =
     | 'A' | 'C' | 'G' | 'T' | 'U'
     | 'X' /** = Unknown */
+    | '-' /** = Gap */
 
 // from NGL
 const ProteinOneLetterCodes: { [name: string]: AminoAlphabet }  = {
diff --git a/src/mol-model/sequence/sequence.ts b/src/mol-model/sequence/sequence.ts
index 7f99ca54ce82ad570fb6f3d76e24d49f730b23f4..9401390e5b74d3efe3887f3959c9bc74864bbbf8 100644
--- a/src/mol-model/sequence/sequence.ts
+++ b/src/mol-model/sequence/sequence.ts
@@ -29,7 +29,7 @@ namespace Sequence {
     export interface Protein extends Base<Kind.Protein, AminoAlphabet> { }
     export interface RNA extends Base<Kind.RNA, NuclecicAlphabet> { }
     export interface DNA extends Base<Kind.DNA, NuclecicAlphabet> { }
-    export interface Generic extends Base<Kind.Generic, 'X'> { }
+    export interface Generic extends Base<Kind.Generic, 'X' | '-'> { }
 
     export function create(kind: Kind, sequence: string, offset: number = 0): Sequence {
         return { kind: kind as any, sequence: sequence as any, offset };
@@ -49,11 +49,21 @@ namespace Sequence {
         return { kind: Kind.Generic, code: (v: string) => 'X' };
     }
 
-    export function ofResidueNames(residueName: Column<string>, seqId: Column<number>): Sequence {
+    function modCode(code: (name: string) => string, map: Map<string, string>): (name: string) => string {
+        return n => {
+            const ret = code(n);
+            if (ret !== 'X' || !map.has(n)) return ret;
+            return code(map.get(n)!);
+        }
+    }
+
+    export function ofResidueNames(residueName: Column<string>, seqId: Column<number>, modifiedMap?: Map<string, string>): Sequence {
         if (seqId.rowCount === 0) throw new Error('cannot be empty');
 
         const { kind, code } = determineKind(residueName);
-        return new Impl(kind, residueName, seqId, code) as Sequence;
+
+        if (!modifiedMap || modifiedMap.size === 0) return new Impl(kind, residueName, seqId, code) as Sequence;
+        return new Impl(kind, residueName, seqId, modCode(code, modifiedMap)) as Sequence;
     }
 
     class Impl implements Base<any, any> {
@@ -83,7 +93,7 @@ namespace Sequence {
             const count = maxSeqId - minSeqId + 1;
             const sequenceArray = new Array(maxSeqId + 1);
             for (let i = 0; i < count; i++) {
-                sequenceArray[i] = 'X';
+                sequenceArray[i] = '-';
             }
 
             for (let i = 0, _i = this.seqId.rowCount; i < _i; i++) {
diff --git a/src/mol-model/structure/model.ts b/src/mol-model/structure/model.ts
index 1704ab09282eb09ec1e30a442532e52dc80def63..0551a4c2098ebf6cb5ec74367fb37905d55b0500 100644
--- a/src/mol-model/structure/model.ts
+++ b/src/mol-model/structure/model.ts
@@ -8,5 +8,6 @@ import Model from './model/model'
 import * as Types from './model/types'
 import Format from './model/format'
 import { ModelSymmetry } from './model/properties/symmetry'
+import StructureSequence from './model/properties/sequence'
 
-export { Model, Types, Format, ModelSymmetry }
\ No newline at end of file
+export { Model, Types, Format, ModelSymmetry, StructureSequence }
\ No newline at end of file
diff --git a/src/mol-model/structure/model/formats/mmcif.ts b/src/mol-model/structure/model/formats/mmcif.ts
index a50d293112e9e2316c7f8f4afa0b344d4652790d..a5c8c500844863e4aef5c76f525e2efb80fbd8ef 100644
--- a/src/mol-model/structure/model/formats/mmcif.ts
+++ b/src/mol-model/structure/model/formats/mmcif.ts
@@ -39,11 +39,12 @@ function findHierarchyOffsets({ data }: mmCIF_Format, bounds: Interval) {
     const start = Interval.start(bounds), end = Interval.end(bounds);
     const residues = [start], chains = [start];
 
-    const { label_entity_id, auth_asym_id, auth_seq_id, pdbx_PDB_ins_code, label_comp_id } = data.atom_site;
+    const { label_entity_id, label_asym_id, label_seq_id, auth_seq_id, pdbx_PDB_ins_code, label_comp_id } = data.atom_site;
 
     for (let i = start + 1; i < end; i++) {
-        const newChain = !label_entity_id.areValuesEqual(i - 1, i) || !auth_asym_id.areValuesEqual(i - 1, i);
+        const newChain = !label_entity_id.areValuesEqual(i - 1, i) || !label_asym_id.areValuesEqual(i - 1, i);
         const newResidue = newChain
+            || !label_seq_id.areValuesEqual(i - 1, i)
             || !auth_seq_id.areValuesEqual(i - 1, i)
             || !pdbx_PDB_ins_code.areValuesEqual(i - 1, i)
             || !label_comp_id.areValuesEqual(i - 1, i);
@@ -118,6 +119,19 @@ function isHierarchyDataEqual(a: AtomicData, b: AtomicData) {
         && Table.areEqual(a.atoms as Table<AtomsSchema>, b.atoms as Table<AtomsSchema>)
 }
 
+function modResMap(format: mmCIF_Format) {
+    const data = format.data.pdbx_struct_mod_residue;
+    const map = new Map<string, string>();
+    const comp_id = data.label_comp_id.isDefined ? data.label_comp_id : data.auth_comp_id;
+    const parent_id = data.parent_comp_id;
+
+    for (let i = 0; i < data._rowCount; i++) {
+        map.set(comp_id.value(i), parent_id.value(i));
+    }
+
+    return map;
+}
+
 function createModel(format: mmCIF_Format, bounds: Interval, previous?: Model): Model {
     const hierarchyOffsets = findHierarchyOffsets(format, bounds);
     const hierarchyData = createHierarchyData(format, bounds, hierarchyOffsets);
@@ -146,6 +160,8 @@ function createModel(format: mmCIF_Format, bounds: Interval, previous?: Model):
         ? format.data.entry.id.value(0)
         : format.data._name;
 
+    const modifiedResidueNameMap = modResMap(format);
+
     return {
         id: UUID.create(),
         label,
@@ -153,12 +169,13 @@ function createModel(format: mmCIF_Format, bounds: Interval, previous?: Model):
         modelNum: format.data.atom_site.pdbx_PDB_model_num.value(Interval.start(bounds)),
         entities,
         atomicHierarchy,
-        sequence: getSequence(format.data, entities, atomicHierarchy),
+        sequence: getSequence(format.data, entities, atomicHierarchy, modifiedResidueNameMap),
         atomicConformation: getConformation(format, bounds),
         coarseHierarchy: coarse.hierarchy,
         coarseConformation: coarse.conformation,
         properties: {
-            secondaryStructure: getSecondaryStructureMmCif(format.data, atomicHierarchy)
+            secondaryStructure: getSecondaryStructureMmCif(format.data, atomicHierarchy),
+            modifiedResidueNameMap
         },
         symmetry: getSymmetry(format)
     };
diff --git a/src/mol-model/structure/model/formats/mmcif/bonds.ts b/src/mol-model/structure/model/formats/mmcif/bonds.ts
index 89abf70d400bf0da529f3841d875bbfbd83a968f..4eb62528687553ac5ba1ff9afb74d2ea0d31afa2 100644
--- a/src/mol-model/structure/model/formats/mmcif/bonds.ts
+++ b/src/mol-model/structure/model/formats/mmcif/bonds.ts
@@ -6,72 +6,80 @@
  */
 
 import Model from '../../model'
-import { BondType } from '../../types'
+import { LinkType } from '../../types'
 import { findEntityIdByAsymId, findAtomIndexByLabelName } from './util'
 import { Column } from 'mol-data/db'
-import { IntraUnitBonds } from '../../../structure/unit/bonds';
 
-export class StructConn implements IntraUnitBonds.StructConn {
-    private _residuePairIndex: Map<string, StructConn.Entry[]> | undefined = void 0;
-    private _atomIndex: Map<number, StructConn.Entry[]> | undefined = void 0;
+export interface StructConn {
+    getResidueEntries(residueAIndex: number, residueBIndex: number): ReadonlyArray<StructConn.Entry>
+    getAtomEntries(atomIndex: number): ReadonlyArray<StructConn.Entry>
+}
+
+export interface ComponentBond {
+    entries: Map<string, ComponentBond.Entry>
+}
 
-    private static _resKey(rA: number, rB: number) {
+export namespace StructConn {
+    function _resKey(rA: number, rB: number) {
         if (rA < rB) return `${rA}-${rB}`;
         return `${rB}-${rA}`;
     }
-
-    private getResiduePairIndex() {
-        if (this._residuePairIndex) return this._residuePairIndex;
-        this._residuePairIndex = new Map();
-        for (const e of this.entries) {
-            const ps = e.partners;
-            const l = ps.length;
-            for (let i = 0; i < l - 1; i++) {
-                for (let j = i + i; j < l; j++) {
-                    const key = StructConn._resKey(ps[i].residueIndex, ps[j].residueIndex);
-                    if (this._residuePairIndex.has(key)) {
-                        this._residuePairIndex.get(key)!.push(e);
-                    } else {
-                        this._residuePairIndex.set(key, [e]);
+    const _emptyEntry: Entry[] = [];
+
+    class StructConnImpl implements StructConn {
+        private _residuePairIndex: Map<string, StructConn.Entry[]> | undefined = void 0;
+        private _atomIndex: Map<number, StructConn.Entry[]> | undefined = void 0;
+
+        private getResiduePairIndex() {
+            if (this._residuePairIndex) return this._residuePairIndex;
+            this._residuePairIndex = new Map();
+            for (const e of this.entries) {
+                const ps = e.partners;
+                const l = ps.length;
+                for (let i = 0; i < l - 1; i++) {
+                    for (let j = i + i; j < l; j++) {
+                        const key = _resKey(ps[i].residueIndex, ps[j].residueIndex);
+                        if (this._residuePairIndex.has(key)) {
+                            this._residuePairIndex.get(key)!.push(e);
+                        } else {
+                            this._residuePairIndex.set(key, [e]);
+                        }
                     }
                 }
             }
+            return this._residuePairIndex;
         }
-        return this._residuePairIndex;
-    }
 
-    private getAtomIndex() {
-        if (this._atomIndex) return this._atomIndex;
-        this._atomIndex = new Map();
-        for (const e of this.entries) {
-            for (const p of e.partners) {
-                const key = p.atomIndex;
-                if (this._atomIndex.has(key)) {
-                    this._atomIndex.get(key)!.push(e);
-                } else {
-                    this._atomIndex.set(key, [e]);
+        private getAtomIndex() {
+            if (this._atomIndex) return this._atomIndex;
+            this._atomIndex = new Map();
+            for (const e of this.entries) {
+                for (const p of e.partners) {
+                    const key = p.atomIndex;
+                    if (this._atomIndex.has(key)) {
+                        this._atomIndex.get(key)!.push(e);
+                    } else {
+                        this._atomIndex.set(key, [e]);
+                    }
                 }
             }
+            return this._atomIndex;
         }
-        return this._atomIndex;
-    }
 
-    private static _emptyEntry = [];
 
-    getResidueEntries(residueAIndex: number, residueBIndex: number): ReadonlyArray<StructConn.Entry> {
-        return this.getResiduePairIndex().get(StructConn._resKey(residueAIndex, residueBIndex)) || StructConn._emptyEntry;
-    }
+        getResidueEntries(residueAIndex: number, residueBIndex: number): ReadonlyArray<StructConn.Entry> {
+            return this.getResiduePairIndex().get(_resKey(residueAIndex, residueBIndex)) || _emptyEntry;
+        }
 
-    getAtomEntries(atomIndex: number): ReadonlyArray<StructConn.Entry> {
-        return this.getAtomIndex().get(atomIndex) || StructConn._emptyEntry;
-    }
+        getAtomEntries(atomIndex: number): ReadonlyArray<StructConn.Entry> {
+            return this.getAtomIndex().get(atomIndex) || _emptyEntry;
+        }
 
-    constructor(public entries: StructConn.Entry[]) {
+        constructor(public entries: StructConn.Entry[]) {
+        }
     }
-}
 
-export namespace StructConn {
-    export interface Entry extends IntraUnitBonds.StructConnEntry {
+    export interface Entry {
         distance: number,
         order: number,
         flags: number,
@@ -90,8 +98,11 @@ export namespace StructConn {
         | 'modres'
         | 'saltbr'
 
-    export function create(model: Model): StructConn | undefined {
-        if (model.sourceData.kind !== 'mmCIF') return
+    export const PropName = '__StructConn__';
+    export function fromModel(model: Model): StructConn | undefined {
+        if (model.properties[PropName]) return model.properties[PropName];
+
+        if (model.sourceData.kind !== 'mmCIF') return;
         const { struct_conn } = model.sourceData.data;
         if (!struct_conn._rowCount) return void 0;
 
@@ -150,7 +161,7 @@ export namespace StructConn {
 
             const type = conn_type_id.value(i)! as StructConnType;
             const orderType = (pdbx_value_order.value(i) || '').toLowerCase();
-            let flags = BondType.Flag.None;
+            let flags = LinkType.Flag.None;
             let order = 1;
 
             switch (orderType) {
@@ -166,33 +177,35 @@ export namespace StructConn {
                 case 'covale_phosphate':
                 case 'covale_sugar':
                 case 'modres':
-                    flags = BondType.Flag.Covalent;
+                    flags = LinkType.Flag.Covalent;
                     break;
-                case 'disulf': flags = BondType.Flag.Covalent | BondType.Flag.Sulfide; break;
-                case 'hydrog': flags = BondType.Flag.Hydrogen; break;
-                case 'metalc': flags = BondType.Flag.MetallicCoordination; break;
-                case 'saltbr': flags = BondType.Flag.Ion; break;
+                case 'disulf': flags = LinkType.Flag.Covalent | LinkType.Flag.Sulfide; break;
+                case 'hydrog': flags = LinkType.Flag.Hydrogen; break;
+                case 'metalc': flags = LinkType.Flag.MetallicCoordination; break;
+                case 'saltbr': flags = LinkType.Flag.Ion; break;
             }
 
             entries.push({ flags, order, distance: pdbx_dist_value.value(i), partners });
         }
 
-        return new StructConn(entries);
+        const ret = new StructConnImpl(entries);
+        model.properties[PropName] = ret;
+        return ret;
     }
 }
 
-export class ComponentBondInfo implements IntraUnitBonds.ComponentBondInfo {
-    entries: Map<string, ComponentBondInfo.Entry> = new Map();
+export namespace ComponentBond {
+    export class ComponentBondImpl implements ComponentBond {
+        entries: Map<string, ComponentBond.Entry> = new Map();
 
-    newEntry(id: string) {
-        let e = new ComponentBondInfo.Entry(id);
-        this.entries.set(id, e);
-        return e;
+        addEntry(id: string) {
+            let e = new Entry(id);
+            this.entries.set(id, e);
+            return e;
+        }
     }
-}
 
-export namespace ComponentBondInfo {
-    export class Entry implements IntraUnitBonds.ComponentBondInfoEntry {
+    export class Entry implements Entry {
         map: Map<string, Map<string, { order: number, flags: number }>> = new Map();
 
         add(a: string, b: string, order: number, flags: number, swap = true) {
@@ -215,16 +228,19 @@ export namespace ComponentBondInfo {
         }
     }
 
-    export function create(model: Model): ComponentBondInfo | undefined {
+    export const PropName = '__ComponentBond__';
+    export function fromModel(model: Model): ComponentBond | undefined {
+        if (model.properties[PropName]) return model.properties[PropName];
+
         if (model.sourceData.kind !== 'mmCIF') return
         const { chem_comp_bond } = model.sourceData.data;
         if (!chem_comp_bond._rowCount) return void 0;
 
-        let info = new ComponentBondInfo();
+        let compBond = new ComponentBondImpl();
 
         const { comp_id, atom_id_1, atom_id_2, value_order, pdbx_aromatic_flag, _rowCount: rowCount } = chem_comp_bond;
 
-        let entry = info.newEntry(comp_id.value(0)!);
+        let entry = compBond.addEntry(comp_id.value(0)!);
 
         for (let i = 0; i < rowCount; i++) {
 
@@ -235,12 +251,12 @@ export namespace ComponentBondInfo {
             const aromatic = pdbx_aromatic_flag.value(i) === 'Y';
 
             if (entry.id !== id) {
-                entry = info.newEntry(id);
+                entry = compBond.addEntry(id);
             }
 
-            let flags: number = BondType.Flag.Covalent;
+            let flags: number = LinkType.Flag.Covalent;
             let ord = 1;
-            if (aromatic) flags |= BondType.Flag.Aromatic;
+            if (aromatic) flags |= LinkType.Flag.Aromatic;
             switch (order.toLowerCase()) {
                 case 'doub':
                 case 'delo':
@@ -253,6 +269,7 @@ export namespace ComponentBondInfo {
             entry.add(nameA, nameB, ord, flags);
         }
 
-        return info;
+        model.properties[PropName] = compBond;
+        return compBond;
     }
 }
\ No newline at end of file
diff --git a/src/mol-model/structure/model/formats/mmcif/sequence.ts b/src/mol-model/structure/model/formats/mmcif/sequence.ts
index 06f0c888d9af880650668dfe69776c0308aae9ce..a279b6122979af54ae375a8be6fd394f400b49c6 100644
--- a/src/mol-model/structure/model/formats/mmcif/sequence.ts
+++ b/src/mol-model/structure/model/formats/mmcif/sequence.ts
@@ -21,12 +21,13 @@ import { Sequence } from '../../../../sequence';
 // corresponding ATOM_SITE entries should reflect this
 // heterogeneity.
 
-export function getSequence(cif: mmCIF, entities: Entities, hierarchy: AtomicHierarchy): StructureSequence {
-    if (!cif.entity_poly_seq._rowCount) return StructureSequence.fromAtomicHierarchy(entities, hierarchy);
+export function getSequence(cif: mmCIF, entities: Entities, hierarchy: AtomicHierarchy, modResMap: Map<string, string>): StructureSequence {
+    if (!cif.entity_poly_seq._rowCount) return StructureSequence.fromAtomicHierarchy(entities, hierarchy, modResMap);
 
     const { entity_id, num, mon_id } = cif.entity_poly_seq;
 
     const byEntityKey: StructureSequence['byEntityKey'] = {};
+    const sequences: StructureSequence.Entity[] = [];
     const count = entity_id.rowCount;
 
     let i = 0;
@@ -38,14 +39,17 @@ export function getSequence(cif: mmCIF, entities: Entities, hierarchy: AtomicHie
         const id = entity_id.value(start);
         const _compId = Column.window(mon_id, start, i);
         const _num = Column.window(num, start, i);
+        const entityKey = entities.getEntityIndex(id);
 
-        byEntityKey[entities.getEntityIndex(id)] = {
+        byEntityKey[entityKey] = {
             entityId: id,
             compId: _compId,
             num: _num,
-            sequence: Sequence.ofResidueNames(_compId, _num)
+            sequence: Sequence.ofResidueNames(_compId, _num, modResMap)
         };
+
+        sequences.push(byEntityKey[entityKey]);
     }
 
-    return { byEntityKey };
+    return { byEntityKey, sequences };
 }
\ No newline at end of file
diff --git a/src/mol-model/structure/model/model.ts b/src/mol-model/structure/model/model.ts
index da0037d65dff36c294992e38a77c7d5d0d109b53..799bb664363c9dd7d90d11946a0ba0023e49452f 100644
--- a/src/mol-model/structure/model/model.ts
+++ b/src/mol-model/structure/model/model.ts
@@ -15,7 +15,6 @@ import { SecondaryStructure } from './properties/seconday-structure';
 
 //import from_gro from './formats/gro'
 import from_mmCIF from './formats/mmcif'
-
 /**
  * Interface to the "source data" of the molecule.
  *
@@ -36,7 +35,13 @@ interface Model extends Readonly<{
     atomicHierarchy: AtomicHierarchy,
     atomicConformation: AtomicConformation,
 
-    properties: { secondaryStructure: SecondaryStructure },
+    /** Various parts of the code can "cache" custom properties here */
+    properties: {
+        readonly secondaryStructure: SecondaryStructure,
+        // maps modified residue name to its parent
+        readonly modifiedResidueNameMap: Map<string, string>,
+        [customName: string]: any
+    },
 
     coarseHierarchy: CoarseHierarchy,
     coarseConformation: CoarseConformation
diff --git a/src/mol-model/structure/model/properties/atomic/hierarchy.ts b/src/mol-model/structure/model/properties/atomic/hierarchy.ts
index fe50e990b242ece24021f6f8eee514388e95d6da..4327d22d406451be93e6d9116bb58526cf4fb9d2 100644
--- a/src/mol-model/structure/model/properties/atomic/hierarchy.ts
+++ b/src/mol-model/structure/model/properties/atomic/hierarchy.ts
@@ -65,7 +65,9 @@ export interface AtomicKeys {
     // also index to the Entities table.
     entityKey: ArrayLike<number>,
 
-    findChainKey(entityId: string, label_asym_id: string): number
+    findChainKey(entityId: string, label_asym_id: string): number,
+
+    /** Unique number for each of the residue. Also the index of the 1st occurence of this residue. */
     findResidueKey(entityId: string, label_asym_id: string, label_comp_id: string, auth_seq_id: number, pdbx_PDB_ins_code: string): number
 }
 
diff --git a/src/mol-model/structure/model/properties/sequence.ts b/src/mol-model/structure/model/properties/sequence.ts
index a2f53c56df7c790bbbacadc67e1d28a4f983a6ac..7725eb561352a4c044b1cd410160e545f31aa8e8 100644
--- a/src/mol-model/structure/model/properties/sequence.ts
+++ b/src/mol-model/structure/model/properties/sequence.ts
@@ -10,6 +10,7 @@ import { Entities } from './common';
 import { Sequence } from '../../../sequence';
 
 interface StructureSequence {
+    readonly sequences: ReadonlyArray<StructureSequence.Entity>,
     readonly byEntityKey: { [key: number]: StructureSequence.Entity }
 }
 
@@ -22,11 +23,12 @@ namespace StructureSequence {
         readonly sequence: Sequence
     }
 
-    export function fromAtomicHierarchy(entities: Entities, hierarchy: AtomicHierarchy): StructureSequence {
+    export function fromAtomicHierarchy(entities: Entities, hierarchy: AtomicHierarchy, modResMap?: Map<string, string>): StructureSequence {
         const { label_comp_id, label_seq_id } = hierarchy.residues
         const { chainSegments, residueSegments } = hierarchy
 
         const byEntityKey: StructureSequence['byEntityKey'] = { };
+        const sequences: StructureSequence.Entity[] = [];
 
         for (let cI = 0, _cI = hierarchy.chains._rowCount; cI < _cI; cI++) {
             const entityKey = hierarchy.entityKey[cI];
@@ -50,11 +52,13 @@ namespace StructureSequence {
                 entityId: entities.data.id.value(entityKey),
                 compId,
                 num,
-                sequence: Sequence.ofResidueNames(compId, num)
+                sequence: Sequence.ofResidueNames(compId, num, modResMap)
             };
+
+            sequences.push(byEntityKey[entityKey]);
         }
 
-        return { byEntityKey }
+        return { byEntityKey, sequences };
     }
 }
 
diff --git a/src/mol-model/structure/model/types.ts b/src/mol-model/structure/model/types.ts
index 36d0cb935f3631e5fd0057526443f753472090f6..16c748b99b1d3d38f4a88614837ae8658acaed94 100644
--- a/src/mol-model/structure/model/types.ts
+++ b/src/mol-model/structure/model/types.ts
@@ -340,9 +340,9 @@ export const VdwRadii = {
 }
 export const DefaultVdwRadius = 2.0
 
-export interface BondType extends BitFlags<BondType.Flag> { }
-export namespace BondType {
-    export const is: (b: BondType, f: Flag) => boolean = BitFlags.has
+export interface LinkType extends BitFlags<LinkType.Flag> { }
+export namespace LinkType {
+    export const is: (b: LinkType, f: Flag) => boolean = BitFlags.has
     export const enum Flag {
         None                 = 0x0,
         Covalent             = 0x1,
@@ -354,4 +354,8 @@ export namespace BondType {
         Computed             = 0x40
         // currently at most 16 flags are supported!!
     }
+
+    export function isCovalent(flags: LinkType.Flag) {
+        return (flags & LinkType.Flag.Covalent) !== 0;
+    }
 }
\ No newline at end of file
diff --git a/src/mol-model/structure/query/selection.ts b/src/mol-model/structure/query/selection.ts
index a1fc34f2a0e6f741a667aa97c2af2fcd7ec7a9c2..0aaa01922dc0f82ca9626e9ba9b0d258125ec742 100644
--- a/src/mol-model/structure/query/selection.ts
+++ b/src/mol-model/structure/query/selection.ts
@@ -5,8 +5,9 @@
  */
 
 import { HashSet } from 'mol-data/generic'
-import { Structure } from '../structure'
+import { Structure, Element, Unit } from '../structure'
 import { structureUnion } from './utils/structure';
+import { SortedArray } from 'mol-data/int';
 
 // A selection is a pair of a Structure and a sequence of unique AtomSets
 type Selection = Selection.Singletons | Selection.Sequence
@@ -34,6 +35,16 @@ namespace Selection {
         return structureUnion(sel.source, sel.structures);
     }
 
+    export function toLoci(sel: Selection): Element.Loci {
+        const loci: { unit: Unit, indices: SortedArray }[] = [];
+
+        for (const unit of unionStructure(sel).units) {
+            loci[loci.length] = { unit, indices: SortedArray.indicesOf(sel.source.unitMap.get(unit.id).elements, unit.elements) }
+        }
+
+        return Element.Loci(loci);
+    }
+
     export interface Builder {
         add(structure: Structure): void,
         getSelection(): Selection
diff --git a/src/mol-model/structure/structure.ts b/src/mol-model/structure/structure.ts
index 44a299b2e5c3a9c41148e6298e8fc5488fb8c04d..bed8ae1fd045532d92ac1922e95cfdcd5b5afc04 100644
--- a/src/mol-model/structure/structure.ts
+++ b/src/mol-model/structure/structure.ts
@@ -8,6 +8,6 @@ import Element from './structure/element'
 import Structure from './structure/structure'
 import Unit from './structure/unit'
 import StructureSymmetry from './structure/symmetry'
-import { Bond } from './structure/unit/bonds'
+import { Link } from './structure/unit/links'
 
-export { Element, Bond, Structure, Unit, StructureSymmetry }
\ No newline at end of file
+export { Element, Link, Structure, Unit, StructureSymmetry }
\ No newline at end of file
diff --git a/src/mol-model/structure/structure/structure.ts b/src/mol-model/structure/structure/structure.ts
index cac54e0b559c6f488ad7541915907a1e5a57f061..22666fb137319710faae97b12058c3861003ecaf 100644
--- a/src/mol-model/structure/structure/structure.ts
+++ b/src/mol-model/structure/structure/structure.ts
@@ -15,6 +15,7 @@ import { StructureLookup3D } from './util/lookup3d';
 import { CoarseElements } from '../model/properties/coarse';
 import { StructureSubsetBuilder } from './util/subset-builder';
 import { Queries } from '../query';
+import { InterUnitBonds, computeInterUnitBonds } from './unit/links';
 
 class Structure {
     readonly unitMap: IntMap<Unit>;
@@ -60,6 +61,13 @@ class Structure {
         return this._lookup3d;
     }
 
+    private _links?: InterUnitBonds = void 0;
+    get links() {
+        if (this._links) return this._links;
+        this._links = computeInterUnitBonds(this);
+        return this._links;
+    }
+
     constructor(units: ArrayLike<Unit>) {
         const map = IntMap.Mutable<Unit>();
         let elementCount = 0;
@@ -90,8 +98,21 @@ namespace Structure {
         const chains = model.atomicHierarchy.chainSegments;
         const builder = new StructureBuilder();
 
+        const { residueSegments: { segmentMap: residueIndex } } = model.atomicHierarchy;
+
         for (let c = 0; c < chains.count; c++) {
-            const elements = SortedArray.ofBounds(chains.segments[c], chains.segments[c + 1]);
+            const start = chains.segments[c];
+            let end = chains.segments[c + 1];
+
+            let rStart = residueIndex[start], rEnd = residueIndex[end - 1];
+            while (rEnd - rStart <= 1 && c + 1 < chains.count) {
+                c++;
+                end = chains.segments[c + 1];
+                rStart = rEnd;
+                rEnd = residueIndex[end - 1];
+            }
+
+            const elements = SortedArray.ofBounds(start, end);
             builder.addUnit(Unit.Kind.Atomic, model, SymmetryOperator.Default, elements);
         }
 
diff --git a/src/mol-model/structure/structure/unit.ts b/src/mol-model/structure/structure/unit.ts
index e5ef8d9080ebf11d6b2b5b6e6f2a76353aee9a61..c5488a09bc76f1393318cc1b9c5620998a8daa8f 100644
--- a/src/mol-model/structure/structure/unit.ts
+++ b/src/mol-model/structure/structure/unit.ts
@@ -9,7 +9,7 @@ import { Model } from '../model'
 import { GridLookup3D, Lookup3D } from 'mol-math/geometry'
 import { SortedArray } from 'mol-data/int';
 import { idFactory } from 'mol-util/id-factory';
-import { IntraUnitBonds, computeIntraUnitBonds } from './unit/bonds'
+import { IntraUnitLinks, computeIntraUnitBonds } from './unit/links'
 import { CoarseElements, CoarseSphereConformation, CoarseGaussianConformation } from '../model/properties/coarse';
 import { ValueRef } from 'mol-util';
 import { UnitRings } from './unit/rings';
@@ -100,10 +100,10 @@ namespace Unit {
             return this.props.lookup3d.ref;
         }
 
-        get bonds() {
-            if (this.props.bonds.ref) return this.props.bonds.ref;
-            this.props.bonds.ref = computeIntraUnitBonds(this);
-            return this.props.bonds.ref;
+        get links() {
+            if (this.props.links.ref) return this.props.links.ref;
+            this.props.links.ref = computeIntraUnitBonds(this);
+            return this.props.links.ref;
         }
 
         get rings() {
@@ -127,12 +127,12 @@ namespace Unit {
 
     interface AtomicProperties {
         lookup3d: ValueRef<Lookup3D | undefined>,
-        bonds: ValueRef<IntraUnitBonds | undefined>,
+        links: ValueRef<IntraUnitLinks | undefined>,
         rings: ValueRef<UnitRings | undefined>
     }
 
-    function AtomicProperties() {
-        return { lookup3d: ValueRef.create(void 0), bonds: ValueRef.create(void 0), rings: ValueRef.create(void 0) };
+    function AtomicProperties(): AtomicProperties {
+        return { lookup3d: ValueRef.create(void 0), links: ValueRef.create(void 0), rings: ValueRef.create(void 0) };
     }
 
     class Coarse<K extends Kind.Gaussians | Kind.Spheres, C extends CoarseSphereConformation | CoarseGaussianConformation> implements Base {
diff --git a/src/mol-model/structure/structure/unit/bonds/intra-compute.ts b/src/mol-model/structure/structure/unit/bonds/intra-compute.ts
deleted file mode 100644
index ba344073c94604ba612a5dfafe4bd100770e30bb..0000000000000000000000000000000000000000
--- a/src/mol-model/structure/structure/unit/bonds/intra-compute.ts
+++ /dev/null
@@ -1,214 +0,0 @@
-/**
- * Copyright (c) 2017 Mol* contributors, licensed under MIT, See LICENSE file for more info.
- *
- * @author David Sehnal <david.sehnal@gmail.com>
- */
-
-import { BondType, ElementSymbol } from '../../../model/types'
-import { IntraUnitBonds } from './intra-data'
-import { StructConn, ComponentBondInfo } from '../../../model/formats/mmcif/bonds'
-import Unit from '../../unit'
-import { IntGraph } from 'mol-math/graph';
-
-export interface BondComputationParameters {
-    maxHbondLength: number,
-    forceCompute: boolean
-}
-
-// H,D,T are all mapped to H
-const __ElementIndex: { [e: string]: number | undefined } = { 'H': 0, 'h': 0, 'D': 0, 'd': 0, 'T': 0, 't': 0, 'He': 2, 'HE': 2, 'he': 2, 'Li': 3, 'LI': 3, 'li': 3, 'Be': 4, 'BE': 4, 'be': 4, 'B': 5, 'b': 5, 'C': 6, 'c': 6, 'N': 7, 'n': 7, 'O': 8, 'o': 8, 'F': 9, 'f': 9, 'Ne': 10, 'NE': 10, 'ne': 10, 'Na': 11, 'NA': 11, 'na': 11, 'Mg': 12, 'MG': 12, 'mg': 12, 'Al': 13, 'AL': 13, 'al': 13, 'Si': 14, 'SI': 14, 'si': 14, 'P': 15, 'p': 15, 'S': 16, 's': 16, 'Cl': 17, 'CL': 17, 'cl': 17, 'Ar': 18, 'AR': 18, 'ar': 18, 'K': 19, 'k': 19, 'Ca': 20, 'CA': 20, 'ca': 20, 'Sc': 21, 'SC': 21, 'sc': 21, 'Ti': 22, 'TI': 22, 'ti': 22, 'V': 23, 'v': 23, 'Cr': 24, 'CR': 24, 'cr': 24, 'Mn': 25, 'MN': 25, 'mn': 25, 'Fe': 26, 'FE': 26, 'fe': 26, 'Co': 27, 'CO': 27, 'co': 27, 'Ni': 28, 'NI': 28, 'ni': 28, 'Cu': 29, 'CU': 29, 'cu': 29, 'Zn': 30, 'ZN': 30, 'zn': 30, 'Ga': 31, 'GA': 31, 'ga': 31, 'Ge': 32, 'GE': 32, 'ge': 32, 'As': 33, 'AS': 33, 'as': 33, 'Se': 34, 'SE': 34, 'se': 34, 'Br': 35, 'BR': 35, 'br': 35, 'Kr': 36, 'KR': 36, 'kr': 36, 'Rb': 37, 'RB': 37, 'rb': 37, 'Sr': 38, 'SR': 38, 'sr': 38, 'Y': 39, 'y': 39, 'Zr': 40, 'ZR': 40, 'zr': 40, 'Nb': 41, 'NB': 41, 'nb': 41, 'Mo': 42, 'MO': 42, 'mo': 42, 'Tc': 43, 'TC': 43, 'tc': 43, 'Ru': 44, 'RU': 44, 'ru': 44, 'Rh': 45, 'RH': 45, 'rh': 45, 'Pd': 46, 'PD': 46, 'pd': 46, 'Ag': 47, 'AG': 47, 'ag': 47, 'Cd': 48, 'CD': 48, 'cd': 48, 'In': 49, 'IN': 49, 'in': 49, 'Sn': 50, 'SN': 50, 'sn': 50, 'Sb': 51, 'SB': 51, 'sb': 51, 'Te': 52, 'TE': 52, 'te': 52, 'I': 53, 'i': 53, 'Xe': 54, 'XE': 54, 'xe': 54, 'Cs': 55, 'CS': 55, 'cs': 55, 'Ba': 56, 'BA': 56, 'ba': 56, 'La': 57, 'LA': 57, 'la': 57, 'Ce': 58, 'CE': 58, 'ce': 58, 'Pr': 59, 'PR': 59, 'pr': 59, 'Nd': 60, 'ND': 60, 'nd': 60, 'Pm': 61, 'PM': 61, 'pm': 61, 'Sm': 62, 'SM': 62, 'sm': 62, 'Eu': 63, 'EU': 63, 'eu': 63, 'Gd': 64, 'GD': 64, 'gd': 64, 'Tb': 65, 'TB': 65, 'tb': 65, 'Dy': 66, 'DY': 66, 'dy': 66, 'Ho': 67, 'HO': 67, 'ho': 67, 'Er': 68, 'ER': 68, 'er': 68, 'Tm': 69, 'TM': 69, 'tm': 69, 'Yb': 70, 'YB': 70, 'yb': 70, 'Lu': 71, 'LU': 71, 'lu': 71, 'Hf': 72, 'HF': 72, 'hf': 72, 'Ta': 73, 'TA': 73, 'ta': 73, 'W': 74, 'w': 74, 'Re': 75, 'RE': 75, 're': 75, 'Os': 76, 'OS': 76, 'os': 76, 'Ir': 77, 'IR': 77, 'ir': 77, 'Pt': 78, 'PT': 78, 'pt': 78, 'Au': 79, 'AU': 79, 'au': 79, 'Hg': 80, 'HG': 80, 'hg': 80, 'Tl': 81, 'TL': 81, 'tl': 81, 'Pb': 82, 'PB': 82, 'pb': 82, 'Bi': 83, 'BI': 83, 'bi': 83, 'Po': 84, 'PO': 84, 'po': 84, 'At': 85, 'AT': 85, 'at': 85, 'Rn': 86, 'RN': 86, 'rn': 86, 'Fr': 87, 'FR': 87, 'fr': 87, 'Ra': 88, 'RA': 88, 'ra': 88, 'Ac': 89, 'AC': 89, 'ac': 89, 'Th': 90, 'TH': 90, 'th': 90, 'Pa': 91, 'PA': 91, 'pa': 91, 'U': 92, 'u': 92, 'Np': 93, 'NP': 93, 'np': 93, 'Pu': 94, 'PU': 94, 'pu': 94, 'Am': 95, 'AM': 95, 'am': 95, 'Cm': 96, 'CM': 96, 'cm': 96, 'Bk': 97, 'BK': 97, 'bk': 97, 'Cf': 98, 'CF': 98, 'cf': 98, 'Es': 99, 'ES': 99, 'es': 99, 'Fm': 100, 'FM': 100, 'fm': 100, 'Md': 101, 'MD': 101, 'md': 101, 'No': 102, 'NO': 102, 'no': 102, 'Lr': 103, 'LR': 103, 'lr': 103, 'Rf': 104, 'RF': 104, 'rf': 104, 'Db': 105, 'DB': 105, 'db': 105, 'Sg': 106, 'SG': 106, 'sg': 106, 'Bh': 107, 'BH': 107, 'bh': 107, 'Hs': 108, 'HS': 108, 'hs': 108, 'Mt': 109, 'MT': 109, 'mt': 109 };
-
-const __ElementBondThresholds: { [e: number]: number | undefined } = { 0: 1.42, 1: 1.42, 3: 2.7, 4: 2.7, 6: 1.75, 7: 1.6, 8: 1.52, 11: 2.7, 12: 2.7, 13: 2.7, 14: 1.9, 15: 1.9, 16: 1.9, 17: 1.8, 19: 2.7, 20: 2.7, 21: 2.7, 22: 2.7, 23: 2.7, 24: 2.7, 25: 2.7, 26: 2.7, 27: 2.7, 28: 2.7, 29: 2.7, 30: 2.7, 31: 2.7, 33: 2.68, 37: 2.7, 38: 2.7, 39: 2.7, 40: 2.7, 41: 2.7, 42: 2.7, 43: 2.7, 44: 2.7, 45: 2.7, 46: 2.7, 47: 2.7, 48: 2.7, 49: 2.7, 50: 2.7, 55: 2.7, 56: 2.7, 57: 2.7, 58: 2.7, 59: 2.7, 60: 2.7, 61: 2.7, 62: 2.7, 63: 2.7, 64: 2.7, 65: 2.7, 66: 2.7, 67: 2.7, 68: 2.7, 69: 2.7, 70: 2.7, 71: 2.7, 72: 2.7, 73: 2.7, 74: 2.7, 75: 2.7, 76: 2.7, 77: 2.7, 78: 2.7, 79: 2.7, 80: 2.7, 81: 2.7, 82: 2.7, 83: 2.7, 87: 2.7, 88: 2.7, 89: 2.7, 90: 2.7, 91: 2.7, 92: 2.7, 93: 2.7, 94: 2.7, 95: 2.7, 96: 2.7, 97: 2.7, 98: 2.7, 99: 2.7, 100: 2.7, 101: 2.7, 102: 2.7, 103: 2.7, 104: 2.7, 105: 2.7, 106: 2.7, 107: 2.7, 108: 2.7, 109: 2.88 };
-
-const __ElementPairThresholds: { [e: number]: number | undefined } = { 0: 0.8, 20: 1.31, 27: 1.3, 35: 1.3, 44: 1.05, 54: 1, 60: 1.84, 72: 1.88, 84: 1.75, 85: 1.56, 86: 1.76, 98: 1.6, 99: 1.68, 100: 1.63, 112: 1.55, 113: 1.59, 114: 1.36, 129: 1.45, 144: 1.6, 170: 1.4, 180: 1.55, 202: 2.4, 222: 2.24, 224: 1.91, 225: 1.98, 243: 2.02, 269: 2, 293: 1.9, 480: 2.3, 512: 2.3, 544: 2.3, 612: 2.1, 629: 1.54, 665: 1, 813: 2.6, 854: 2.27, 894: 1.93, 896: 2.1, 937: 2.05, 938: 2.06, 981: 1.62, 1258: 2.68, 1309: 2.33, 1484: 1, 1763: 2.14, 1823: 2.48, 1882: 2.1, 1944: 1.72, 2380: 2.34, 3367: 2.44, 3733: 2.11, 3819: 2.6, 3821: 2.36, 4736: 2.75, 5724: 2.73, 5959: 2.63, 6519: 2.84, 6750: 2.87, 8991: 2.81 };
-
-const __DefaultBondingRadius = 2.001;
-
-const MetalsSet = (function () {
-    const metals = ['LI', 'NA', 'K', 'RB', 'CS', 'FR', 'BE', 'MG', 'CA', 'SR', 'BA', 'RA', 'AL', 'GA', 'IN', 'SN', 'TL', 'PB', 'BI', 'SC', 'TI', 'V', 'CR', 'MN', 'FE', 'CO', 'NI', 'CU', 'ZN', 'Y', 'ZR', 'NB', 'MO', 'TC', 'RU', 'RH', 'PD', 'AG', 'CD', 'LA', 'HF', 'TA', 'W', 'RE', 'OS', 'IR', 'PT', 'AU', 'HG', 'AC', 'RF', 'DB', 'SG', 'BH', 'HS', 'MT', 'CE', 'PR', 'ND', 'PM', 'SM', 'EU', 'GD', 'TB', 'DY', 'HO', 'ER', 'TM', 'YB', 'LU', 'TH', 'PA', 'U', 'NP', 'PU', 'AM', 'CM', 'BK', 'CF', 'ES', 'FM', 'MD', 'NO', 'LR'];
-    const set = new Set<number>();
-    for (const m of metals) {
-        set.add(__ElementIndex[m]!);
-    }
-    return set;
-})();
-
-function pair(a: number, b: number) {
-    if (a < b) return (a + b) * (a + b + 1) / 2 + b;
-    else return (a + b) * (a + b + 1) / 2 + a;
-}
-
-function idx(e: ElementSymbol) {
-    const i = __ElementIndex[e as any as string];
-    if (i === void 0) return -1;
-    return i;
-}
-
-function pairThreshold(i: number, j: number) {
-    if (i < 0 || j < 0) return -1;
-    const r = __ElementPairThresholds[pair(i, j)];
-    if (r === void 0) return -1;
-    return r;
-}
-
-function threshold(i: number) {
-    if (i < 0) return __DefaultBondingRadius;
-    const r = __ElementBondThresholds[i];
-    if (r === void 0) return __DefaultBondingRadius;
-    return r;
-}
-
-const H_ID = __ElementIndex['H']!;
-function isHydrogen(i: number) {
-    return i === H_ID;
-}
-
-function getGraph(atomA: number[], atomB: number[], _order: number[], _flags: number[], atomCount: number): IntraUnitBonds {
-    const builder = new IntGraph.EdgeBuilder(atomCount, atomA, atomB);
-    const flags = new Uint16Array(builder.slotCount);
-    const order = new Int8Array(builder.slotCount);
-    for (let i = 0, _i = builder.edgeCount; i < _i; i++) {
-        builder.addNextEdge();
-        builder.assignProperty(flags, _flags[i]);
-        builder.assignProperty(order, _order[i]);
-    }
-
-    return builder.createGraph({ flags, order });
-}
-
-function _computeBonds(unit: Unit.Atomic, params: BondComputationParameters): IntraUnitBonds {
-    const MAX_RADIUS = 3;
-
-    const { x, y, z } = unit.model.atomicConformation;
-    const atomCount = unit.elements.length;
-    const { elements: atoms, residueIndex } = unit;
-    const { type_symbol, label_atom_id, label_alt_id } = unit.model.atomicHierarchy.atoms;
-    const { label_comp_id } = unit.model.atomicHierarchy.residues;
-    const query3d = unit.lookup3d;
-
-    const structConn = unit.model.sourceData.kind === 'mmCIF' ? StructConn.create(unit.model) : void 0
-    const component = unit.model.sourceData.kind === 'mmCIF' ? ComponentBondInfo.create(unit.model) : void 0
-
-    const atomA: number[] = [];
-    const atomB: number[] = [];
-    const flags: number[] = [];
-    const order: number[] = [];
-
-    let lastResidue = -1;
-    let componentMap: Map<string, Map<string, { flags: number, order: number }>> | undefined = void 0;
-
-    for (let _aI = 0; _aI < atomCount; _aI++) {
-        const aI =  atoms[_aI];
-        const raI = residueIndex[aI];
-
-        if (!params.forceCompute && raI !== lastResidue) {
-            const resn = label_comp_id.value(raI)!;
-            if (!!component && component.entries.has(resn)) {
-                componentMap = component.entries.get(resn)!.map;
-            } else {
-                componentMap = void 0;
-            }
-        }
-        lastResidue = raI;
-
-        const componentPairs = componentMap ? componentMap.get(label_atom_id.value(aI)) : void 0;
-
-        const aeI = idx(type_symbol.value(aI)!);
-
-        const { indices, count, squaredDistances } = query3d.find(x[aI], y[aI], z[aI], MAX_RADIUS);
-        const isHa = isHydrogen(aeI);
-        const thresholdA = threshold(aeI);
-        const altA = label_alt_id.value(aI);
-        const metalA = MetalsSet.has(aeI);
-        const structConnEntries = params.forceCompute ? void 0 : structConn && structConn.getAtomEntries(aI);
-
-        for (let ni = 0; ni < count; ni++) {
-            const _bI = indices[ni];
-            const bI = atoms[_bI];
-            if (bI <= aI) continue;
-
-            const altB = label_alt_id.value(bI);
-            if (altA && altB && altA !== altB) continue;
-
-            const beI = idx(type_symbol.value(bI)!);
-            const isMetal = metalA || MetalsSet.has(beI);
-
-            const rbI = residueIndex[bI];
-            // handle "component dictionary" bonds.
-            if (raI === rbI && componentPairs) {
-                const e = componentPairs.get(label_atom_id.value(bI)!);
-                if (e) {
-                    atomA[atomA.length] = _aI;
-                    atomB[atomB.length] = _bI;
-                    order[order.length] = e.order;
-                    let flag = e.flags;
-                    if (isMetal) {
-                        if (flag | BondType.Flag.Covalent) flag ^= BondType.Flag.Covalent;
-                        flag |= BondType.Flag.MetallicCoordination;
-                    }
-                    flags[flags.length] = flag;
-                }
-                continue;
-            }
-
-            const isHb = isHydrogen(beI);
-            if (isHa && isHb) continue;
-
-            const dist = Math.sqrt(squaredDistances[ni]);
-            if (dist === 0) continue;
-
-            // handle "struct conn" bonds.
-            if (structConnEntries && structConnEntries.length) {
-                let added = false;
-                for (const se of structConnEntries) {
-                    for (const p of se.partners) {
-                        if (p.atomIndex === bI) {
-                            atomA[atomA.length] = _aI;
-                            atomB[atomB.length] = _bI;
-                            flags[flags.length] = se.flags;
-                            order[order.length] = se.order;
-                            added = true;
-                            break;
-                        }
-                    }
-                    if (added) break;
-                }
-                if (added) continue;
-            }
-
-            if (isHa || isHb) {
-                if (dist < params.maxHbondLength) {
-                    atomA[atomA.length] = _aI;
-                    atomB[atomB.length] = _bI;
-                    order[order.length] = 1;
-                    flags[flags.length] = BondType.Flag.Covalent | BondType.Flag.Computed; // TODO: check if correct
-                }
-                continue;
-            }
-
-            const thresholdAB = pairThreshold(aeI, beI);
-            const pairingThreshold = thresholdAB > 0
-                ? thresholdAB
-                : beI < 0 ? thresholdA : Math.max(thresholdA, threshold(beI));
-
-
-            if (dist <= pairingThreshold) {
-                atomA[atomA.length] = _aI;
-                atomB[atomB.length] = _bI;
-                order[order.length] = 1;
-                flags[flags.length] = (isMetal ? BondType.Flag.MetallicCoordination : BondType.Flag.Covalent) | BondType.Flag.Computed;
-            }
-        }
-    }
-
-    return getGraph(atomA, atomB, order, flags, atomCount);
-}
-
-function computeIntraUnitBonds(unit: Unit.Atomic, params?: Partial<BondComputationParameters>) {
-    return _computeBonds(unit, {
-        maxHbondLength: (params && params.maxHbondLength) || 1.15,
-        forceCompute: !!(params && params.forceCompute),
-    });
-}
-
-export { computeIntraUnitBonds }
\ No newline at end of file
diff --git a/src/mol-model/structure/structure/unit/bonds/intra-data.ts b/src/mol-model/structure/structure/unit/bonds/intra-data.ts
deleted file mode 100644
index 9914145379663e38ce3882ef4634bd57ae3da57e..0000000000000000000000000000000000000000
--- a/src/mol-model/structure/structure/unit/bonds/intra-data.ts
+++ /dev/null
@@ -1,38 +0,0 @@
-/**
- * Copyright (c) 2017-2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
- *
- * @author David Sehnal <david.sehnal@gmail.com>
- * @author Alexander Rose <alexander.rose@weirdbyte.de>
- */
-
-import { BondType } from '../../../model/types'
-import { IntGraph } from 'mol-math/graph';
-
-type IntraUnitBonds = IntGraph<{ readonly order: ArrayLike<number>, readonly flags: ArrayLike<BondType.Flag> }>
-
-namespace IntraUnitBonds {
-    export function createEmpty(): IntraUnitBonds {
-        return IntGraph.create([], [], [], 0, { flags: [], order: [] });
-    }
-    export function isCovalent(flags: number) {
-        return (flags & BondType.Flag.Covalent) !== 0;
-    }
-    export interface StructConnEntry {
-        flags: BondType.Flag,
-        order: number,
-        distance: number,
-        partners: { residueIndex: number, atomIndex: number, symmetry: string }[]
-    }
-    export interface StructConn {
-        getResidueEntries(residueAIndex: number, residueBIndex: number): ReadonlyArray<StructConnEntry>
-        getAtomEntries(atomIndex: number): ReadonlyArray<StructConnEntry>
-    }
-    export interface ComponentBondInfoEntry {
-        map: Map<string, Map<string, { order: number, flags: number }>>
-    }
-    export interface ComponentBondInfo {
-        entries: Map<string, ComponentBondInfoEntry>
-    }
-}
-
-export { IntraUnitBonds }
\ No newline at end of file
diff --git a/src/mol-model/structure/structure/unit/bonds.ts b/src/mol-model/structure/structure/unit/links.ts
similarity index 58%
rename from src/mol-model/structure/structure/unit/bonds.ts
rename to src/mol-model/structure/structure/unit/links.ts
index d7268742832c8d7ff04d19199e218655ff48a099..20345c1bfd56e36caa39d01afe9d72ec38bc0aec 100644
--- a/src/mol-model/structure/structure/unit/bonds.ts
+++ b/src/mol-model/structure/structure/unit/links.ts
@@ -6,10 +6,11 @@
 
 import { Unit } from '../../structure'
 
-export * from './bonds/intra-data'
-export * from './bonds/intra-compute'
+export * from './links/data'
+export * from './links/intra-compute'
+export * from './links/inter-compute'
 
-namespace Bond {
+namespace Link {
     export interface Location {
         readonly aUnit: Unit,
         /** Index into aUnit.elements */
@@ -20,17 +21,17 @@ namespace Bond {
     }
 
     export interface Loci {
-        readonly kind: 'bond-loci',
-        readonly bonds: ReadonlyArray<Location>
+        readonly kind: 'link-loci',
+        readonly links: ReadonlyArray<Location>
     }
 
-    export function Loci(bonds: ArrayLike<Location>): Loci {
-        return { kind: 'bond-loci', bonds: bonds as Loci['bonds'] };
+    export function Loci(links: ArrayLike<Location>): Loci {
+        return { kind: 'link-loci', links: links as Loci['links'] };
     }
 
     export function isLoci(x: any): x is Loci {
-        return !!x && x.kind === 'bond-loci';
+        return !!x && x.kind === 'link-loci';
     }
 }
 
-export { Bond }
\ No newline at end of file
+export { Link }
\ No newline at end of file
diff --git a/src/mol-model/structure/structure/unit/links/common.ts b/src/mol-model/structure/structure/unit/links/common.ts
new file mode 100644
index 0000000000000000000000000000000000000000..6457c86b89ae8bbfd20a33894dc118f271076b6d
--- /dev/null
+++ b/src/mol-model/structure/structure/unit/links/common.ts
@@ -0,0 +1,60 @@
+/**
+ * Copyright (c) 2017-2018 Mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author David Sehnal <david.sehnal@gmail.com>
+ */
+
+import { ElementSymbol } from '../../../model/types';
+
+export interface LinkComputationParameters {
+    maxHbondLength: number,
+    forceCompute: boolean
+}
+
+// H,D,T are all mapped to H
+const __ElementIndex: { [e: string]: number | undefined } = { 'H': 0, 'h': 0, 'D': 0, 'd': 0, 'T': 0, 't': 0, 'He': 2, 'HE': 2, 'he': 2, 'Li': 3, 'LI': 3, 'li': 3, 'Be': 4, 'BE': 4, 'be': 4, 'B': 5, 'b': 5, 'C': 6, 'c': 6, 'N': 7, 'n': 7, 'O': 8, 'o': 8, 'F': 9, 'f': 9, 'Ne': 10, 'NE': 10, 'ne': 10, 'Na': 11, 'NA': 11, 'na': 11, 'Mg': 12, 'MG': 12, 'mg': 12, 'Al': 13, 'AL': 13, 'al': 13, 'Si': 14, 'SI': 14, 'si': 14, 'P': 15, 'p': 15, 'S': 16, 's': 16, 'Cl': 17, 'CL': 17, 'cl': 17, 'Ar': 18, 'AR': 18, 'ar': 18, 'K': 19, 'k': 19, 'Ca': 20, 'CA': 20, 'ca': 20, 'Sc': 21, 'SC': 21, 'sc': 21, 'Ti': 22, 'TI': 22, 'ti': 22, 'V': 23, 'v': 23, 'Cr': 24, 'CR': 24, 'cr': 24, 'Mn': 25, 'MN': 25, 'mn': 25, 'Fe': 26, 'FE': 26, 'fe': 26, 'Co': 27, 'CO': 27, 'co': 27, 'Ni': 28, 'NI': 28, 'ni': 28, 'Cu': 29, 'CU': 29, 'cu': 29, 'Zn': 30, 'ZN': 30, 'zn': 30, 'Ga': 31, 'GA': 31, 'ga': 31, 'Ge': 32, 'GE': 32, 'ge': 32, 'As': 33, 'AS': 33, 'as': 33, 'Se': 34, 'SE': 34, 'se': 34, 'Br': 35, 'BR': 35, 'br': 35, 'Kr': 36, 'KR': 36, 'kr': 36, 'Rb': 37, 'RB': 37, 'rb': 37, 'Sr': 38, 'SR': 38, 'sr': 38, 'Y': 39, 'y': 39, 'Zr': 40, 'ZR': 40, 'zr': 40, 'Nb': 41, 'NB': 41, 'nb': 41, 'Mo': 42, 'MO': 42, 'mo': 42, 'Tc': 43, 'TC': 43, 'tc': 43, 'Ru': 44, 'RU': 44, 'ru': 44, 'Rh': 45, 'RH': 45, 'rh': 45, 'Pd': 46, 'PD': 46, 'pd': 46, 'Ag': 47, 'AG': 47, 'ag': 47, 'Cd': 48, 'CD': 48, 'cd': 48, 'In': 49, 'IN': 49, 'in': 49, 'Sn': 50, 'SN': 50, 'sn': 50, 'Sb': 51, 'SB': 51, 'sb': 51, 'Te': 52, 'TE': 52, 'te': 52, 'I': 53, 'i': 53, 'Xe': 54, 'XE': 54, 'xe': 54, 'Cs': 55, 'CS': 55, 'cs': 55, 'Ba': 56, 'BA': 56, 'ba': 56, 'La': 57, 'LA': 57, 'la': 57, 'Ce': 58, 'CE': 58, 'ce': 58, 'Pr': 59, 'PR': 59, 'pr': 59, 'Nd': 60, 'ND': 60, 'nd': 60, 'Pm': 61, 'PM': 61, 'pm': 61, 'Sm': 62, 'SM': 62, 'sm': 62, 'Eu': 63, 'EU': 63, 'eu': 63, 'Gd': 64, 'GD': 64, 'gd': 64, 'Tb': 65, 'TB': 65, 'tb': 65, 'Dy': 66, 'DY': 66, 'dy': 66, 'Ho': 67, 'HO': 67, 'ho': 67, 'Er': 68, 'ER': 68, 'er': 68, 'Tm': 69, 'TM': 69, 'tm': 69, 'Yb': 70, 'YB': 70, 'yb': 70, 'Lu': 71, 'LU': 71, 'lu': 71, 'Hf': 72, 'HF': 72, 'hf': 72, 'Ta': 73, 'TA': 73, 'ta': 73, 'W': 74, 'w': 74, 'Re': 75, 'RE': 75, 're': 75, 'Os': 76, 'OS': 76, 'os': 76, 'Ir': 77, 'IR': 77, 'ir': 77, 'Pt': 78, 'PT': 78, 'pt': 78, 'Au': 79, 'AU': 79, 'au': 79, 'Hg': 80, 'HG': 80, 'hg': 80, 'Tl': 81, 'TL': 81, 'tl': 81, 'Pb': 82, 'PB': 82, 'pb': 82, 'Bi': 83, 'BI': 83, 'bi': 83, 'Po': 84, 'PO': 84, 'po': 84, 'At': 85, 'AT': 85, 'at': 85, 'Rn': 86, 'RN': 86, 'rn': 86, 'Fr': 87, 'FR': 87, 'fr': 87, 'Ra': 88, 'RA': 88, 'ra': 88, 'Ac': 89, 'AC': 89, 'ac': 89, 'Th': 90, 'TH': 90, 'th': 90, 'Pa': 91, 'PA': 91, 'pa': 91, 'U': 92, 'u': 92, 'Np': 93, 'NP': 93, 'np': 93, 'Pu': 94, 'PU': 94, 'pu': 94, 'Am': 95, 'AM': 95, 'am': 95, 'Cm': 96, 'CM': 96, 'cm': 96, 'Bk': 97, 'BK': 97, 'bk': 97, 'Cf': 98, 'CF': 98, 'cf': 98, 'Es': 99, 'ES': 99, 'es': 99, 'Fm': 100, 'FM': 100, 'fm': 100, 'Md': 101, 'MD': 101, 'md': 101, 'No': 102, 'NO': 102, 'no': 102, 'Lr': 103, 'LR': 103, 'lr': 103, 'Rf': 104, 'RF': 104, 'rf': 104, 'Db': 105, 'DB': 105, 'db': 105, 'Sg': 106, 'SG': 106, 'sg': 106, 'Bh': 107, 'BH': 107, 'bh': 107, 'Hs': 108, 'HS': 108, 'hs': 108, 'Mt': 109, 'MT': 109, 'mt': 109 };
+
+const __ElementBondThresholds: { [e: number]: number | undefined } = { 0: 1.42, 1: 1.42, 3: 2.7, 4: 2.7, 6: 1.75, 7: 1.6, 8: 1.52, 11: 2.7, 12: 2.7, 13: 2.7, 14: 1.9, 15: 1.9, 16: 1.9, 17: 1.8, 19: 2.7, 20: 2.7, 21: 2.7, 22: 2.7, 23: 2.7, 24: 2.7, 25: 2.7, 26: 2.7, 27: 2.7, 28: 2.7, 29: 2.7, 30: 2.7, 31: 2.7, 33: 2.68, 37: 2.7, 38: 2.7, 39: 2.7, 40: 2.7, 41: 2.7, 42: 2.7, 43: 2.7, 44: 2.7, 45: 2.7, 46: 2.7, 47: 2.7, 48: 2.7, 49: 2.7, 50: 2.7, 55: 2.7, 56: 2.7, 57: 2.7, 58: 2.7, 59: 2.7, 60: 2.7, 61: 2.7, 62: 2.7, 63: 2.7, 64: 2.7, 65: 2.7, 66: 2.7, 67: 2.7, 68: 2.7, 69: 2.7, 70: 2.7, 71: 2.7, 72: 2.7, 73: 2.7, 74: 2.7, 75: 2.7, 76: 2.7, 77: 2.7, 78: 2.7, 79: 2.7, 80: 2.7, 81: 2.7, 82: 2.7, 83: 2.7, 87: 2.7, 88: 2.7, 89: 2.7, 90: 2.7, 91: 2.7, 92: 2.7, 93: 2.7, 94: 2.7, 95: 2.7, 96: 2.7, 97: 2.7, 98: 2.7, 99: 2.7, 100: 2.7, 101: 2.7, 102: 2.7, 103: 2.7, 104: 2.7, 105: 2.7, 106: 2.7, 107: 2.7, 108: 2.7, 109: 2.88 };
+
+const __ElementPairThresholds: { [e: number]: number | undefined } = { 0: 0.8, 20: 1.31, 27: 1.3, 35: 1.3, 44: 1.05, 54: 1, 60: 1.84, 72: 1.88, 84: 1.75, 85: 1.56, 86: 1.76, 98: 1.6, 99: 1.68, 100: 1.63, 112: 1.55, 113: 1.59, 114: 1.36, 129: 1.45, 144: 1.6, 170: 1.4, 180: 1.55, 202: 2.4, 222: 2.24, 224: 1.91, 225: 1.98, 243: 2.02, 269: 2, 293: 1.9, 480: 2.3, 512: 2.3, 544: 2.3, 612: 2.1, 629: 1.54, 665: 1, 813: 2.6, 854: 2.27, 894: 1.93, 896: 2.1, 937: 2.05, 938: 2.06, 981: 1.62, 1258: 2.68, 1309: 2.33, 1484: 1, 1763: 2.14, 1823: 2.48, 1882: 2.1, 1944: 1.72, 2380: 2.34, 3367: 2.44, 3733: 2.11, 3819: 2.6, 3821: 2.36, 4736: 2.75, 5724: 2.73, 5959: 2.63, 6519: 2.84, 6750: 2.87, 8991: 2.81 };
+
+const __DefaultBondingRadius = 2.001;
+
+export const MetalsSet = (function () {
+    const metals = ['LI', 'NA', 'K', 'RB', 'CS', 'FR', 'BE', 'MG', 'CA', 'SR', 'BA', 'RA', 'AL', 'GA', 'IN', 'SN', 'TL', 'PB', 'BI', 'SC', 'TI', 'V', 'CR', 'MN', 'FE', 'CO', 'NI', 'CU', 'ZN', 'Y', 'ZR', 'NB', 'MO', 'TC', 'RU', 'RH', 'PD', 'AG', 'CD', 'LA', 'HF', 'TA', 'W', 'RE', 'OS', 'IR', 'PT', 'AU', 'HG', 'AC', 'RF', 'DB', 'SG', 'BH', 'HS', 'MT', 'CE', 'PR', 'ND', 'PM', 'SM', 'EU', 'GD', 'TB', 'DY', 'HO', 'ER', 'TM', 'YB', 'LU', 'TH', 'PA', 'U', 'NP', 'PU', 'AM', 'CM', 'BK', 'CF', 'ES', 'FM', 'MD', 'NO', 'LR'];
+    const set = new Set<number>();
+    for (const m of metals) {
+        set.add(__ElementIndex[m]!);
+    }
+    return set;
+})();
+
+function pair(a: number, b: number) {
+    if (a < b) return (a + b) * (a + b + 1) / 2 + b;
+    else return (a + b) * (a + b + 1) / 2 + a;
+}
+
+export function getElementIdx(e: ElementSymbol) {
+    const i = __ElementIndex[e as any as string];
+    if (i === void 0) return -1;
+    return i;
+}
+
+export function getElementPairThreshold(i: number, j: number) {
+    if (i < 0 || j < 0) return -1;
+    const r = __ElementPairThresholds[pair(i, j)];
+    if (r === void 0) return -1;
+    return r;
+}
+
+export function getElementThreshold(i: number) {
+    if (i < 0) return __DefaultBondingRadius;
+    const r = __ElementBondThresholds[i];
+    if (r === void 0) return __DefaultBondingRadius;
+    return r;
+}
+
+const H_ID = __ElementIndex['H']!;
+export function isHydrogen(i: number) {
+    return i === H_ID;
+}
\ No newline at end of file
diff --git a/src/mol-model/structure/structure/unit/links/data.ts b/src/mol-model/structure/structure/unit/links/data.ts
new file mode 100644
index 0000000000000000000000000000000000000000..dfd9762c5a6fdc0578f94f56011bcd8605d7cbc0
--- /dev/null
+++ b/src/mol-model/structure/structure/unit/links/data.ts
@@ -0,0 +1,59 @@
+/**
+ * Copyright (c) 2017-2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author David Sehnal <david.sehnal@gmail.com>
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
+ */
+
+import { LinkType } from '../../../model/types'
+import { IntAdjacencyGraph } from 'mol-math/graph';
+import Unit from '../../unit';
+
+type IntraUnitLinks = IntAdjacencyGraph<{ readonly order: ArrayLike<number>, readonly flags: ArrayLike<LinkType.Flag> }>
+
+namespace IntraUnitLinks {
+    export const Empty: IntraUnitLinks = IntAdjacencyGraph.create([], [], [], 0, { flags: [], order: [] });
+}
+
+class InterUnitBonds {
+    getLinkedUnits(unit: Unit): ReadonlyArray<InterUnitBonds.UnitPairBonds> {
+        if (!this.map.has(unit.id)) return emptyArray;
+        return this.map.get(unit.id)!;
+    }
+
+    constructor(private map: Map<number, InterUnitBonds.UnitPairBonds[]>) {
+    }
+}
+
+namespace InterUnitBonds {
+    export class UnitPairBonds {
+        hasBonds(indexA: number) {
+            return this.linkMap.has(indexA);
+        }
+
+        getBonds(indexA: number): ReadonlyArray<InterUnitBonds.BondInfo> {
+            if (!this.linkMap.has(indexA)) return emptyArray;
+            return this.linkMap.get(indexA)!;
+        }
+
+        get areUnitsOrdered() {
+            return this.unitA.id < this.unitB.id;
+        }
+
+        constructor(public unitA: Unit.Atomic, public unitB: Unit.Atomic,
+            public bondCount: number, public linkedElementIndices: ReadonlyArray<number>,
+            private linkMap: Map<number, BondInfo[]>) {
+        }
+    }
+
+    export interface BondInfo {
+        /** indexInto */
+        readonly indexB: number,
+        readonly order: number,
+        readonly flag: LinkType.Flag
+    }
+}
+
+const emptyArray: any[] = [];
+
+export { IntraUnitLinks, InterUnitBonds }
\ No newline at end of file
diff --git a/src/mol-model/structure/structure/unit/links/inter-compute.ts b/src/mol-model/structure/structure/unit/links/inter-compute.ts
new file mode 100644
index 0000000000000000000000000000000000000000..3eeb8ecc97e0b78ed83e5842f07350aaa326fdac
--- /dev/null
+++ b/src/mol-model/structure/structure/unit/links/inter-compute.ts
@@ -0,0 +1,149 @@
+/**
+ * Copyright (c) 2017 Mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author David Sehnal <david.sehnal@gmail.com>
+ */
+
+import { StructConn } from '../../../model/formats/mmcif/bonds';
+import { LinkType } from '../../../model/types';
+import Structure from '../../structure';
+import Unit from '../../unit';
+import { getElementIdx, getElementPairThreshold, getElementThreshold, isHydrogen, LinkComputationParameters, MetalsSet } from './common';
+import { InterUnitBonds } from './data';
+import { UniqueArray } from 'mol-data/generic';
+
+const MAX_RADIUS = 4;
+
+function addMapEntry<A, B>(map: Map<A, B[]>, a: A, b: B) {
+    if (map.has(a)) map.get(a)!.push(b);
+    else map.set(a, [b]);
+}
+
+interface PairState {
+    mapAB: Map<number, InterUnitBonds.BondInfo[]>,
+    mapBA: Map<number, InterUnitBonds.BondInfo[]>,
+    bondedA: UniqueArray<number, number>,
+    bondedB: UniqueArray<number, number>
+}
+
+function addLink(indexA: number, indexB: number, order: number, flag: LinkType.Flag, state: PairState) {
+    addMapEntry(state.mapAB, indexA, { indexB, order, flag });
+    addMapEntry(state.mapBA, indexB, { indexB: indexA, order, flag });
+    UniqueArray.add(state.bondedA, indexA, indexA);
+    UniqueArray.add(state.bondedB, indexB, indexB);
+}
+
+function findPairLinks(unitA: Unit.Atomic, unitB: Unit.Atomic, params: LinkComputationParameters, map: Map<number, InterUnitBonds.UnitPairBonds[]>) {
+    const state: PairState = { mapAB: new Map(), mapBA: new Map(), bondedA: UniqueArray.create(), bondedB: UniqueArray.create() };
+    let bondCount = 0;
+
+    const { elements: atomsA, conformation: { x, y, z } } = unitA;
+    const { elements: atomsB } = unitB;
+    const atomCount = unitA.elements.length;
+
+    const { type_symbol: type_symbolA, label_alt_id: label_alt_idA } = unitA.model.atomicHierarchy.atoms;
+    const { type_symbol: type_symbolB, label_alt_id: label_alt_idB } = unitB.model.atomicHierarchy.atoms;
+    const { lookup3d } = unitB;
+    const structConn = unitA.model === unitB.model && unitA.model.sourceData.kind === 'mmCIF' ? StructConn.fromModel(unitA.model) : void 0;
+
+    for (let _aI = 0; _aI < atomCount; _aI++) {
+        const aI =  atomsA[_aI];
+
+        const aeI = getElementIdx(type_symbolA.value(aI));
+        const { indices, count, squaredDistances } = lookup3d.find(x(aI), y(aI), z(aI), MAX_RADIUS);
+        const isHa = isHydrogen(aeI);
+        const thresholdA = getElementThreshold(aeI);
+        const altA = label_alt_idA.value(aI);
+        const metalA = MetalsSet.has(aeI);
+        const structConnEntries = params.forceCompute ? void 0 : structConn && structConn.getAtomEntries(aI);
+
+        for (let ni = 0; ni < count; ni++) {
+            const _bI = indices[ni];
+            const bI = atomsB[_bI];
+
+            const altB = label_alt_idB.value(bI);
+            // TODO: check if they have the same model?
+            if (altA && altB && altA !== altB) continue;
+
+            const beI = getElementIdx(type_symbolB.value(bI)!);
+            const isMetal = metalA || MetalsSet.has(beI);
+
+            const isHb = isHydrogen(beI);
+            if (isHa && isHb) continue;
+
+            const dist = Math.sqrt(squaredDistances[ni]);
+            if (dist === 0) continue;
+
+            // handle "struct conn" bonds.
+            if (structConnEntries && structConnEntries.length) {
+                let added = false;
+                for (const se of structConnEntries) {
+                    for (const p of se.partners) {
+                        if (p.atomIndex === bI) {
+                            addLink(_aI, _bI, se.order, se.flags, state);
+                            bondCount++;
+                            added = true;
+                            break;
+                        }
+                    }
+                    if (added) break;
+                }
+                if (added) continue;
+            }
+
+            if (isHa || isHb) {
+                if (dist < params.maxHbondLength) {
+                    addLink(_aI, _bI, 1, LinkType.Flag.Covalent | LinkType.Flag.Computed, state); // TODO: check if correct
+                    bondCount++;
+                }
+                continue;
+            }
+
+            const thresholdAB = getElementPairThreshold(aeI, beI);
+            const pairingThreshold = thresholdAB > 0
+                ? thresholdAB
+                : beI < 0 ? thresholdA : Math.max(thresholdA, getElementThreshold(beI));
+
+            if (dist <= pairingThreshold) {
+                addLink(_aI, _bI, 1, (isMetal ? LinkType.Flag.MetallicCoordination : LinkType.Flag.Covalent) | LinkType.Flag.Computed, state);
+                bondCount++;
+            }
+        }
+    }
+
+    addMapEntry(map, unitA.id, new InterUnitBonds.UnitPairBonds(unitA, unitB, bondCount, state.bondedA.array, state.mapAB));
+    addMapEntry(map, unitB.id, new InterUnitBonds.UnitPairBonds(unitB, unitA, bondCount, state.bondedB.array, state.mapBA));
+
+    return bondCount;
+}
+
+function findLinks(structure: Structure, params: LinkComputationParameters) {
+    const map = new Map<number, InterUnitBonds.UnitPairBonds[]>();
+    if (!structure.units.some(u => Unit.isAtomic(u))) return new InterUnitBonds(map);
+
+    const lookup = structure.lookup3d;
+    for (const unit of structure.units) {
+        if (!Unit.isAtomic(unit)) continue;
+
+        const bs = unit.lookup3d.boundary.sphere;
+        const closeUnits = lookup.findUnitIndices(bs.center[0], bs.center[1], bs.center[2], bs.radius + MAX_RADIUS);
+        for (let i = 0; i < closeUnits.count; i++) {
+            const other = structure.units[closeUnits.indices[i]];
+            if (!Unit.isAtomic(other) || unit.id >= other.id) continue;
+
+            if (other.elements.length >= unit.elements.length) findPairLinks(unit, other, params, map);
+            else findPairLinks(other, unit, params, map);
+        }
+    }
+
+    return new InterUnitBonds(map);
+}
+
+function computeInterUnitBonds(structure: Structure, params?: Partial<LinkComputationParameters>): InterUnitBonds {
+    return findLinks(structure, {
+        maxHbondLength: (params && params.maxHbondLength) || 1.15,
+        forceCompute: !!(params && params.forceCompute),
+    });
+}
+
+export { computeInterUnitBonds };
diff --git a/src/mol-model/structure/structure/unit/links/intra-compute.ts b/src/mol-model/structure/structure/unit/links/intra-compute.ts
new file mode 100644
index 0000000000000000000000000000000000000000..28ee7cc7d701f49d3fe91af6f6a96d40bc01a313
--- /dev/null
+++ b/src/mol-model/structure/structure/unit/links/intra-compute.ts
@@ -0,0 +1,161 @@
+/**
+ * Copyright (c) 2017 Mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author David Sehnal <david.sehnal@gmail.com>
+ */
+
+import { LinkType } from '../../../model/types'
+import { IntraUnitLinks } from './data'
+import { StructConn, ComponentBond } from '../../../model/formats/mmcif/bonds'
+import Unit from '../../unit'
+import { IntAdjacencyGraph } from 'mol-math/graph';
+import { LinkComputationParameters, getElementIdx, MetalsSet, getElementThreshold, isHydrogen, getElementPairThreshold } from './common';
+
+function getGraph(atomA: number[], atomB: number[], _order: number[], _flags: number[], atomCount: number): IntraUnitLinks {
+    const builder = new IntAdjacencyGraph.EdgeBuilder(atomCount, atomA, atomB);
+    const flags = new Uint16Array(builder.slotCount);
+    const order = new Int8Array(builder.slotCount);
+    for (let i = 0, _i = builder.edgeCount; i < _i; i++) {
+        builder.addNextEdge();
+        builder.assignProperty(flags, _flags[i]);
+        builder.assignProperty(order, _order[i]);
+    }
+
+    return builder.createGraph({ flags, order });
+}
+
+function _computeBonds(unit: Unit.Atomic, params: LinkComputationParameters): IntraUnitLinks {
+    const MAX_RADIUS = 4;
+
+    const { x, y, z } = unit.model.atomicConformation;
+    const atomCount = unit.elements.length;
+    const { elements: atoms, residueIndex } = unit;
+    const { type_symbol, label_atom_id, label_alt_id } = unit.model.atomicHierarchy.atoms;
+    const { label_comp_id } = unit.model.atomicHierarchy.residues;
+    const query3d = unit.lookup3d;
+
+    const structConn = unit.model.sourceData.kind === 'mmCIF' ? StructConn.fromModel(unit.model) : void 0;
+    const component = unit.model.sourceData.kind === 'mmCIF' ? ComponentBond.fromModel(unit.model) : void 0;
+
+    const atomA: number[] = [];
+    const atomB: number[] = [];
+    const flags: number[] = [];
+    const order: number[] = [];
+
+    let lastResidue = -1;
+    let componentMap: Map<string, Map<string, { flags: number, order: number }>> | undefined = void 0;
+
+    for (let _aI = 0; _aI < atomCount; _aI++) {
+        const aI =  atoms[_aI];
+        const raI = residueIndex[aI];
+
+        if (!params.forceCompute && raI !== lastResidue) {
+            const resn = label_comp_id.value(raI)!;
+            if (!!component && component.entries.has(resn)) {
+                componentMap = component.entries.get(resn)!.map;
+            } else {
+                componentMap = void 0;
+            }
+        }
+        lastResidue = raI;
+
+        const componentPairs = componentMap ? componentMap.get(label_atom_id.value(aI)) : void 0;
+
+        const aeI = getElementIdx(type_symbol.value(aI)!);
+
+        const { indices, count, squaredDistances } = query3d.find(x[aI], y[aI], z[aI], MAX_RADIUS);
+        const isHa = isHydrogen(aeI);
+        const thresholdA = getElementThreshold(aeI);
+        const altA = label_alt_id.value(aI);
+        const metalA = MetalsSet.has(aeI);
+        const structConnEntries = params.forceCompute ? void 0 : structConn && structConn.getAtomEntries(aI);
+
+        for (let ni = 0; ni < count; ni++) {
+            const _bI = indices[ni];
+            const bI = atoms[_bI];
+            if (bI <= aI) continue;
+
+            const altB = label_alt_id.value(bI);
+            if (altA && altB && altA !== altB) continue;
+
+            const beI = getElementIdx(type_symbol.value(bI)!);
+            const isMetal = metalA || MetalsSet.has(beI);
+
+            const rbI = residueIndex[bI];
+            // handle "component dictionary" bonds.
+            if (raI === rbI && componentPairs) {
+                const e = componentPairs.get(label_atom_id.value(bI)!);
+                if (e) {
+                    atomA[atomA.length] = _aI;
+                    atomB[atomB.length] = _bI;
+                    order[order.length] = e.order;
+                    let flag = e.flags;
+                    if (isMetal) {
+                        if (flag | LinkType.Flag.Covalent) flag ^= LinkType.Flag.Covalent;
+                        flag |= LinkType.Flag.MetallicCoordination;
+                    }
+                    flags[flags.length] = flag;
+                }
+                continue;
+            }
+
+            const isHb = isHydrogen(beI);
+            if (isHa && isHb) continue;
+
+            const dist = Math.sqrt(squaredDistances[ni]);
+            if (dist === 0) continue;
+
+            // handle "struct conn" bonds.
+            if (structConnEntries && structConnEntries.length) {
+                let added = false;
+                for (const se of structConnEntries) {
+                    for (const p of se.partners) {
+                        if (p.atomIndex === bI) {
+                            atomA[atomA.length] = _aI;
+                            atomB[atomB.length] = _bI;
+                            flags[flags.length] = se.flags;
+                            order[order.length] = se.order;
+                            added = true;
+                            break;
+                        }
+                    }
+                    if (added) break;
+                }
+                if (added) continue;
+            }
+
+            if (isHa || isHb) {
+                if (dist < params.maxHbondLength) {
+                    atomA[atomA.length] = _aI;
+                    atomB[atomB.length] = _bI;
+                    order[order.length] = 1;
+                    flags[flags.length] = LinkType.Flag.Covalent | LinkType.Flag.Computed; // TODO: check if correct
+                }
+                continue;
+            }
+
+            const thresholdAB = getElementPairThreshold(aeI, beI);
+            const pairingThreshold = thresholdAB > 0
+                ? thresholdAB
+                : beI < 0 ? thresholdA : Math.max(thresholdA, getElementThreshold(beI));
+
+            if (dist <= pairingThreshold) {
+                atomA[atomA.length] = _aI;
+                atomB[atomB.length] = _bI;
+                order[order.length] = 1;
+                flags[flags.length] = (isMetal ? LinkType.Flag.MetallicCoordination : LinkType.Flag.Covalent) | LinkType.Flag.Computed;
+            }
+        }
+    }
+
+    return getGraph(atomA, atomB, order, flags, atomCount);
+}
+
+function computeIntraUnitBonds(unit: Unit.Atomic, params?: Partial<LinkComputationParameters>) {
+    return _computeBonds(unit, {
+        maxHbondLength: (params && params.maxHbondLength) || 1.15,
+        forceCompute: !!(params && params.forceCompute),
+    });
+}
+
+export { computeIntraUnitBonds }
\ No newline at end of file
diff --git a/src/mol-model/structure/structure/unit/rings/compute.ts b/src/mol-model/structure/structure/unit/rings/compute.ts
index a76cd6e47c29b67c4d57d2a7565f91a6cd3f2b76..c0bafe3e3abe380d3557ef14edfdd3225299902b 100644
--- a/src/mol-model/structure/structure/unit/rings/compute.ts
+++ b/src/mol-model/structure/structure/unit/rings/compute.ts
@@ -5,8 +5,9 @@
  */
 
 import Unit from '../../unit';
-import { IntraUnitBonds } from '../bonds/intra-data';
+import { IntraUnitLinks } from '../links/data';
 import { Segmentation } from 'mol-data/int';
+import { LinkType } from '../../../model/types';
 
 export default function computeRings(unit: Unit.Atomic) {
     const size = largestResidue(unit);
@@ -40,7 +41,7 @@ interface State {
     currentColor: number,
 
     rings: number[][],
-    bonds: IntraUnitBonds,
+    bonds: IntraUnitLinks,
     unit: Unit.Atomic
 }
 
@@ -58,7 +59,7 @@ function State(unit: Unit.Atomic, capacity: number): State {
         currentColor: 0,
         rings: [],
         unit,
-        bonds: unit.bonds
+        bonds: unit.links
     };
 }
 
@@ -148,7 +149,7 @@ function addRing(state: State, a: number, b: number) {
 
 function findRings(state: State, from: number) {
     const { bonds, startVertex, endVertex, visited, queue, pred } = state;
-    const { b: neighbor, flags: bondFlags, offset } = bonds;
+    const { b: neighbor, edgeProps: { flags: bondFlags }, offset } = bonds;
     visited[from] = 1;
     queue[0] = from;
     let head = 0, size = 1;
@@ -160,7 +161,7 @@ function findRings(state: State, from: number) {
 
         for (let i = start; i < end; i++) {
             const b = neighbor[i];
-            if (b < startVertex || b >= endVertex || !IntraUnitBonds.isCovalent(bondFlags[i])) continue;
+            if (b < startVertex || b >= endVertex || !LinkType.isCovalent(bondFlags[i])) continue;
 
             const other = b - startVertex;
 
diff --git a/src/mol-model/structure/structure/util/lookup3d.ts b/src/mol-model/structure/structure/util/lookup3d.ts
index 2ea308e3ec11624d1528fa28d8d9f38e49f96fd0..3c7b8b6ac24690292f4eacc144fde5fbf27368e8 100644
--- a/src/mol-model/structure/structure/util/lookup3d.ts
+++ b/src/mol-model/structure/structure/util/lookup3d.ts
@@ -17,6 +17,10 @@ export class StructureLookup3D implements Lookup3D<Element> {
     private result = Result.create<Element>();
     private pivot = Vec3.zero();
 
+    findUnitIndices(x: number, y: number, z: number, radius: number): Result<number> {
+        return this.unitLookup.find(x, y, z, radius);
+    }
+
     find(x: number, y: number, z: number, radius: number): Result<Element> {
         Result.reset(this.result);
         const { units } = this.structure;
diff --git a/src/mol-view/label.ts b/src/mol-view/label.ts
index 578a4a7a53260c5c682edc6a7d648edf692a0031..0ea82df1ba0a07c339d76453580f942cf19a85cd 100644
--- a/src/mol-view/label.ts
+++ b/src/mol-view/label.ts
@@ -6,7 +6,7 @@
  */
 
 import { Unit, Element, Queries } from 'mol-model/structure';
-import { Bond } from 'mol-model/structure/structure/unit/bonds';
+import { Link } from 'mol-model/structure/structure/unit/links';
 import { Loci } from 'mol-model/loci';
 
 const elementLocA = Element.Location()
@@ -23,8 +23,8 @@ export function labelFirst(loci: Loci) {
         if (e && e.indices[0] !== undefined) {
             return elementLabel(Element.Location(e.unit, e.indices[0]))
         }
-    } else if (Bond.isLoci(loci)) {
-        const bond = loci.bonds[0]
+    } else if (Link.isLoci(loci)) {
+        const bond = loci.links[0]
         if (bond) {
             setElementLocation(elementLocA, bond.aUnit, bond.aIndex)
             setElementLocation(elementLocB, bond.bUnit, bond.bIndex)
diff --git a/src/mol-view/stage.ts b/src/mol-view/stage.ts
index af44bda4bf3e7f202db99f95f6f971f8ef7b347b..1f46148e836e57db7654686a47d4438b8e1b26bd 100644
--- a/src/mol-view/stage.ts
+++ b/src/mol-view/stage.ts
@@ -10,6 +10,7 @@ import { Progress } from 'mol-task';
 import { MmcifUrlToModel, ModelToStructure, StructureToSpacefill, StructureToBond } from './state/transform';
 import { UrlEntity } from './state/entity';
 import { SpacefillProps } from 'mol-geo/representation/structure/spacefill';
+import { Context } from 'mol-app/context/context';
 
 // export const ColorTheme = {
 //     'atom-index': {},
@@ -30,7 +31,7 @@ export class Stage {
     viewer: Viewer
     ctx = new StateContext(Progress.format)
 
-    constructor() {
+    constructor(public globalContext: Context) {
 
     }
 
@@ -38,7 +39,7 @@ export class Stage {
         this.viewer = Viewer.create(canvas, container)
         this.viewer.animate()
         this.ctx.viewer = this.viewer
-        // this.loadPdbid('1crn')
+        //this.loadPdbid('1jj2')
         this.loadMmcifUrl(`../../examples/1cbs_full.bcif`)
     }
 
@@ -48,6 +49,8 @@ export class Stage {
         const structureEntity = await ModelToStructure.apply(this.ctx, modelEntity)
         StructureToSpacefill.apply(this.ctx, structureEntity, spacefillProps)
         StructureToBond.apply(this.ctx, structureEntity, spacefillProps) // TODO props
+
+        this.globalContext.components.sequenceView.setState({ structure: structureEntity.value });
     }
 
     loadPdbid (pdbid: string) {