From a09e0d487598b7c5290b939331aa7cdcd0c0ac3a Mon Sep 17 00:00:00 2001
From: David Sehnal <david.sehnal@gmail.com>
Date: Mon, 25 Jun 2018 20:35:27 +0200
Subject: [PATCH] wip, custom props

---
 src/mol-model/structure/export/mmcif.ts       | 10 ++++----
 .../structure/model/formats/mmcif.ts          |  6 ++++-
 .../structure/model/formats/mmcif/bonds.ts    | 10 +++++---
 src/mol-model/structure/model/model.ts        | 15 ++++++++---
 .../structure/model/properties/custom.ts      |  8 ++++++
 .../model/properties/custom/collection.ts     | 25 +++++++++++++++++++
 .../model/properties/custom/descriptor.ts     | 18 +++++++++++++
 7 files changed, 78 insertions(+), 14 deletions(-)
 create mode 100644 src/mol-model/structure/model/properties/custom.ts
 create mode 100644 src/mol-model/structure/model/properties/custom/collection.ts
 create mode 100644 src/mol-model/structure/model/properties/custom/descriptor.ts

diff --git a/src/mol-model/structure/export/mmcif.ts b/src/mol-model/structure/export/mmcif.ts
index ea699f649..bf93893f0 100644
--- a/src/mol-model/structure/export/mmcif.ts
+++ b/src/mol-model/structure/export/mmcif.ts
@@ -11,7 +11,7 @@ import { Structure, Element } from '../structure'
 import { Model } from '../model'
 import P from '../query/properties'
 
-interface Context {
+export interface CifExportContext {
     structure: Structure,
     model: Model
 }
@@ -53,7 +53,7 @@ const atom_site_fields: CifField<Element.Location>[] = [
 ];
 
 function copy_mmCif_cat(name: keyof mmCIF_Schema) {
-    return ({ model }: Context) => {
+    return ({ model }: CifExportContext) => {
         if (model.sourceData.kind !== 'mmCIF') return CifCategory.Empty;
         const table = model.sourceData.data[name];
         if (!table || !table._rowCount) return CifCategory.Empty;
@@ -61,12 +61,12 @@ function copy_mmCif_cat(name: keyof mmCIF_Schema) {
     };
 }
 
-function _entity({ model, structure }: Context): CifCategory {
+function _entity({ model, structure }: CifExportContext): CifCategory {
     const keys = Structure.getEntityKeys(structure);
     return CifCategory.ofTable('entity', model.entities.data, keys);
 }
 
-function _atom_site({ structure }: Context): CifCategory {
+function _atom_site({ structure }: CifExportContext): CifCategory {
     return {
         data: structure,
         name: 'atom_site',
@@ -104,7 +104,7 @@ export function encode_mmCIF_categories(encoder: CifWriter.Encoder, structure: S
     if (models.length !== 1) throw 'Can\'t export stucture composed from multiple models.';
     const model = models[0];
 
-    const ctx: Context[] = [{ structure, model }];
+    const ctx: CifExportContext[] = [{ structure, model }];
 
     for (const cat of Categories) {
         encoder.writeCategory(cat, ctx);
diff --git a/src/mol-model/structure/model/formats/mmcif.ts b/src/mol-model/structure/model/formats/mmcif.ts
index abd3a7a59..66c2bfc28 100644
--- a/src/mol-model/structure/model/formats/mmcif.ts
+++ b/src/mol-model/structure/model/formats/mmcif.ts
@@ -24,6 +24,7 @@ import { getSequence } from './mmcif/sequence';
 import { sortAtomSite } from './mmcif/sort';
 import { mmCIF_Database, mmCIF_Schema } from 'mol-io/reader/cif/schema/mmcif';
 import { Element } from '../../../structure'
+import { CustomProperties } from '../properties/custom';
 
 import mmCIF_Format = Format.mmCIF
 type AtomSite = mmCIF_Database['atom_site']
@@ -184,6 +185,7 @@ function createModel(format: mmCIF_Format, atom_site: AtomSite, previous?: Model
         sourceData: format,
         modelNum: format.data.atom_site.pdbx_PDB_model_num.value(0),
         entities,
+        symmetry: getSymmetry(format),
         atomicHierarchy,
         sequence: getSequence(format.data, entities, atomicHierarchy, modifiedResidueNameMap),
         atomicConformation: getConformation(atom_site),
@@ -193,7 +195,9 @@ function createModel(format: mmCIF_Format, atom_site: AtomSite, previous?: Model
             secondaryStructure: getSecondaryStructureMmCif(format.data, atomicHierarchy),
             modifiedResidueNameMap
         },
-        symmetry: getSymmetry(format)
+        customProperties: new CustomProperties(),
+        _staticPropertyData: Object.create(null),
+        _dynamicPropertyData: Object.create(null)
     };
 }
 
diff --git a/src/mol-model/structure/model/formats/mmcif/bonds.ts b/src/mol-model/structure/model/formats/mmcif/bonds.ts
index 4eb625286..5dd2180ce 100644
--- a/src/mol-model/structure/model/formats/mmcif/bonds.ts
+++ b/src/mol-model/structure/model/formats/mmcif/bonds.ts
@@ -10,6 +10,8 @@ import { LinkType } from '../../types'
 import { findEntityIdByAsymId, findAtomIndexByLabelName } from './util'
 import { Column } from 'mol-data/db'
 
+// TODO: add dynamic property descriptor for this?
+
 export interface StructConn {
     getResidueEntries(residueAIndex: number, residueBIndex: number): ReadonlyArray<StructConn.Entry>
     getAtomEntries(atomIndex: number): ReadonlyArray<StructConn.Entry>
@@ -100,7 +102,7 @@ export namespace StructConn {
 
     export const PropName = '__StructConn__';
     export function fromModel(model: Model): StructConn | undefined {
-        if (model.properties[PropName]) return model.properties[PropName];
+        if (model._staticPropertyData[PropName]) return model._staticPropertyData[PropName];
 
         if (model.sourceData.kind !== 'mmCIF') return;
         const { struct_conn } = model.sourceData.data;
@@ -189,7 +191,7 @@ export namespace StructConn {
         }
 
         const ret = new StructConnImpl(entries);
-        model.properties[PropName] = ret;
+        model._staticPropertyData[PropName] = ret;
         return ret;
     }
 }
@@ -230,7 +232,7 @@ export namespace ComponentBond {
 
     export const PropName = '__ComponentBond__';
     export function fromModel(model: Model): ComponentBond | undefined {
-        if (model.properties[PropName]) return model.properties[PropName];
+        if (model._staticPropertyData[PropName]) return model._staticPropertyData[PropName];
 
         if (model.sourceData.kind !== 'mmCIF') return
         const { chem_comp_bond } = model.sourceData.data;
@@ -269,7 +271,7 @@ export namespace ComponentBond {
             entry.add(nameA, nameB, ord, flags);
         }
 
-        model.properties[PropName] = compBond;
+        model._staticPropertyData[PropName] = compBond;
         return compBond;
     }
 }
\ 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 6ade4fe45..bce90da7e 100644
--- a/src/mol-model/structure/model/model.ts
+++ b/src/mol-model/structure/model/model.ts
@@ -11,6 +11,7 @@ import { AtomicHierarchy, AtomicConformation } from './properties/atomic'
 import { ModelSymmetry } from './properties/symmetry'
 import { CoarseHierarchy, CoarseConformation } from './properties/coarse'
 import { Entities } from './properties/common';
+import { CustomProperties } from './properties/custom';
 import { SecondaryStructure } from './properties/seconday-structure';
 
 //import from_gro from './formats/gro'
@@ -35,15 +36,21 @@ interface Model extends Readonly<{
     atomicHierarchy: AtomicHierarchy,
     atomicConformation: AtomicConformation,
 
-    /** Various parts of the code can "cache" custom properties here */
     properties: {
+        // secondary structure provided by the input file
         readonly secondaryStructure: SecondaryStructure,
         // maps modified residue name to its parent
-        readonly modifiedResidueNameMap: Map<string, string>,
-        [customName: string]: any
+        readonly modifiedResidueNameMap: Map<string, string>
     },
 
-    // TODO: separate properties to "static" (propagated with trajectory) and "dynamic" (computed for each frame separately)
+    customProperties: CustomProperties,
+
+    /**
+     * Not to be accessed directly, each custom property descriptor
+     * defines property accessors that use this field to store the data.
+     */
+    _staticPropertyData: { [name: string]: any },
+    _dynamicPropertyData: { [name: string]: any },
 
     coarseHierarchy: CoarseHierarchy,
     coarseConformation: CoarseConformation
diff --git a/src/mol-model/structure/model/properties/custom.ts b/src/mol-model/structure/model/properties/custom.ts
new file mode 100644
index 000000000..d9a06eee1
--- /dev/null
+++ b/src/mol-model/structure/model/properties/custom.ts
@@ -0,0 +1,8 @@
+/**
+ * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author David Sehnal <david.sehnal@gmail.com>
+ */
+
+export * from './custom/descriptor'
+export * from './custom/collection'
\ 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
new file mode 100644
index 000000000..75dda9b2d
--- /dev/null
+++ b/src/mol-model/structure/model/properties/custom/collection.ts
@@ -0,0 +1,25 @@
+/**
+ * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author David Sehnal <david.sehnal@gmail.com>
+ */
+
+import { PropertyDescriptor } from './descriptor'
+
+export class CustomProperties {
+    private _list: PropertyDescriptor[] = [];
+    private _set = new Set<PropertyDescriptor>();
+
+    get all(): ReadonlyArray<PropertyDescriptor> {
+        return this._list;
+    }
+
+    add(desc: PropertyDescriptor) {
+        this._list.push(desc);
+        this._set.add(desc);
+    }
+
+    has(desc: PropertyDescriptor): 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
new file mode 100644
index 000000000..37af3562b
--- /dev/null
+++ b/src/mol-model/structure/model/properties/custom/descriptor.ts
@@ -0,0 +1,18 @@
+/**
+ * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author David Sehnal <david.sehnal@gmail.com>
+ */
+
+import { CifWriter } from 'mol-io/writer/cif'
+import { CifExportContext } from '../../../export/mmcif';
+
+interface PropertyDescriptor {
+    readonly isStatic: boolean,
+    readonly name: string,
+
+    /** Given a structure, returns a list of category providers used for export. */
+    getCifCategories: (ctx: CifExportContext) => CifWriter.Category.Provider[]
+}
+
+export { PropertyDescriptor }
\ No newline at end of file
-- 
GitLab