Browse Source

add structure-info behavior and plugin-wide properties

- Model.AsymIdOffset
- Model.Index
- Structure.Index
Alexander Rose 4 years ago
parent
commit
bceb044552

+ 19 - 1
src/mol-model/structure/model/model.ts

@@ -13,7 +13,7 @@ import { Entities, ChemicalComponentMap, MissingResidues, StructAsymMap } from '
 import { CustomProperties } from '../../custom-property';
 import { SaccharideComponentMap } from '../structure/carbohydrates/constants';
 import { ModelFormat } from '../../../mol-model-formats/format';
-import { calcModelCenter } from './util';
+import { calcModelCenter, getAsymIdCount } from './util';
 import { Vec3 } from '../../../mol-math/linear-algebra';
 import { Mutable } from '../../../mol-util/type-helpers';
 import { Coordinates } from '../coordinates';
@@ -26,6 +26,7 @@ import { ChainIndex } from './indexing';
 import { SymmetryOperator } from '../../../mol-math/geometry';
 import { ModelSymmetry } from '../../../mol-model-formats/structure/property/symmetry';
 import { Column } from '../../../mol-data/db';
+import { CustomModelProperty } from '../../../mol-model-props/common/custom-model-property';
 
 /**
  * Interface to the "source data" of the molecule.
@@ -144,6 +145,23 @@ export namespace Model {
         }
     };
 
+    const AsymIdCountProp = '__AsymIdCount__';
+    export type AsymIdCount = { readonly auth: number, readonly label: number }
+    export const AsymIdCount = {
+        get(model: Model): AsymIdCount {
+            if (model._staticPropertyData[AsymIdCountProp]) return model._staticPropertyData[AsymIdCountProp];
+            const asymIdCount = getAsymIdCount(model);
+            model._staticPropertyData[AsymIdCountProp] = asymIdCount;
+            return asymIdCount;
+        },
+    };
+
+    export type AsymIdOffset = { auth: number, label: number };
+    export const AsymIdOffset = CustomModelProperty.createSimple<AsymIdOffset>('asym_id_offset', 'static');
+
+    export type Index = number;
+    export const Index = CustomModelProperty.createSimple<Index>('index', 'static');
+
     //
 
     export function hasCarbohydrate(model: Model): boolean {

+ 12 - 1
src/mol-model/structure/model/util.ts

@@ -1,5 +1,5 @@
 /**
- * Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2019-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
@@ -8,6 +8,7 @@ import { Vec3 } from '../../../mol-math/linear-algebra';
 import { AtomicConformation } from './properties/atomic';
 import { CoarseConformation } from './properties/coarse';
 import { arrayMinMax } from '../../../mol-util/array';
+import { Model } from './model';
 
 export function calcModelCenter(atomicConformation: AtomicConformation, coarseConformation?: CoarseConformation) {
     let rangesX: number[] = [];
@@ -42,4 +43,14 @@ export function calcModelCenter(atomicConformation: AtomicConformation, coarseCo
     const z = minZ + (maxZ - minZ) / 2;
 
     return Vec3.create(x, y, z);
+}
+
+export function getAsymIdCount(model: Model) {
+    const auth = new Set<string>();
+    const label = new Set<string>();
+    model.properties.structAsymMap.forEach(({ auth_id }, label_id) => {
+        auth.add(auth_id);
+        label.add(label_id);
+    });
+    return { auth: auth.size, label: label.size };
 }

+ 6 - 0
src/mol-model/structure/structure/structure.ts

@@ -30,6 +30,7 @@ import { AtomicHierarchy } from '../model/properties/atomic';
 import { StructureSelection } from '../query/selection';
 import { getBoundary } from '../../../mol-math/geometry/boundary';
 import { ElementSymbol } from '../model/types';
+import { CustomStructureProperty } from '../../../mol-model-props/common/custom-structure-property';
 
 class Structure {
     /** Maps unit.id to unit */
@@ -1124,6 +1125,11 @@ namespace Structure {
             return Size.Large;
         }
     }
+
+    //
+
+    export type Index = number;
+    export const Index = CustomStructureProperty.createSimple<Index>('index', 'root');
 }
 
 export default Structure;

+ 2 - 0
src/mol-plugin/behavior/dynamic/custom-props.ts

@@ -5,6 +5,8 @@
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
 
+export { StructureInfo } from './custom-props/structure-info';
+
 export { AccessibleSurfaceArea } from './custom-props/computed/accessible-surface-area';
 export { Interactions } from './custom-props/computed/interactions';
 export { SecondaryStructure } from './custom-props/computed/secondary-structure';

+ 112 - 0
src/mol-plugin/behavior/dynamic/custom-props/structure-info.ts

@@ -0,0 +1,112 @@
+/**
+ * Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
+ */
+
+import { PluginBehavior } from '../../../behavior';
+import { Structure, Model } from '../../../../mol-model/structure';
+import { PluginStateObject } from '../../../../mol-plugin-state/objects';
+import { StateSelection, StateObject } from '../../../../mol-state';
+
+export const StructureInfo = PluginBehavior.create({
+    name: 'structure-info-prop',
+    category: 'custom-props',
+    display: { name: 'Structure Info' },
+    ctor: class extends PluginBehavior.Handler {
+        private get maxModelIndex() {
+            let maxIndex = -1;
+            const cells = this.ctx.state.data.select(StateSelection.Generators.rootsOfType(PluginStateObject.Molecule.Model));
+            for (const c of cells) {
+                const index = c.obj?.data && Model.Index.get(c.obj?.data).value;
+                if (index !== undefined && index > maxIndex) maxIndex = index;
+            }
+            return maxIndex;
+        }
+
+        private get maxStructureIndex() {
+            let maxIndex = -1;
+            const cells = this.ctx.state.data.select(StateSelection.Generators.rootsOfType(PluginStateObject.Molecule.Structure));
+            for (const c of cells) {
+                const index = c.obj?.data && Structure.Index.get(c.obj?.data).value;
+                if (index !== undefined && index > maxIndex) maxIndex = index;
+            }
+            return maxIndex;
+        }
+
+        private get asymIdOffset() {
+            let auth = 0;
+            let label = 0;
+            const cells = this.ctx.state.data.select(StateSelection.Generators.rootsOfType(PluginStateObject.Molecule.Model));
+            for (const c of cells) {
+                const m = c.obj?.data;
+                if (m) {
+                    const count = Model.AsymIdCount.get(m);
+                    const offset = Model.AsymIdOffset.get(m).value;
+                    if (count !== undefined && offset !== undefined) {
+                        auth = Math.max(auth, offset.auth + count.auth);
+                        label = Math.max(label, offset.label + count.label);
+                    }
+                }
+            }
+            return { auth, label };
+        }
+
+        private handleModel(model: Model, oldModel?: Model) {
+            if (Model.Index.get(model).value === undefined) {
+                const oldIndex = oldModel && Model.Index.get(oldModel).value;
+                const value = oldIndex ?? (this.maxModelIndex + 1);
+                Model.Index.set(model, { value });
+            }
+
+            if (Model.AsymIdOffset.get(model).value === undefined) {
+                const oldOffset = oldModel && Model.AsymIdOffset.get(oldModel).value;
+                const value = oldOffset ?? { ...this.asymIdOffset };
+                Model.AsymIdOffset.set(model, { value });
+            }
+        }
+
+        private handleStructure(structure: Structure, oldStructure?: Structure) {
+            if (structure.parent !== undefined) return;
+            if (Structure.Index.get(structure).value !== undefined) return;
+
+            const oldIndex = oldStructure && Structure.Index.get(oldStructure).value;
+            const value = oldIndex ?? (this.maxStructureIndex + 1);
+            Structure.Index.set(structure, { value });
+        }
+
+        private handle(ref: string, obj: StateObject<any, StateObject.Type<any>>, oldObj?: StateObject<any, StateObject.Type<any>>) {
+            if (PluginStateObject.Molecule.Structure.is(obj)) {
+                const transform = this.ctx.state.data.tree.transforms.get(ref);
+                if (!transform.transformer.definition.isDecorator && obj.data.parent === undefined) {
+                    this.handleStructure(obj.data, oldObj?.data);
+                }
+            } else if (PluginStateObject.Molecule.Model.is(obj)) {
+                const transform = this.ctx.state.data.tree.transforms.get(ref);
+                if (!transform.transformer.definition.isDecorator) {
+                    this.handleModel(obj.data, oldObj?.data);
+                }
+            }
+        }
+
+        register(): void {
+            this.ctx.customModelProperties.register(Model.AsymIdOffset, true);
+            this.ctx.customModelProperties.register(Model.Index, true);
+            this.ctx.customStructureProperties.register(Structure.Index, true);
+
+            this.subscribeObservable(this.ctx.state.data.events.object.created, o => {
+                this.handle(o.ref, o.obj);
+            });
+
+            this.subscribeObservable(this.ctx.state.data.events.object.updated, o => {
+                this.handle(o.ref, o.obj, o.oldObj);
+            });
+        }
+
+        unregister() {
+            this.ctx.customModelProperties.unregister(Model.AsymIdOffset.descriptor.name);
+            this.ctx.customModelProperties.unregister(Model.Index.descriptor.name);
+            this.ctx.customStructureProperties.unregister(Structure.Index.descriptor.name);
+        }
+    }
+});

+ 1 - 0
src/mol-plugin/index.ts

@@ -74,6 +74,7 @@ export const DefaultPluginSpec: PluginSpec = {
         PluginSpec.Behavior(PluginBehaviors.Camera.FocusLoci),
         PluginSpec.Behavior(StructureFocusRepresentation),
 
+        PluginSpec.Behavior(PluginBehaviors.CustomProps.StructureInfo),
         PluginSpec.Behavior(PluginBehaviors.CustomProps.AccessibleSurfaceArea),
         PluginSpec.Behavior(PluginBehaviors.CustomProps.Interactions),
         PluginSpec.Behavior(PluginBehaviors.CustomProps.SecondaryStructure),