Browse Source

structure selection query improvements

- moved out of selection.tsx
- added queries for entities (based on entity description)
Alexander Rose 5 years ago
parent
commit
95d3ef491f

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

@@ -429,7 +429,8 @@ function getUniqueElementSymbols(s: Structure) {
     const prop = StructureProperties.atom.type_symbol;
     const symbols = new Set<ElementSymbol>();
     const loc = StructureElement.Location.create(s);
-    for (const unit of s.units) {
+    for (const unitGroup of s.unitSymmetryGroups) {
+        const unit = unitGroup.units[0];
         if (!Unit.isAtomic(unit)) continue;
         loc.unit = unit;
         for (let i = 0, il = unit.elements.length; i < il; ++i) {

+ 1 - 1
src/mol-plugin-state/builder/structure/representation-preset.ts

@@ -105,7 +105,7 @@ const polymerAndLigand = StructureRepresentationPresetProvider({
             polymer: await presetStaticComponent(plugin, structureCell, 'polymer'),
             ligand: await presetStaticComponent(plugin, structureCell, 'ligand'),
             nonStandard: await presetStaticComponent(plugin, structureCell, 'non-standard'),
-            branched: await presetStaticComponent(plugin, structureCell, 'branched'),
+            branched: await presetStaticComponent(plugin, structureCell, 'branched', { label: 'Carbohydrate' }),
             water: await presetStaticComponent(plugin, structureCell, 'water'),
             coarse: await presetStaticComponent(plugin, structureCell, 'coarse')
         };

+ 89 - 16
src/mol-plugin-state/helpers/structure-selection-query.ts

@@ -6,8 +6,8 @@
  */
 
 import { CustomProperty } from '../../mol-model-props/common/custom-property';
-import { QueryContext, Structure, StructureQuery, StructureSelection } from '../../mol-model/structure';
-import { BondType, NucleicBackboneAtoms, ProteinBackboneAtoms, SecondaryStructureType } from '../../mol-model/structure/model/types';
+import { QueryContext, Structure, StructureQuery, StructureSelection, StructureProperties, StructureElement } from '../../mol-model/structure';
+import { BondType, NucleicBackboneAtoms, ProteinBackboneAtoms, SecondaryStructureType, AminoAcidNamesL, RnaBaseNames, DnaBaseNames, WaterNames, ElementSymbol } from '../../mol-model/structure/model/types';
 import { PluginContext } from '../../mol-plugin/context';
 import { MolScriptBuilder as MS } from '../../mol-script/language/builder';
 import Expression from '../../mol-script/language/expression';
@@ -15,9 +15,9 @@ import { compile } from '../../mol-script/runtime/query/compiler';
 import { StateBuilder } from '../../mol-state';
 import { RuntimeContext } from '../../mol-task';
 import { SetUtils } from '../../mol-util/set';
-import { stringToWords } from '../../mol-util/string';
 import { PluginStateObject } from '../objects';
 import { StateTransforms } from '../transforms';
+import { ElementNames } from '../../mol-model/structure/model/properties/atomic/types';
 
 export enum StructureSelectionCategory {
     Type = 'Type',
@@ -41,6 +41,7 @@ interface StructureSelectionQuery {
     readonly description: string
     readonly category: string
     readonly isHidden: boolean
+    readonly priority: number
     readonly referencesCurrent: boolean
     readonly query: StructureQuery
     readonly ensureCustomProperties?: (ctx: CustomProperty.Context, structure: Structure) => Promise<void>
@@ -51,6 +52,7 @@ interface StructureSelectionQueryProps {
     description?: string
     category?: string
     isHidden?: boolean
+    priority?: number
     referencesCurrent?: boolean
     ensureCustomProperties?: (ctx: CustomProperty.Context, structure: Structure) => Promise<void>
 }
@@ -63,6 +65,7 @@ function StructureSelectionQuery(label: string, expression: Expression, props: S
         description: props.description || '',
         category: props.category ?? StructureSelectionCategory.Misc,
         isHidden: !!props.isHidden,
+        priority: props.priority || 0,
         referencesCurrent: !!props.referencesCurrent,
         get query() {
             if (!_query) _query = compile<StructureSelection>(expression);
@@ -81,7 +84,7 @@ function StructureSelectionQuery(label: string, expression: Expression, props: S
     };
 }
 
-const all = StructureSelectionQuery('All', MS.struct.generator.all(), { category: '' });
+const all = StructureSelectionQuery('All', MS.struct.generator.all(), { category: '', priority: 1000 });
 const current = StructureSelectionQuery('Current Selection', MS.internal.generator.current(), { category: '', referencesCurrent: true });
 
 const polymer = StructureSelectionQuery('Polymer', MS.struct.modifier.union([
@@ -426,20 +429,96 @@ const StandardNucleicBases = [
     [['N', 'DN'], 'UNKNOWN'],
 ].sort((a, b) => a[1] < b[1] ? -1 : a[1] > b[1] ? 1 : 0) as [string[], string][];
 
-export function ResidueQuery([names, label]: [string[], string], category: string) {
-    return StructureSelectionQuery(`${stringToWords(label)} (${names.join(', ')})`, MS.struct.modifier.union([
+export function ResidueQuery([names, label]: [string[], string], category: string, priority = 0) {
+    return StructureSelectionQuery(`${label} (${names.join(', ')})`, MS.struct.modifier.union([
         MS.struct.generator.atomGroups({
             'residue-test': MS.core.set.has([MS.set(...names), MS.ammp('auth_comp_id')])
         })
-    ]), { category });
+    ]), { category, priority, description: label });
 }
 
-export function ElementSymbolQuery([names, label]: [string[], string], category: string) {
-    return StructureSelectionQuery(`${stringToWords(label)} (${names.join(', ')})`, MS.struct.modifier.union([
+export function ElementSymbolQuery([names, label]: [string[], string], category: string, priority: number) {
+    return StructureSelectionQuery(`${label} (${names.join(', ')})`, MS.struct.modifier.union([
         MS.struct.generator.atomGroups({
             'atom-test': MS.core.set.has([MS.set(...names), MS.acp('elementSymbol')])
         })
-    ]), { category });
+    ]), { category, priority });
+}
+
+export function EntityDescriptionQuery([description, label]: [string[], string], category: string, priority: number) {
+    return StructureSelectionQuery(`${label}`, MS.struct.modifier.union([
+        MS.struct.generator.atomGroups({
+            'entity-test': MS.core.list.equal([MS.list(...description), MS.ammp('entityDescription')])
+        })
+    ]), { category, priority, description: description.join(', ') });
+}
+
+const StandardResidues = SetUtils.unionMany(
+    AminoAcidNamesL, RnaBaseNames, DnaBaseNames, WaterNames
+);
+
+export function getElementQueries(structures: Structure[]) {
+    const uniqueElements = new Set<ElementSymbol>();
+    for (const structure of structures) {
+        structure.uniqueElementSymbols.forEach(e => uniqueElements.add(e));
+    }
+
+    const queries: StructureSelectionQuery[] = [];
+    uniqueElements.forEach(e => {
+        const label = ElementNames[e] || e;
+        queries.push(ElementSymbolQuery([[e], label], 'Element Symbol', 0));
+    });
+    return queries;
+}
+
+export function getNonStandardResidueQueries(structures: Structure[]) {
+    const residueLabels = new Map<string, string>();
+    const uniqueResidues = new Set<string>();
+    for (const structure of structures) {
+        structure.uniqueResidueNames.forEach(r => uniqueResidues.add(r));
+        for (const m of structure.models) {
+            structure.uniqueResidueNames.forEach(r => {
+                const comp = m.properties.chemicalComponentMap.get(r);
+                if (comp) residueLabels.set(r, comp.name);
+            });
+        }
+    }
+
+    const queries: StructureSelectionQuery[] = [];
+    SetUtils.difference(uniqueResidues, StandardResidues).forEach(r => {
+        const label = residueLabels.get(r) || r;
+        queries.push(ResidueQuery([[r], label], 'Ligand/Non-standard Residue', 200));
+    });
+    return queries;
+}
+
+export function getPolymerAndBranchedEntityQueries(structures: Structure[]) {
+    const uniqueEntities = new Map<string, string[]>();
+    const l = StructureElement.Location.create();
+    for (const structure of structures) {
+        l.structure = structure;
+        for (const ug of structure.unitSymmetryGroups) {
+            l.unit = ug.units[0];
+            l.element = ug.elements[0];
+            const entityType = StructureProperties.entity.type(l);
+            if (entityType === 'polymer' || entityType === 'branched') {
+                const description = StructureProperties.entity.pdbx_description(l);
+                uniqueEntities.set(description.join(', '), description);
+            }
+        }
+    }
+
+    const queries: StructureSelectionQuery[] = [];
+    uniqueEntities.forEach((v, k) => {
+        queries.push(EntityDescriptionQuery([v, k], 'Polymer/Carbohydrate Entities', 300));
+    });
+    return queries;
+}
+
+export function applyBuiltInSelection(to: StateBuilder.To<PluginStateObject.Molecule.Structure>, query: keyof typeof StructureSelectionQueries, customTag?: string) {
+    return to.apply(StateTransforms.Model.StructureSelectionFromExpression,
+        { expression: StructureSelectionQueries[query].expression, label: StructureSelectionQueries[query].label },
+        { tags: customTag ? [query, customTag] : [query] });
 }
 
 export const StructureSelectionQueries = {
@@ -471,12 +550,6 @@ export const StructureSelectionQueries = {
     wholeResidues,
 };
 
-export function applyBuiltInSelection(to: StateBuilder.To<PluginStateObject.Molecule.Structure>, query: keyof typeof StructureSelectionQueries, customTag?: string) {
-    return to.apply(StateTransforms.Model.StructureSelectionFromExpression,
-        { expression: StructureSelectionQueries[query].expression, label: StructureSelectionQueries[query].label },
-        { tags: customTag ? [query, customTag] : [query] });
-}
-
 export class StructureSelectionQueryRegistry {
     list: StructureSelectionQuery[] = []
     options: [StructureSelectionQuery, string, string][] = []

+ 20 - 52
src/mol-plugin-ui/structure/selection.tsx

@@ -11,7 +11,7 @@ import Brush from '@material-ui/icons/Brush';
 import Restore from '@material-ui/icons/Restore';
 import Remove from '@material-ui/icons/Remove';
 import * as React from 'react';
-import { StructureSelectionQueries, StructureSelectionQuery, ResidueQuery, ElementSymbolQuery } from '../../mol-plugin-state/helpers/structure-selection-query';
+import { StructureSelectionQueries, StructureSelectionQuery, getNonStandardResidueQueries, getElementQueries, getPolymerAndBranchedEntityQueries } from '../../mol-plugin-state/helpers/structure-selection-query';
 import { InteractivityManager } from '../../mol-plugin-state/manager/interactivity';
 import { StructureComponentManager } from '../../mol-plugin-state/manager/structure/component';
 import { StructureRef, StructureComponentRef } from '../../mol-plugin-state/manager/structure/hierarchy-state';
@@ -25,9 +25,7 @@ import { Button, ControlGroup, IconButton, ToggleButton } from '../controls/comm
 import { ParameterControls, ParamOnChange, PureSelectControl } from '../controls/parameters';
 import { Union, Subtract, Intersect, SetSvg as SetSvg, CubeSvg } from '../controls/icons';
 import { AddComponentControls } from './components';
-import { SetUtils } from '../../mol-util/set';
-import { AminoAcidNamesL, RnaBaseNames, DnaBaseNames, WaterNames, ElementSymbol } from '../../mol-model/structure/model/types';
-import { ElementNames } from '../../mol-model/structure/model/properties/atomic/types';
+import Structure from '../../mol-model/structure/structure/structure';
 
 const StructureSelectionParams = {
     granularity: InteractivityManager.Params.granularity,
@@ -48,10 +46,6 @@ const ActionHeader = new Map<StructureSelectionModifier, string>([
     ['set', 'Set Selection']
 ] as const);
 
-const StandardResidues = SetUtils.unionMany(
-    AminoAcidNamesL, RnaBaseNames, DnaBaseNames, WaterNames
-);
-
 export class StructureSelectionActionsControls extends PluginUIComponent<{}, StructureSelectionActionsControlsState> {
     state = {
         action: void 0 as StructureSelectionActionsControlsState['action'],
@@ -108,45 +102,13 @@ export class StructureSelectionActionsControls extends PluginUIComponent<{}, Str
         }
     }
 
-    get elementQueries () {
-        const uniqueElements = new Set<ElementSymbol>();
-        for (const s of this.plugin.managers.structure.hierarchy.selection.structures) {
-            const structure = s.cell.obj?.data;
-            if (!structure) continue;
-
-            structure.uniqueElementSymbols.forEach(e => uniqueElements.add(e));
-        }
-
-        const queries: StructureSelectionQuery[] = [];
-        uniqueElements.forEach(e => {
-            const label = ElementNames[e] || e;
-            queries.push(ElementSymbolQuery([[e], label], 'Element Symbol'));
-        });
-        return queries;
-    }
-
-    get nonStandardResidueQueries () {
-        const residueLabels = new Map<string, string>();
-        const uniqueResidues = new Set<string>();
+    get structures () {
+        const structures: Structure[] = [];
         for (const s of this.plugin.managers.structure.hierarchy.selection.structures) {
             const structure = s.cell.obj?.data;
-            if (!structure) continue;
-
-            structure.uniqueResidueNames.forEach(r => uniqueResidues.add(r));
-            for (const m of structure.models) {
-                structure.uniqueResidueNames.forEach(r => {
-                    const comp = m.properties.chemicalComponentMap.get(r);
-                    if (comp) residueLabels.set(r, comp.name);
-                });
-            }
+            if (structure) structures.push(structure);
         }
-
-        const queries: StructureSelectionQuery[] = [];
-        SetUtils.difference(uniqueResidues, StandardResidues).forEach(r => {
-            const label = residueLabels.get(r) || r;
-            queries.push(ResidueQuery([[r], label], 'Ligand/Non-standard Residue'));
-        });
-        return queries;
+        return structures;
     }
 
     private queriesItems: ActionMenu.Items[] = []
@@ -154,7 +116,13 @@ export class StructureSelectionActionsControls extends PluginUIComponent<{}, Str
     get queries () {
         const { registry } = this.plugin.query.structure;
         if (registry.version !== this.queriesVersion) {
-            const queries = [...registry.list, ...this.nonStandardResidueQueries, ...this.elementQueries];
+            const structures = this.structures;
+            const queries = [
+                ...registry.list,
+                ...getPolymerAndBranchedEntityQueries(structures),
+                ...getNonStandardResidueQueries(structures),
+                ...getElementQueries(structures)
+            ].sort((a, b) => b.priority - a.priority);
             this.queriesItems = ActionMenu.createItems(queries, {
                 filter: q => q !== StructureSelectionQueries.current && !q.isHidden,
                 label: q => q.label,
@@ -204,29 +172,29 @@ export class StructureSelectionActionsControls extends PluginUIComponent<{}, Str
 
         return <>
             <div className='msp-flex-row' style={{ background: 'none' }}>
-                <PureSelectControl title={`Picking Level`} param={StructureSelectionParams.granularity} name='granularity' value={granularity} onChange={this.setGranuality} isDisabled={this.isDisabled} />
+                <PureSelectControl title={`Picking Level for selecting and highlighting`} param={StructureSelectionParams.granularity} name='granularity' value={granularity} onChange={this.setGranuality} isDisabled={this.isDisabled} />
                 <ToggleButton icon={Union} title={ActionHeader.get('add')} toggle={this.toggleAdd} isSelected={this.state.action === 'add'} disabled={this.isDisabled} />
                 <ToggleButton icon={Subtract} title={ActionHeader.get('remove')} toggle={this.toggleRemove} isSelected={this.state.action === 'remove'} disabled={this.isDisabled} />
                 <ToggleButton icon={Intersect} title={ActionHeader.get('intersect')} toggle={this.toggleIntersect} isSelected={this.state.action === 'intersect'} disabled={this.isDisabled} />
                 <ToggleButton icon={SetSvg} title={ActionHeader.get('set')} toggle={this.toggleSet} isSelected={this.state.action === 'set'} disabled={this.isDisabled} />
 
-                <ToggleButton icon={Brush} title='Color' toggle={this.toggleColor} isSelected={this.state.action === 'color'} disabled={this.isDisabled} style={{ marginLeft: '10px' }}  />
-                <ToggleButton icon={CubeSvg} title='Create Representation' toggle={this.toggleAddRepr} isSelected={this.state.action === 'add-repr'} disabled={this.isDisabled} />
-                <IconButton svg={Remove} title='Subtract from Representations' onClick={this.subtract} disabled={this.isDisabled} />
+                <ToggleButton icon={Brush} title='Color Selection' toggle={this.toggleColor} isSelected={this.state.action === 'color'} disabled={this.isDisabled} style={{ marginLeft: '10px' }}  />
+                <ToggleButton icon={CubeSvg} title='Create Representation of Selection' toggle={this.toggleAddRepr} isSelected={this.state.action === 'add-repr'} disabled={this.isDisabled} />
+                <IconButton svg={Remove} title='Subtract Selection from Representations' onClick={this.subtract} disabled={this.isDisabled} />
                 <IconButton svg={Restore} onClick={this.undo} disabled={!this.state.canUndo || this.isDisabled} title={undoTitle} />
 
                 <IconButton svg={CancelOutlined} title='Turn selection mode off' onClick={this.turnOff} style={{ marginLeft: '10px' }} />
             </div>
             {(this.state.action && this.state.action !== 'color' && this.state.action !== 'add-repr') && <div className='msp-selection-viewport-controls-actions'>
-                <ActionMenu header={ActionHeader.get(this.state.action as StructureSelectionModifier)} items={this.queries} onSelect={this.selectQuery} noOffset />
+                <ActionMenu header={ActionHeader.get(this.state.action as StructureSelectionModifier)} title='Click to close.' items={this.queries} onSelect={this.selectQuery} noOffset />
             </div>}
             {this.state.action === 'color' && <div className='msp-selection-viewport-controls-actions'>
-                <ControlGroup header='Color' initialExpanded={true} hideExpander={true} hideOffset={true} onHeaderClick={this.toggleColor} topRightIcon={Close}>
+                <ControlGroup header='Color' title='Click to close.' initialExpanded={true} hideExpander={true} hideOffset={true} onHeaderClick={this.toggleColor} topRightIcon={Close}>
                     <ApplyColorControls onApply={this.toggleColor} />
                 </ControlGroup>
             </div>}
             {this.state.action === 'add-repr' && <div className='msp-selection-viewport-controls-actions'>
-                <ControlGroup header='Add Representation' initialExpanded={true} hideExpander={true} hideOffset={true} onHeaderClick={this.toggleAddRepr} topRightIcon={Close}>
+                <ControlGroup header='Add Representation' title='Click to close.' initialExpanded={true} hideExpander={true} hideOffset={true} onHeaderClick={this.toggleAddRepr} topRightIcon={Close}>
                     <AddComponentControls onApply={this.toggleAddRepr} forSelection />
                 </ControlGroup>
             </div>}