structure-selection-helper.ts 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550
  1. /**
  2. * Copyright (c) 2019-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
  3. *
  4. * @author Alexander Rose <alexander.rose@weirdbyte.de>
  5. * @author David Sehnal <david.sehnal@gmail.com>
  6. */
  7. import { MolScriptBuilder as MS } from '../../mol-script/language/builder';
  8. import { StateSelection, StateBuilder } from '../../mol-state';
  9. import { PluginStateObject } from '../../mol-plugin-state/objects';
  10. import { QueryContext, StructureSelection, StructureQuery, StructureElement, Structure } from '../../mol-model/structure';
  11. import { compile } from '../../mol-script/runtime/query/compiler';
  12. import { Loci } from '../../mol-model/loci';
  13. import { PluginContext } from '../context';
  14. import Expression from '../../mol-script/language/expression';
  15. import { BondType, ProteinBackboneAtoms, NucleicBackboneAtoms, SecondaryStructureType } from '../../mol-model/structure/model/types';
  16. import { StateTransforms } from '../../mol-plugin-state/transforms';
  17. import { SetUtils } from '../../mol-util/set';
  18. import { ValidationReport, ValidationReportProvider } from '../../mol-model-props/rcsb/validation-report';
  19. import { CustomProperty } from '../../mol-model-props/common/custom-property';
  20. import { Task } from '../../mol-task';
  21. import { AccessibleSurfaceAreaSymbols, AccessibleSurfaceAreaProvider } from '../../mol-model-props/computed/accessible-surface-area';
  22. import { stringToWords } from '../../mol-util/string';
  23. export enum StructureSelectionCategory {
  24. Type = 'Type',
  25. Structure = 'Structure Property',
  26. Atom = 'Atom Property',
  27. Bond = 'Bond Property',
  28. Residue = 'Residue Property',
  29. AminoAcid = 'Amino Acid',
  30. NucleicBase = 'Nucleic Base',
  31. Manipulate = 'Manipulate Selection',
  32. Validation = 'Validation',
  33. Misc = 'Miscellaneous',
  34. Internal = 'Internal',
  35. }
  36. export { StructureSelectionQuery }
  37. interface StructureSelectionQuery {
  38. readonly label: string
  39. readonly expression: Expression
  40. readonly description: string
  41. readonly category: string
  42. readonly isHidden: boolean
  43. readonly query: StructureQuery
  44. readonly ensureCustomProperties?: (ctx: CustomProperty.Context, structure: Structure) => Promise<void>
  45. }
  46. interface StructureSelectionQueryProps {
  47. description?: string,
  48. category?: string
  49. isHidden?: boolean
  50. ensureCustomProperties?: (ctx: CustomProperty.Context, structure: Structure) => Promise<void>
  51. }
  52. function StructureSelectionQuery(label: string, expression: Expression, props: StructureSelectionQueryProps = {}): StructureSelectionQuery {
  53. let _query: StructureQuery
  54. return {
  55. label,
  56. expression,
  57. description: props.description || '',
  58. category: props.category ?? StructureSelectionCategory.Misc,
  59. isHidden: !!props.isHidden,
  60. get query() {
  61. if (!_query) _query = compile<StructureSelection>(expression)
  62. return _query
  63. },
  64. ensureCustomProperties: props.ensureCustomProperties
  65. }
  66. }
  67. const all = StructureSelectionQuery('All', MS.struct.generator.all(), { category: '' })
  68. const polymer = StructureSelectionQuery('Polymer', MS.struct.modifier.union([
  69. MS.struct.generator.atomGroups({
  70. 'entity-test': MS.core.logic.and([
  71. MS.core.rel.eq([MS.ammp('entityType'), 'polymer']),
  72. MS.core.str.match([
  73. MS.re('(polypeptide|cyclic-pseudo-peptide|nucleotide|peptide nucleic acid)', 'i'),
  74. MS.ammp('entitySubtype')
  75. ])
  76. ])
  77. })
  78. ]), { category: StructureSelectionCategory.Type })
  79. const trace = StructureSelectionQuery('Trace', MS.struct.modifier.union([
  80. MS.struct.combinator.merge([
  81. MS.struct.modifier.union([
  82. MS.struct.generator.atomGroups({
  83. 'entity-test': MS.core.rel.eq([MS.ammp('entityType'), 'polymer']),
  84. 'chain-test': MS.core.set.has([
  85. MS.set('sphere', 'gaussian'), MS.ammp('objectPrimitive')
  86. ])
  87. })
  88. ]),
  89. MS.struct.modifier.union([
  90. MS.struct.generator.atomGroups({
  91. 'entity-test': MS.core.rel.eq([MS.ammp('entityType'), 'polymer']),
  92. 'chain-test': MS.core.rel.eq([MS.ammp('objectPrimitive'), 'atomistic']),
  93. 'atom-test': MS.core.set.has([MS.set('CA', 'P'), MS.ammp('label_atom_id')])
  94. })
  95. ])
  96. ])
  97. ]), { category: StructureSelectionCategory.Structure })
  98. // TODO maybe pre-calculate atom properties like backbone/sidechain
  99. const backbone = StructureSelectionQuery('Backbone', MS.struct.modifier.union([
  100. MS.struct.combinator.merge([
  101. MS.struct.modifier.union([
  102. MS.struct.generator.atomGroups({
  103. 'entity-test': MS.core.logic.and([
  104. MS.core.rel.eq([MS.ammp('entityType'), 'polymer']),
  105. MS.core.str.match([
  106. MS.re('(polypeptide|cyclic-pseudo-peptide)', 'i'),
  107. MS.ammp('entitySubtype')
  108. ])
  109. ]),
  110. 'chain-test': MS.core.rel.eq([MS.ammp('objectPrimitive'), 'atomistic']),
  111. 'atom-test': MS.core.set.has([MS.set(...SetUtils.toArray(ProteinBackboneAtoms)), MS.ammp('label_atom_id')])
  112. })
  113. ]),
  114. MS.struct.modifier.union([
  115. MS.struct.generator.atomGroups({
  116. 'entity-test': MS.core.logic.and([
  117. MS.core.rel.eq([MS.ammp('entityType'), 'polymer']),
  118. MS.core.str.match([
  119. MS.re('(nucleotide|peptide nucleic acid)', 'i'),
  120. MS.ammp('entitySubtype')
  121. ])
  122. ]),
  123. 'chain-test': MS.core.rel.eq([MS.ammp('objectPrimitive'), 'atomistic']),
  124. 'atom-test': MS.core.set.has([MS.set(...SetUtils.toArray(NucleicBackboneAtoms)), MS.ammp('label_atom_id')])
  125. })
  126. ])
  127. ])
  128. ]), { category: StructureSelectionCategory.Structure })
  129. const protein = StructureSelectionQuery('Protein', MS.struct.modifier.union([
  130. MS.struct.generator.atomGroups({
  131. 'entity-test': MS.core.logic.and([
  132. MS.core.rel.eq([MS.ammp('entityType'), 'polymer']),
  133. MS.core.str.match([
  134. MS.re('(polypeptide|cyclic-pseudo-peptide)', 'i'),
  135. MS.ammp('entitySubtype')
  136. ])
  137. ])
  138. })
  139. ]), { category: StructureSelectionCategory.Type })
  140. const nucleic = StructureSelectionQuery('Nucleic', MS.struct.modifier.union([
  141. MS.struct.generator.atomGroups({
  142. 'entity-test': MS.core.logic.and([
  143. MS.core.rel.eq([MS.ammp('entityType'), 'polymer']),
  144. MS.core.str.match([
  145. MS.re('(nucleotide|peptide nucleic acid)', 'i'),
  146. MS.ammp('entitySubtype')
  147. ])
  148. ])
  149. })
  150. ]), { category: StructureSelectionCategory.Type })
  151. const helix = StructureSelectionQuery('Helix', MS.struct.modifier.union([
  152. MS.struct.generator.atomGroups({
  153. 'entity-test': MS.core.logic.and([
  154. MS.core.rel.eq([MS.ammp('entityType'), 'polymer']),
  155. MS.core.str.match([
  156. MS.re('(polypeptide|cyclic-pseudo-peptide)', 'i'),
  157. MS.ammp('entitySubtype')
  158. ])
  159. ]),
  160. 'residue-test': MS.core.flags.hasAny([
  161. MS.ammp('secondaryStructureFlags'),
  162. MS.core.type.bitflags([SecondaryStructureType.Flag.Helix])
  163. ])
  164. })
  165. ]), { category: StructureSelectionCategory.Residue })
  166. const beta = StructureSelectionQuery('Beta Strand/Sheet', MS.struct.modifier.union([
  167. MS.struct.generator.atomGroups({
  168. 'entity-test': MS.core.logic.and([
  169. MS.core.rel.eq([MS.ammp('entityType'), 'polymer']),
  170. MS.core.str.match([
  171. MS.re('(polypeptide|cyclic-pseudo-peptide)', 'i'),
  172. MS.ammp('entitySubtype')
  173. ])
  174. ]),
  175. 'residue-test': MS.core.flags.hasAny([
  176. MS.ammp('secondaryStructureFlags'),
  177. MS.core.type.bitflags([SecondaryStructureType.Flag.Beta])
  178. ])
  179. })
  180. ]), { category: StructureSelectionCategory.Residue })
  181. const water = StructureSelectionQuery('Water', MS.struct.modifier.union([
  182. MS.struct.generator.atomGroups({
  183. 'entity-test': MS.core.rel.eq([MS.ammp('entityType'), 'water'])
  184. })
  185. ]), { category: StructureSelectionCategory.Type })
  186. const branched = StructureSelectionQuery('Carbohydrate', MS.struct.modifier.union([
  187. MS.struct.generator.atomGroups({
  188. 'entity-test': MS.core.logic.or([
  189. MS.core.rel.eq([MS.ammp('entityType'), 'branched']),
  190. MS.core.logic.and([
  191. MS.core.rel.eq([MS.ammp('entityType'), 'non-polymer']),
  192. MS.core.str.match([
  193. MS.re('oligosaccharide', 'i'),
  194. MS.ammp('entitySubtype')
  195. ])
  196. ])
  197. ])
  198. })
  199. ]), { category: StructureSelectionCategory.Type })
  200. const branchedPlusConnected = StructureSelectionQuery('Carbohydrate with Connected', MS.struct.modifier.union([
  201. MS.struct.modifier.includeConnected({
  202. 0: branched.expression, 'layer-count': 1, 'as-whole-residues': true
  203. })
  204. ]), { category: StructureSelectionCategory.Internal })
  205. const branchedConnectedOnly = StructureSelectionQuery('Connected to Carbohydrate', MS.struct.modifier.union([
  206. MS.struct.modifier.exceptBy({
  207. 0: branchedPlusConnected.expression,
  208. by: branched.expression
  209. })
  210. ]), { category: StructureSelectionCategory.Internal })
  211. const ligand = StructureSelectionQuery('Ligand', MS.struct.modifier.union([
  212. MS.struct.combinator.merge([
  213. MS.struct.modifier.union([
  214. MS.struct.generator.atomGroups({
  215. 'entity-test': MS.core.logic.and([
  216. MS.core.rel.eq([MS.ammp('entityType'), 'non-polymer']),
  217. MS.core.logic.not([MS.core.str.match([
  218. MS.re('oligosaccharide', 'i'),
  219. MS.ammp('entitySubtype')
  220. ])])
  221. ]),
  222. 'chain-test': MS.core.rel.eq([MS.ammp('objectPrimitive'), 'atomistic']),
  223. 'residue-test': MS.core.logic.not([
  224. MS.core.str.match([MS.re('saccharide', 'i'), MS.ammp('chemCompType')])
  225. ])
  226. })
  227. ]),
  228. // this is to get non-polymer and peptide terminus components in polymer entities,
  229. // - non-polymer, e.g. PXZ in 4HIV or generally ACE
  230. // - carboxy terminus, e.g. FC0 in 4BP9, or ETA in 6DDE
  231. // - amino terminus, e.g. ARF in 3K4V, or 4MM in 3EGV
  232. MS.struct.modifier.union([
  233. MS.struct.generator.atomGroups({
  234. 'entity-test': MS.core.rel.eq([MS.ammp('entityType'), 'polymer']),
  235. 'chain-test': MS.core.rel.eq([MS.ammp('objectPrimitive'), 'atomistic']),
  236. 'residue-test': MS.core.str.match([
  237. MS.re('non-polymer|(amino|carboxy) terminus', 'i'),
  238. MS.ammp('chemCompType')
  239. ])
  240. })
  241. ])
  242. ]),
  243. ]), { category: StructureSelectionCategory.Type })
  244. // don't include branched entities as they have their own link representation
  245. const ligandPlusConnected = StructureSelectionQuery('Ligand with Connected', MS.struct.modifier.union([
  246. MS.struct.modifier.exceptBy({
  247. 0: MS.struct.modifier.union([
  248. MS.struct.modifier.includeConnected({
  249. 0: ligand.expression,
  250. 'layer-count': 1,
  251. 'as-whole-residues': true,
  252. 'bond-test': MS.core.flags.hasAny([
  253. MS.struct.bondProperty.flags(),
  254. MS.core.type.bitflags([
  255. BondType.Flag.Covalent | BondType.Flag.MetallicCoordination
  256. ])
  257. ])
  258. })
  259. ]),
  260. by: branched.expression
  261. })
  262. ]), { category: StructureSelectionCategory.Internal })
  263. const ligandConnectedOnly = StructureSelectionQuery('Connected to Ligand', MS.struct.modifier.union([
  264. MS.struct.modifier.exceptBy({
  265. 0: ligandPlusConnected.expression,
  266. by: ligand.expression
  267. })
  268. ]), { category: StructureSelectionCategory.Internal })
  269. // residues connected to ligands or branched entities
  270. const connectedOnly = StructureSelectionQuery('Connected to Ligand or Carbohydrate', MS.struct.modifier.union([
  271. MS.struct.combinator.merge([
  272. branchedConnectedOnly.expression,
  273. ligandConnectedOnly.expression
  274. ]),
  275. ]), { category: StructureSelectionCategory.Internal })
  276. const disulfideBridges = StructureSelectionQuery('Disulfide Bridges', MS.struct.modifier.union([
  277. MS.struct.modifier.wholeResidues([
  278. MS.struct.modifier.union([
  279. MS.struct.generator.bondedAtomicPairs({
  280. 0: MS.core.flags.hasAny([
  281. MS.struct.bondProperty.flags(),
  282. MS.core.type.bitflags([BondType.Flag.Disulfide])
  283. ])
  284. })
  285. ])
  286. ])
  287. ]), { category: StructureSelectionCategory.Bond })
  288. const nonStandardPolymer = StructureSelectionQuery('Non-standard Residues in Polymers', MS.struct.modifier.union([
  289. MS.struct.generator.atomGroups({
  290. 'entity-test': MS.core.rel.eq([MS.ammp('entityType'), 'polymer']),
  291. 'chain-test': MS.core.rel.eq([MS.ammp('objectPrimitive'), 'atomistic']),
  292. 'residue-test': MS.ammp('isNonStandard')
  293. })
  294. ]), { category: StructureSelectionCategory.Residue })
  295. const coarse = StructureSelectionQuery('Coarse Elements', MS.struct.modifier.union([
  296. MS.struct.generator.atomGroups({
  297. 'chain-test': MS.core.set.has([
  298. MS.set('sphere', 'gaussian'), MS.ammp('objectPrimitive')
  299. ])
  300. })
  301. ]), { category: StructureSelectionCategory.Type })
  302. const ring = StructureSelectionQuery('Rings in Residues', MS.struct.modifier.union([
  303. MS.struct.generator.rings()
  304. ]), { category: StructureSelectionCategory.Residue })
  305. const aromaticRing = StructureSelectionQuery('Aromatic Rings in Residues', MS.struct.modifier.union([
  306. MS.struct.generator.rings({ 'only-aromatic': true })
  307. ]), { category: StructureSelectionCategory.Residue })
  308. const surroundings = StructureSelectionQuery('Surrounding Residues (5 \u212B) of Selection', MS.struct.modifier.union([
  309. MS.struct.modifier.exceptBy({
  310. 0: MS.struct.modifier.includeSurroundings({
  311. 0: MS.internal.generator.current(),
  312. radius: 5,
  313. 'as-whole-residues': true
  314. }),
  315. by: MS.internal.generator.current()
  316. })
  317. ]), {
  318. description: 'Select residues within 5 \u212B of the current selection.',
  319. category: StructureSelectionCategory.Manipulate
  320. })
  321. const complement = StructureSelectionQuery('Inverse / Complement of Selection', MS.struct.modifier.union([
  322. MS.struct.modifier.exceptBy({
  323. 0: MS.struct.generator.all(),
  324. by: MS.internal.generator.current()
  325. })
  326. ]), {
  327. description: 'Select everything not in the current selection.',
  328. category: StructureSelectionCategory.Manipulate
  329. })
  330. const bonded = StructureSelectionQuery('Residues Bonded to Selection', MS.struct.modifier.union([
  331. MS.struct.modifier.includeConnected({
  332. 0: MS.internal.generator.current(), 'layer-count': 1, 'as-whole-residues': true
  333. })
  334. ]), {
  335. description: 'Select residues covalently bonded to current selection.',
  336. category: StructureSelectionCategory.Manipulate
  337. })
  338. const hasClash = StructureSelectionQuery('Residues with Clashes', MS.struct.modifier.union([
  339. MS.struct.modifier.wholeResidues([
  340. MS.struct.modifier.union([
  341. MS.struct.generator.atomGroups({
  342. 'chain-test': MS.core.rel.eq([MS.ammp('objectPrimitive'), 'atomistic']),
  343. 'atom-test': ValidationReport.symbols.hasClash.symbol(),
  344. })
  345. ])
  346. ])
  347. ]), {
  348. description: 'Select residues with clashes in the wwPDB validation report.',
  349. category: StructureSelectionCategory.Residue,
  350. ensureCustomProperties: (ctx, structure) => {
  351. return ValidationReportProvider.attach(ctx, structure.models[0])
  352. }
  353. })
  354. const isBuried = StructureSelectionQuery('Buried Protein Residues', MS.struct.modifier.union([
  355. MS.struct.modifier.wholeResidues([
  356. MS.struct.modifier.union([
  357. MS.struct.generator.atomGroups({
  358. 'chain-test': MS.core.rel.eq([MS.ammp('objectPrimitive'), 'atomistic']),
  359. 'residue-test': AccessibleSurfaceAreaSymbols.isBuried.symbol(),
  360. })
  361. ])
  362. ])
  363. ]), {
  364. description: 'Select buried protein residues.',
  365. category: StructureSelectionCategory.Residue,
  366. ensureCustomProperties: (ctx, structure) => {
  367. return AccessibleSurfaceAreaProvider.attach(ctx, structure)
  368. }
  369. })
  370. const isAccessible = StructureSelectionQuery('Accessible Protein Residues', MS.struct.modifier.union([
  371. MS.struct.modifier.wholeResidues([
  372. MS.struct.modifier.union([
  373. MS.struct.generator.atomGroups({
  374. 'chain-test': MS.core.rel.eq([MS.ammp('objectPrimitive'), 'atomistic']),
  375. 'residue-test': AccessibleSurfaceAreaSymbols.isAccessible.symbol(),
  376. })
  377. ])
  378. ])
  379. ]), {
  380. description: 'Select accessible protein residues.',
  381. category: StructureSelectionCategory.Residue,
  382. ensureCustomProperties: (ctx, structure) => {
  383. return AccessibleSurfaceAreaProvider.attach(ctx, structure)
  384. }
  385. })
  386. const StandardAminoAcids = [
  387. [['HIS'], 'HISTIDINE'],
  388. [['ARG'], 'ARGININE'],
  389. [['LYS'], 'LYSINE'],
  390. [['ILE'], 'ISOLEUCINE'],
  391. [['PHE'], 'PHENYLALANINE'],
  392. [['LEU'], 'LEUCINE'],
  393. [['TRP'], 'TRYPTOPHAN'],
  394. [['ALA'], 'ALANINE'],
  395. [['MET'], 'METHIONINE'],
  396. [['CYS'], 'CYSTEINE'],
  397. [['ASN'], 'ASPARAGINE'],
  398. [['VAL'], 'VALINE'],
  399. [['GLY'], 'GLYCINE'],
  400. [['SER'], 'SERINE'],
  401. [['GLN'], 'GLUTAMINE'],
  402. [['TYR'], 'TYROSINE'],
  403. [['ASP'], 'ASPARTIC ACID'],
  404. [['GLU'], 'GLUTAMIC ACID'],
  405. [['THR'], 'THREONINE'],
  406. [['SEC'], 'SELENOCYSTEINE'],
  407. [['PYL'], 'PYRROLYSINE'],
  408. ].sort((a, b) => a[1] < b[1] ? -1 : a[1] > b[1] ? 1 : 0) as [string[], string][]
  409. const StandardNucleicBases = [
  410. [['A', 'DA'], 'ADENOSINE'],
  411. [['C', 'DC'], 'CYTIDINE'],
  412. [['T', 'DT'], 'THYMIDINE'],
  413. [['G', 'DG'], 'GUANOSINE'],
  414. [['I', 'DI'], 'INOSINE'],
  415. [['U', 'DU'], 'URIDINE'],
  416. ].sort((a, b) => a[1] < b[1] ? -1 : a[1] > b[1] ? 1 : 0) as [string[], string][]
  417. function ResidueQuery([names, label]: [string[], string], category: string) {
  418. return StructureSelectionQuery(`${stringToWords(label)} (${names.join(', ')})`, MS.struct.modifier.union([
  419. MS.struct.generator.atomGroups({
  420. 'residue-test': MS.core.set.has([MS.set(...names), MS.ammp('auth_comp_id')])
  421. })
  422. ]), { category })
  423. }
  424. export const StructureSelectionQueries = {
  425. all,
  426. polymer,
  427. trace,
  428. backbone,
  429. protein,
  430. nucleic,
  431. helix,
  432. beta,
  433. water,
  434. branched,
  435. branchedPlusConnected,
  436. branchedConnectedOnly,
  437. ligand,
  438. ligandPlusConnected,
  439. ligandConnectedOnly,
  440. connectedOnly,
  441. disulfideBridges,
  442. nonStandardPolymer,
  443. coarse,
  444. ring,
  445. aromaticRing,
  446. surroundings,
  447. complement,
  448. bonded,
  449. hasClash,
  450. isBuried,
  451. isAccessible
  452. }
  453. export const StructureSelectionQueryList = [
  454. ...Object.values(StructureSelectionQueries),
  455. ...StandardAminoAcids.map(v => ResidueQuery(v, StructureSelectionCategory.AminoAcid)),
  456. ...StandardNucleicBases.map(v => ResidueQuery(v, StructureSelectionCategory.NucleicBase)),
  457. ]
  458. export function applyBuiltInSelection(to: StateBuilder.To<PluginStateObject.Molecule.Structure>, query: keyof typeof StructureSelectionQueries, customTag?: string) {
  459. return to.apply(StateTransforms.Model.StructureSelectionFromExpression,
  460. { expression: StructureSelectionQueries[query].expression, label: StructureSelectionQueries[query].label },
  461. { tags: customTag ? [query, customTag] : [query] });
  462. }
  463. //
  464. export type SelectionModifier = 'add' | 'remove' | 'only'
  465. export class StructureSelectionHelper {
  466. private get structures() {
  467. return this.plugin.state.dataState.select(StateSelection.Generators.rootsOfType(PluginStateObject.Molecule.Structure)).map(s => s.obj!.data)
  468. }
  469. private _set(modifier: SelectionModifier, loci: Loci, applyGranularity = true) {
  470. switch (modifier) {
  471. case 'add':
  472. this.plugin.interactivity.lociSelects.select({ loci }, applyGranularity)
  473. break
  474. case 'remove':
  475. this.plugin.interactivity.lociSelects.deselect({ loci }, applyGranularity)
  476. break
  477. case 'only':
  478. this.plugin.interactivity.lociSelects.selectOnly({ loci }, applyGranularity)
  479. break
  480. }
  481. }
  482. async set(modifier: SelectionModifier, selectionQuery: StructureSelectionQuery, applyGranularity = true) {
  483. this.plugin.runTask(Task.create('Structure Selection', async runtime => {
  484. const ctx = { fetch: this.plugin.fetch, runtime }
  485. for (const s of this.structures) {
  486. const current = this.plugin.helpers.structureSelectionManager.get(s)
  487. const currentSelection = Loci.isEmpty(current)
  488. ? StructureSelection.Empty(s)
  489. : StructureSelection.Singletons(s, StructureElement.Loci.toStructure(current))
  490. if (selectionQuery.ensureCustomProperties) {
  491. await selectionQuery.ensureCustomProperties(ctx, s)
  492. }
  493. const result = selectionQuery.query(new QueryContext(s, { currentSelection }))
  494. const loci = StructureSelection.toLociWithSourceUnits(result)
  495. this._set(modifier, loci, applyGranularity)
  496. }
  497. }))
  498. }
  499. constructor(private plugin: PluginContext) {
  500. }
  501. }