diff --git a/src/mol-plugin/ui/structure/representation.tsx b/src/mol-plugin/ui/structure/representation.tsx index a7ddbea462fdb632ea14ea92b3e28ea8562a378c..ba82755cbdf17a7e19a3483a4a26b21f95cb0f37 100644 --- a/src/mol-plugin/ui/structure/representation.tsx +++ b/src/mol-plugin/ui/structure/representation.tsx @@ -13,6 +13,8 @@ import { Color } from '../../../mol-util/color'; import { ButtonSelect, Options } from '../controls/common' import { ParamDefinition as PD } from '../../../mol-util/param-definition'; import { VisualQuality, VisualQualityOptions } from '../../../mol-geo/geometry/base'; +import { StructureRepresentationPresets as P } from '../../util/structure-representation-helper'; +import { camelCaseToWords } from '../../../mol-util/string'; abstract class BaseStructureRepresentationControls extends PluginUIComponent { onChange = (value: string) => { @@ -49,25 +51,19 @@ abstract class BaseStructureRepresentationControls extends PluginUIComponent { <span title={this.label}>{this.label}</span> <div className='msp-select-row'> <ButtonSelect label='Show' onChange={this.show}> - <optgroup label='Show'> - {Options(types)} - </optgroup> + <optgroup label='Show'>{Options(types)}</optgroup> </ButtonSelect> <ButtonSelect label='Hide' onChange={this.hide}> <optgroup label='Clear'> <option key={'__all__'} value={'__all__'}>All</option> </optgroup> - <optgroup label='Hide'> - {Options(types)} - </optgroup> + <optgroup label='Hide'>{Options(types)}</optgroup> </ButtonSelect> <ButtonSelect label='Color' onChange={this.color}> <optgroup label='Clear'> <option key={-1} value={-1}>Theme</option> </optgroup> - <optgroup label='Color'> - {ColorOptions()} - </optgroup> + <optgroup label='Color'>{ColorOptions()}</optgroup> </ButtonSelect> </div> </div> @@ -90,8 +86,11 @@ class SelectionStructureRepresentationControls extends BaseStructureRepresentati } export class StructureRepresentationControls extends PluginUIComponent { - preset = async () => { - await this.plugin.helpers.structureRepresentation.preset() + preset = async (value: string) => { + const presetFn = P[value as keyof typeof P] + if (presetFn) { + await presetFn(this.plugin.helpers.structureRepresentation) + } } onChange = async (p: { param: PD.Base<any>, name: string, value: any }) => { @@ -123,12 +122,20 @@ export class StructureRepresentationControls extends PluginUIComponent { } render() { + const presets = Object.keys(P).map(name => { + return [name, camelCaseToWords(name)] as [string, string] + }) + return <div className='msp-transform-wrapper'> <div className='msp-transform-header'> <button className='msp-btn msp-btn-block'>Representation</button> </div> - <div className='msp-btn-row-group'> - <button className='msp-btn msp-btn-block msp-form-control' onClick={() => this.preset()}>Preset</button> + <div className='msp-control-row'> + <div className='msp-select-row'> + <ButtonSelect label='Preset' onChange={this.preset}> + <optgroup label='Preset'>{Options(presets)}</optgroup> + </ButtonSelect> + </div> </div> <EverythingStructureRepresentationControls /> <SelectionStructureRepresentationControls /> diff --git a/src/mol-plugin/ui/structure/selection.tsx b/src/mol-plugin/ui/structure/selection.tsx index 10adf56a4ff2f63a73cc7a9d5947d577b435c052..36ac44600da2ef4f57bb16736c9f8a587c35bfea 100644 --- a/src/mol-plugin/ui/structure/selection.tsx +++ b/src/mol-plugin/ui/structure/selection.tsx @@ -15,8 +15,6 @@ import { Interactivity } from '../../util/interactivity'; import { ParameterControls } from '../controls/parameters'; import { camelCaseToWords } from '../../../mol-util/string'; -type SelectionModifier = 'add' | 'remove' | 'only' - const StructureSelectionParams = { granularity: Interactivity.Params.granularity, } diff --git a/src/mol-plugin/util/structure-representation-helper.ts b/src/mol-plugin/util/structure-representation-helper.ts index c2b389e75ae620508390abf7832669a9db3eb8a9..3f73b7d75b3d7b64859c1e439c77cff58f7eaf6b 100644 --- a/src/mol-plugin/util/structure-representation-helper.ts +++ b/src/mol-plugin/util/structure-representation-helper.ts @@ -49,7 +49,7 @@ export class StructureRepresentationHelper { return selections.length > 0 ? selections[0] : undefined } - private async _set(modifier: SelectionModifier, type: string, loci: StructureElement.Loci, structure: StructureTransform) { + private async _set(modifier: SelectionModifier, type: string, loci: StructureElement.Loci, structure: StructureTransform, props = {}) { const state = this.plugin.state.dataState const update = state.build() const s = structure.obj!.data @@ -69,6 +69,7 @@ export class StructureRepresentationHelper { const params = StructureRepresentation3DHelpers.getDefaultParams(this.plugin, type as any, s) const p = params.type.params + Object.assign(p, props) if (p.ignoreHydrogens !== undefined) p.ignoreHydrogens = this._ignoreHydrogens if (p.quality !== undefined) p.quality = this._quality @@ -84,23 +85,23 @@ export class StructureRepresentationHelper { await this.plugin.runTask(state.updateTree(update, { doNotUpdateCurrent: true })) } - async set(modifier: SelectionModifier, type: string, lociGetter: (structure: Structure) => StructureElement.Loci) { + async set(modifier: SelectionModifier, type: string, lociGetter: (structure: Structure) => StructureElement.Loci, props = {}) { const state = this.plugin.state.dataState; const structures = state.select(StateSelection.Generators.rootsOfType(PSO.Molecule.Structure)) for (const structure of structures) { const s = structure.obj!.data const loci = lociGetter(s) - await this._set(modifier, type, loci, structure) + await this._set(modifier, type, loci, structure, props) } } - async setFromExpression(modifier: SelectionModifier, type: string, expression: Expression) { + async setFromExpression(modifier: SelectionModifier, type: string, expression: Expression, props = {}) { return this.set(modifier, type, (structure) => { const compiled = compile<StructureSelection>(expression) const result = compiled(new QueryContext(structure)) return StructureSelection.toLoci2(result) - }) + }, props) } async clear() { @@ -169,16 +170,72 @@ export class StructureRepresentationHelper { } async preset() { - // TODO generalize and make configurable - await this.clear() - await this.setFromExpression('add', 'cartoon', Q.all) - await this.setFromExpression('add', 'carbohydrate', Q.all) - await this.setFromExpression('add', 'ball-and-stick', MS.struct.modifier.union([ - MS.struct.combinator.merge([ Q.ligandsPlusConnected, Q.branchedConnectedOnly, Q.water ]) - ])) + // TODO option to limit to specific structure + const state = this.plugin.state.dataState; + const structures = state.select(StateSelection.Generators.rootsOfType(PSO.Molecule.Structure)) + + if (structures.length === 0) return + const s = structures[0].obj!.data + + if (s.elementCount < 50000) { + polymerAndLigand(this) + } else if (s.elementCount < 200000) { + proteinAndNucleic(this) + } else { + if (s.unitSymmetryGroups[0].units.length > 10) { + capsid(this) + } else { + coarseCapsid(this) + } + } } constructor(private plugin: PluginContext) { } +} + +// + +async function polymerAndLigand(r: StructureRepresentationHelper) { + await r.clear() + await r.setFromExpression('add', 'cartoon', Q.all) + await r.setFromExpression('add', 'carbohydrate', Q.all) + await r.setFromExpression('add', 'ball-and-stick', MS.struct.modifier.union([ + MS.struct.combinator.merge([ Q.ligandPlusConnected, Q.branchedConnectedOnly, Q.water ]) + ])) +} + +async function proteinAndNucleic(r: StructureRepresentationHelper) { + await r.clear() + await r.setFromExpression('add', 'cartoon', Q.protein) + await r.setFromExpression('add', 'gaussian-surface', Q.nucleic) + + await r.setFromExpression('add', 'carbohydrate', Q.all) + await r.setFromExpression('add', 'ball-and-stick', MS.struct.modifier.union([ + MS.struct.combinator.merge([ Q.ligandPlusConnected, Q.branchedConnectedOnly, Q.water ]) + ])) +} + +async function capsid(r: StructureRepresentationHelper) { + await r.clear() + await r.setFromExpression('add', 'gaussian-surface', Q.polymer, { + smoothness: 0.5, + }) +} + +async function coarseCapsid(r: StructureRepresentationHelper) { + await r.clear() + await r.setFromExpression('add', 'gaussian-surface', Q.trace, { + radiusOffset: 1, + smoothness: 0.5, + visuals: ['structure-gaussian-surface-mesh'] + }) +} + +export const StructureRepresentationPresets = { + polymerAndLigand, + proteinAndNucleic, + capsid, + coarseCapsid } \ No newline at end of file diff --git a/src/mol-plugin/util/structure-selection-helper.ts b/src/mol-plugin/util/structure-selection-helper.ts index c474561bd3a0c508c5461bcb6d3ab0281e0c2df3..49ca344c4e77f4e2f1f6a35b1c789ad3b9be47c8 100644 --- a/src/mol-plugin/util/structure-selection-helper.ts +++ b/src/mol-plugin/util/structure-selection-helper.ts @@ -15,17 +15,52 @@ import Expression from '../../mol-script/language/expression'; const all = MS.struct.generator.all() -const polymers = MS.struct.modifier.union([ +const polymer = MS.struct.modifier.union([ MS.struct.generator.atomGroups({ 'entity-test': MS.core.rel.eq([MS.ammp('entityType'), 'polymer']) }) ]) -const backboneTrace = MS.struct.modifier.union([ +const trace = MS.struct.modifier.union([ + MS.struct.combinator.merge([ + MS.struct.modifier.union([ + MS.struct.generator.atomGroups({ + 'entity-test': MS.core.rel.eq([MS.ammp('entityType'), 'polymer']), + 'chain-test': MS.core.set.has([ + MS.set('sphere', 'gaussian'), MS.ammp('objectPrimitive') + ]) + }) + ]), + MS.struct.modifier.union([ + MS.struct.generator.atomGroups({ + 'entity-test': MS.core.rel.eq([MS.ammp('entityType'), 'polymer']), + 'chain-test': MS.core.rel.eq([MS.ammp('objectPrimitive'), 'atomistic']), + 'atom-test': MS.core.set.has([MS.set('CA', 'P'), MS.ammp('label_atom_id')]) + }) + ]) + ]) +]) + +const protein = MS.struct.modifier.union([ + MS.struct.generator.atomGroups({ + 'entity-test': MS.core.logic.and([ + MS.core.rel.eq([MS.ammp('entityType'), 'polymer']), + MS.core.str.match([ + MS.re('(polypeptide|cyclic-pseudo-peptide)', 'i'), + MS.ammp('entitySubtype') + ]) + ]) + }) +]) + +const nucleic = MS.struct.modifier.union([ MS.struct.generator.atomGroups({ - 'atom-test': MS.core.logic.or([ - MS.core.rel.eq([MS.ammp('label_atom_id'), 'CA']), - MS.core.rel.eq([MS.ammp('label_atom_id'), 'P']) + 'entity-test': MS.core.logic.and([ + MS.core.rel.eq([MS.ammp('entityType'), 'polymer']), + MS.core.str.match([ + MS.re('(nucleotide|peptide nucleic acid)', 'i'), + MS.ammp('entitySubtype') + ]) ]) }) ]) @@ -37,19 +72,18 @@ const water = MS.struct.modifier.union([ ]) const branched = MS.struct.modifier.union([ - MS.struct.combinator.merge([ - MS.struct.modifier.union([ - MS.struct.generator.atomGroups({ - 'entity-test': MS.core.rel.eq([MS.ammp('entityType'), 'branched']) - }) - ]), - MS.struct.modifier.union([ - MS.struct.generator.atomGroups({ - 'entity-test': MS.core.rel.eq([MS.ammp('entityType'), 'non-polymer']), - 'residue-test': MS.core.str.match([MS.re('saccharide', 'i'), MS.ammp('chemCompType')]) - }) + MS.struct.generator.atomGroups({ + 'entity-test': MS.core.logic.or([ + MS.core.rel.eq([MS.ammp('entityType'), 'branched']), + MS.core.logic.and([ + MS.core.rel.eq([MS.ammp('entityType'), 'non-polymer']), + MS.core.str.match([ + MS.re('oligosaccharide', 'i'), + MS.ammp('entitySubtype') + ]) + ]) ]) - ]) + }) ]) const branchedPlusConnected = MS.struct.modifier.union([ @@ -65,21 +99,22 @@ const branchedConnectedOnly = MS.struct.modifier.union([ }) ]) -const ligands = MS.struct.modifier.union([ +const ligand = MS.struct.modifier.union([ MS.struct.generator.atomGroups({ 'entity-test': MS.core.logic.and([ MS.core.rel.neq([MS.ammp('entityType'), 'branched']), MS.core.rel.eq([MS.ammp('entityType'), 'non-polymer']) ]), + 'chain-test': MS.core.rel.eq([MS.ammp('objectPrimitive'), 'atomistic']), 'residue-test': MS.core.logic.not([ MS.core.str.match([MS.re('saccharide', 'i'), MS.ammp('chemCompType')]) ]) }) ]) -const ligandsPlusConnected = MS.struct.modifier.union([ +const ligandPlusConnected = MS.struct.modifier.union([ MS.struct.modifier.includeConnected({ - 0: ligands, 'layer-count': 1, 'as-whole-residues': true + 0: ligand, 'layer-count': 1, 'as-whole-residues': true }) ]) @@ -93,20 +128,22 @@ const coarse = MS.struct.modifier.union([ export const StructureSelectionQueries = { all, - polymers, - backboneTrace, + polymer, + trace, + protein, + nucleic, water, branched, branchedPlusConnected, branchedConnectedOnly, - ligands, - ligandsPlusConnected, + ligand, + ligandPlusConnected, coarse, } // -type SelectionModifier = 'add' | 'remove' | 'only' +export type SelectionModifier = 'add' | 'remove' | 'only' export class StructureSelectionHelper { private get structures() {