From 25965d307a838c400936d320e6ce33eac39be4c8 Mon Sep 17 00:00:00 2001
From: David Sehnal <david.sehnal@gmail.com>
Date: Tue, 26 Jun 2018 12:10:15 +0200
Subject: [PATCH] chem_comp_bond as custom property

---
 src/apps/structure-info/model.ts              |  2 +-
 src/mol-data/int/impl/segmentation.ts         |  6 +-
 src/mol-data/int/segmentation.ts              |  4 +-
 .../export/categories/chem_comp_bond.ts       |  7 --
 src/mol-model/structure/export/mmcif.ts       |  6 ++
 .../structure/model/formats/mmcif.ts          |  6 ++
 .../model/formats/mmcif/bonds/comp.ts         | 67 +++++++++++++++++--
 .../model/properties/custom/collection.ts     | 12 ++--
 .../model/properties/custom/descriptor.ts     | 11 +--
 .../structure/unit/links/intra-compute.ts     |  2 +-
 10 files changed, 94 insertions(+), 29 deletions(-)
 delete mode 100644 src/mol-model/structure/export/categories/chem_comp_bond.ts

diff --git a/src/apps/structure-info/model.ts b/src/apps/structure-info/model.ts
index d1252937c..f34db7c79 100644
--- a/src/apps/structure-info/model.ts
+++ b/src/apps/structure-info/model.ts
@@ -207,7 +207,7 @@ async function run(frame: CifFrame) {
     printUnits(structure);
     printSymmetryInfo(models[0]);
     //printRings(structure);
-    //printLinks(structure, true, true);
+    printLinks(structure, true, true);
     //printModRes(models[0]);
     //printSecStructure(models[0]);
 }
diff --git a/src/mol-data/int/impl/segmentation.ts b/src/mol-data/int/impl/segmentation.ts
index 63313431f..a5534e52a 100644
--- a/src/mol-data/int/impl/segmentation.ts
+++ b/src/mol-data/int/impl/segmentation.ts
@@ -54,7 +54,7 @@ export class SegmentIterator<T extends number = number> implements Iterator<Segs
     private segmentMin = 0;
     private segmentMax = 0;
     private setRange = Interval.Empty;
-    private value: Segs.Segment<T> = { index: 0, start: 0, end: 0 };
+    private value: Segs.Segment<T> = { index: 0, start: 0 as T, end: 0 as T };
 
     hasNext: boolean = false;
 
@@ -75,8 +75,8 @@ export class SegmentIterator<T extends number = number> implements Iterator<Segs
         const segmentEnd = this.segments[this.segmentMin + 1];
         // TODO: add optimized version for interval and array?
         const setEnd = OrderedSet.findPredecessorIndexInInterval(this.set, segmentEnd, this.setRange);
-        this.value.start = Interval.start(this.setRange);
-        this.value.end = setEnd;
+        this.value.start = Interval.start(this.setRange) as T;
+        this.value.end = setEnd as T;
         this.setRange = Interval.ofBounds(setEnd, Interval.end(this.setRange));
         return setEnd > this.value.start;
     }
diff --git a/src/mol-data/int/segmentation.ts b/src/mol-data/int/segmentation.ts
index 8f7c58db6..94abaaa0f 100644
--- a/src/mol-data/int/segmentation.ts
+++ b/src/mol-data/int/segmentation.ts
@@ -9,7 +9,7 @@ import OrderedSet from './ordered-set'
 import * as Impl from './impl/segmentation'
 
 namespace Segmentation {
-    export interface Segment<T extends number = number> { index: number, start: number, end: number }
+    export interface Segment<T extends number = number> { index: number, start: T, end: T }
 
     export const create: <T extends number = number>(segs: ArrayLike<T>) => Segmentation<T> = Impl.create as any;
     export const ofOffsets: <T extends number = number>(offsets: ArrayLike<T>, bounds: Interval) => Segmentation<T> = Impl.ofOffsets as any;
@@ -19,7 +19,7 @@ namespace Segmentation {
     export const projectValue: <T extends number = number>(segs: Segmentation<T>, set: OrderedSet<T>, value: T) => Interval = Impl.projectValue as any;
 
     // Segment iterator that mutates a single segment object to mark all the segments.
-    export const transientSegments: <T extends number = number>(segs: Segmentation<T>, set: OrderedSet<T>, segment?: Segment<T>) => Impl.SegmentIterator = Impl.segments as any;
+    export const transientSegments: <T extends number = number>(segs: Segmentation<T>, set: OrderedSet<T>, segment?: Segment<T>) => Impl.SegmentIterator<T> = Impl.segments as any;
 }
 
 interface Segmentation<T extends number = number> {
diff --git a/src/mol-model/structure/export/categories/chem_comp_bond.ts b/src/mol-model/structure/export/categories/chem_comp_bond.ts
deleted file mode 100644
index 66298c566..000000000
--- a/src/mol-model/structure/export/categories/chem_comp_bond.ts
+++ /dev/null
@@ -1,7 +0,0 @@
-/**
- * Copyright (c) 2017-2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
- *
- * @author David Sehnal <david.sehnal@gmail.com>
- */
-
-// TODO
\ No newline at end of file
diff --git a/src/mol-model/structure/export/mmcif.ts b/src/mol-model/structure/export/mmcif.ts
index 4458ef154..f09ee2103 100644
--- a/src/mol-model/structure/export/mmcif.ts
+++ b/src/mol-model/structure/export/mmcif.ts
@@ -63,6 +63,12 @@ export function encode_mmCIF_categories(encoder: CifWriter.Encoder, structure: S
     for (const cat of Categories) {
         encoder.writeCategory(cat, ctx);
     }
+    for (const customProp of model.customProperties.all) {
+        const cats = customProp.cifExport.categoryProvider(ctx[0]);
+        for (const cat of cats) {
+            encoder.writeCategory(cat, ctx);
+        }
+    }
 }
 
 function to_mmCIF(name: string, structure: Structure, asBinary = false) {
diff --git a/src/mol-model/structure/model/formats/mmcif.ts b/src/mol-model/structure/model/formats/mmcif.ts
index 66c2bfc28..6bd490457 100644
--- a/src/mol-model/structure/model/formats/mmcif.ts
+++ b/src/mol-model/structure/model/formats/mmcif.ts
@@ -27,6 +27,7 @@ import { Element } from '../../../structure'
 import { CustomProperties } from '../properties/custom';
 
 import mmCIF_Format = Format.mmCIF
+import { ComponentBond } from './mmcif/bonds';
 type AtomSite = mmCIF_Database['atom_site']
 
 function findModelEnd({ data }: mmCIF_Format, startIndex: number) {
@@ -201,6 +202,10 @@ function createModel(format: mmCIF_Format, atom_site: AtomSite, previous?: Model
     };
 }
 
+function attachProps(model: Model) {
+    ComponentBond.attachFromMmCif(model);
+}
+
 function buildModels(format: mmCIF_Format): Task<ReadonlyArray<Model>> {
     return Task.create('Create mmCIF Model', async ctx => {
         const atomCount = format.data.atom_site._rowCount;
@@ -218,6 +223,7 @@ function buildModels(format: mmCIF_Format): Task<ReadonlyArray<Model>> {
             const modelEnd = findModelEnd(format, modelStart);
             const atom_site = await sortAtomSite(ctx, format.data.atom_site, modelStart, modelEnd);
             const model = createModel(format, atom_site, models.length > 0 ? models[models.length - 1] : void 0);
+            attachProps(model);
             models.push(model);
             modelStart = modelEnd;
         }
diff --git a/src/mol-model/structure/model/formats/mmcif/bonds/comp.ts b/src/mol-model/structure/model/formats/mmcif/bonds/comp.ts
index 4b31456ab..fe0c4ab4e 100644
--- a/src/mol-model/structure/model/formats/mmcif/bonds/comp.ts
+++ b/src/mol-model/structure/model/formats/mmcif/bonds/comp.ts
@@ -7,12 +7,50 @@
 
 import Model from '../../../model'
 import { LinkType } from '../../../types'
+import { ModelPropertyDescriptor } from '../../../properties/custom';
+import { mmCIF_Database } from 'mol-io/reader/cif/schema/mmcif';
+import { Structure, Unit, StructureProperties, Element } from '../../../../structure';
+import { Segmentation } from 'mol-data/int';
+import { CifWriter } from 'mol-io/writer/cif'
 
 export interface ComponentBond {
     entries: Map<string, ComponentBond.Entry>
 }
 
 export namespace ComponentBond {
+    export const Descriptor: ModelPropertyDescriptor = {
+        isStatic: true,
+        name: 'chem_comp_bond',
+        cifExport: {
+            categoryNames: ['chem_comp_bond'],
+            categoryProvider(ctx) {
+                const comp_names = getUniqueResidueNames(ctx.structure);
+                const chem_comp_bond = getChemCompBond(ctx.model);
+
+                if (!chem_comp_bond) return [];
+
+                const { comp_id, _rowCount } = chem_comp_bond;
+                const indices: number[] = [];
+                for (let i = 0; i < _rowCount; i++) {
+                    if (comp_names.has(comp_id.value(i))) indices[indices.length] = i;
+                }
+
+                return [
+                    () => CifWriter.Category.ofTable('chem_comp_bond', chem_comp_bond, indices)
+                ];
+            }
+        }
+    }
+
+    export function attachFromMmCif(model: Model): boolean {
+        if (model.sourceData.kind !== 'mmCIF') return false;
+        const { chem_comp_bond } = model.sourceData.data;
+        if (chem_comp_bond._rowCount === 0) return false;
+        model.customProperties.add(Descriptor);
+        model._staticPropertyData.__ComponentBondData__ = chem_comp_bond;
+        return true;
+    }
+
     export class ComponentBondImpl implements ComponentBond {
         entries: Map<string, ComponentBond.Entry> = new Map();
 
@@ -46,13 +84,15 @@ export namespace ComponentBond {
         }
     }
 
+    function getChemCompBond(model: Model) {
+        return model._staticPropertyData.__ComponentBondData__ as mmCIF_Database['chem_comp_bond'];
+    }
+
     export const PropName = '__ComponentBond__';
-    export function fromModel(model: Model): ComponentBond | undefined {
+    export function get(model: Model): ComponentBond | undefined {
         if (model._staticPropertyData[PropName]) return model._staticPropertyData[PropName];
-
-        if (model.sourceData.kind !== 'mmCIF') return
-        const { chem_comp_bond } = model.sourceData.data;
-        if (!chem_comp_bond._rowCount) return void 0;
+        if (!model.customProperties.has(Descriptor)) return void 0;
+        const chem_comp_bond = getChemCompBond(model);
 
         let compBond = new ComponentBondImpl();
 
@@ -90,4 +130,21 @@ export namespace ComponentBond {
         model._staticPropertyData[PropName] = compBond;
         return compBond;
     }
+
+    function getUniqueResidueNames(s: Structure) {
+        const prop = StructureProperties.residue.label_comp_id;
+        const names = new Set<string>();
+        const loc = Element.Location();
+        for (const unit of s.units) {
+            if (!Unit.isAtomic(unit)) continue;
+            const residues = Segmentation.transientSegments(unit.model.atomicHierarchy.residueSegments, unit.elements);
+            loc.unit = unit;
+            while (residues.hasNext) {
+                const seg = residues.move();
+                loc.element = seg.start;
+                names.add(prop(loc));
+            }
+        }
+        return names;
+    }
 }
\ No newline at end of file
diff --git a/src/mol-model/structure/model/properties/custom/collection.ts b/src/mol-model/structure/model/properties/custom/collection.ts
index 75dda9b2d..371889b8f 100644
--- a/src/mol-model/structure/model/properties/custom/collection.ts
+++ b/src/mol-model/structure/model/properties/custom/collection.ts
@@ -4,22 +4,22 @@
  * @author David Sehnal <david.sehnal@gmail.com>
  */
 
-import { PropertyDescriptor } from './descriptor'
+import { ModelPropertyDescriptor } from './descriptor'
 
 export class CustomProperties {
-    private _list: PropertyDescriptor[] = [];
-    private _set = new Set<PropertyDescriptor>();
+    private _list: ModelPropertyDescriptor[] = [];
+    private _set = new Set<ModelPropertyDescriptor>();
 
-    get all(): ReadonlyArray<PropertyDescriptor> {
+    get all(): ReadonlyArray<ModelPropertyDescriptor> {
         return this._list;
     }
 
-    add(desc: PropertyDescriptor) {
+    add(desc: ModelPropertyDescriptor) {
         this._list.push(desc);
         this._set.add(desc);
     }
 
-    has(desc: PropertyDescriptor): boolean {
+    has(desc: ModelPropertyDescriptor): boolean {
         return this._set.has(desc);
     }
 }
\ No newline at end of file
diff --git a/src/mol-model/structure/model/properties/custom/descriptor.ts b/src/mol-model/structure/model/properties/custom/descriptor.ts
index 37af3562b..89304d197 100644
--- a/src/mol-model/structure/model/properties/custom/descriptor.ts
+++ b/src/mol-model/structure/model/properties/custom/descriptor.ts
@@ -7,12 +7,15 @@
 import { CifWriter } from 'mol-io/writer/cif'
 import { CifExportContext } from '../../../export/mmcif';
 
-interface PropertyDescriptor {
+interface ModelPropertyDescriptor {
     readonly isStatic: boolean,
     readonly name: string,
 
-    /** Given a structure, returns a list of category providers used for export. */
-    getCifCategories: (ctx: CifExportContext) => CifWriter.Category.Provider[]
+    cifExport: {
+        /** used category names that can be used for "filtering" by the writer */
+        readonly categoryNames: ReadonlyArray<string>,
+        categoryProvider: (ctx: CifExportContext) => CifWriter.Category.Provider[]
+    }
 }
 
-export { PropertyDescriptor }
\ No newline at end of file
+export { ModelPropertyDescriptor }
\ No newline at end of file
diff --git a/src/mol-model/structure/structure/unit/links/intra-compute.ts b/src/mol-model/structure/structure/unit/links/intra-compute.ts
index 3f2949ec9..c17d81539 100644
--- a/src/mol-model/structure/structure/unit/links/intra-compute.ts
+++ b/src/mol-model/structure/structure/unit/links/intra-compute.ts
@@ -36,7 +36,7 @@ function _computeBonds(unit: Unit.Atomic, params: LinkComputationParameters): In
     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 component = unit.model.sourceData.kind === 'mmCIF' ? ComponentBond.get(unit.model) : void 0;
 
     const atomA: number[] = [];
     const atomB: number[] = [];
-- 
GitLab