representation-preset.ts 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387
  1. /**
  2. * Copyright (c) 2019-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
  3. *
  4. * @author David Sehnal <david.sehnal@gmail.com>
  5. * @author Alexander Rose <alexander.rose@weirdbyte.de>
  6. */
  7. import { PresetProvider } from '../preset-provider';
  8. import { PluginStateObject } from '../../objects';
  9. import { ParamDefinition as PD } from '../../../mol-util/param-definition';
  10. import { VisualQuality, VisualQualityOptions } from '../../../mol-geo/geometry/base';
  11. import { ColorTheme } from '../../../mol-theme/color';
  12. import { Structure } from '../../../mol-model/structure';
  13. import { PluginContext } from '../../../mol-plugin/context';
  14. import { StateObjectRef, StateObjectSelector } from '../../../mol-state';
  15. import { StaticStructureComponentType } from '../../helpers/structure-component';
  16. import { StructureSelectionQueries as Q } from '../../helpers/structure-selection-query';
  17. import { PluginConfig } from '../../../mol-plugin/config';
  18. import { StructureFocusRepresentation } from '../../../mol-plugin/behavior/dynamic/selection/structure-focus-representation';
  19. import { createStructureColorThemeParams } from '../../helpers/structure-representation-params';
  20. import { ChainIdColorThemeProvider } from '../../../mol-theme/color/chain-id';
  21. import { OperatorNameColorThemeProvider } from '../../../mol-theme/color/operator-name';
  22. import { IndexPairBonds } from '../../../mol-model-formats/structure/property/bonds/index-pair';
  23. import { StructConn } from '../../../mol-model-formats/structure/property/bonds/struct_conn';
  24. export interface StructureRepresentationPresetProvider<P = any, S extends _Result = _Result> extends PresetProvider<PluginStateObject.Molecule.Structure, P, S> { }
  25. export function StructureRepresentationPresetProvider<P, S extends _Result>(repr: StructureRepresentationPresetProvider<P, S>) { return repr; }
  26. export namespace StructureRepresentationPresetProvider {
  27. export type Params<P extends StructureRepresentationPresetProvider> = P extends StructureRepresentationPresetProvider<infer T> ? T : never;
  28. export type State<P extends StructureRepresentationPresetProvider> = P extends StructureRepresentationPresetProvider<infer _, infer S> ? S : never;
  29. export type Result = {
  30. components?: { [name: string]: StateObjectSelector | undefined },
  31. representations?: { [name: string]: StateObjectSelector | undefined }
  32. }
  33. export const CommonParams = {
  34. ignoreHydrogens: PD.Optional(PD.Boolean(false)),
  35. quality: PD.Optional(PD.Select<VisualQuality>('auto', VisualQualityOptions)),
  36. theme: PD.Optional(PD.Group({
  37. globalName: PD.Optional(PD.Text<ColorTheme.BuiltIn>('')),
  38. carbonColor: PD.Optional(PD.Select('chain-id', PD.arrayToOptions(['chain-id', 'operator-name', 'element-symbol'] as const))),
  39. symmetryColor: PD.Optional(PD.Text<ColorTheme.BuiltIn>('')),
  40. focus: PD.Optional(PD.Group({
  41. name: PD.Optional(PD.Text<ColorTheme.BuiltIn>('')),
  42. params: PD.Optional(PD.Value<ColorTheme.BuiltInParams<ColorTheme.BuiltIn>>({} as any))
  43. }))
  44. }))
  45. };
  46. export type CommonParams = PD.ValuesFor<typeof CommonParams>
  47. function getCarbonColorParams(name: 'chain-id' | 'operator-name' | 'element-symbol') {
  48. return name === 'chain-id'
  49. ? { name, params: ChainIdColorThemeProvider.defaultValues }
  50. : name === 'operator-name'
  51. ? { name, params: OperatorNameColorThemeProvider.defaultValues }
  52. : { name, params: {} };
  53. }
  54. function isSymmetry(structure: Structure) {
  55. return structure.units.some(u => !u.conformation.operator.assembly && u.conformation.operator.spgrOp >= 0);
  56. }
  57. export function reprBuilder(plugin: PluginContext, params: CommonParams, structure?: Structure) {
  58. const update = plugin.state.data.build();
  59. const builder = plugin.builders.structure.representation;
  60. const typeParams = {
  61. quality: plugin.managers.structure.component.state.options.visualQuality,
  62. ignoreHydrogens: !plugin.managers.structure.component.state.options.showHydrogens,
  63. };
  64. if (params.quality && params.quality !== 'auto') typeParams.quality = params.quality;
  65. if (params.ignoreHydrogens !== void 0) typeParams.ignoreHydrogens = !!params.ignoreHydrogens;
  66. const color: ColorTheme.BuiltIn | undefined = params.theme?.globalName ? params.theme?.globalName : void 0;
  67. const ballAndStickColor: ColorTheme.BuiltInParams<'element-symbol'> = params.theme?.carbonColor !== undefined
  68. ? { carbonColor: getCarbonColorParams(params.theme?.carbonColor) }
  69. : { };
  70. const symmetryColor: ColorTheme.BuiltIn | undefined = structure && params.theme?.symmetryColor
  71. ? isSymmetry(structure) ? params.theme?.symmetryColor : color
  72. : color;
  73. return { update, builder, color, symmetryColor, typeParams, ballAndStickColor };
  74. }
  75. export function updateFocusRepr<T extends ColorTheme.BuiltIn>(plugin: PluginContext, structure: Structure, themeName: T | undefined, themeParams: ColorTheme.BuiltInParams<T> | undefined) {
  76. return plugin.state.updateBehavior(StructureFocusRepresentation, p => {
  77. const c = createStructureColorThemeParams(plugin, structure, 'ball-and-stick', themeName || 'element-symbol', themeParams);
  78. p.surroundingsParams.colorTheme = c;
  79. p.targetParams.colorTheme = c;
  80. });
  81. }
  82. }
  83. type _Result = StructureRepresentationPresetProvider.Result
  84. const CommonParams = StructureRepresentationPresetProvider.CommonParams;
  85. type CommonParams = StructureRepresentationPresetProvider.CommonParams
  86. const reprBuilder = StructureRepresentationPresetProvider.reprBuilder;
  87. const updateFocusRepr = StructureRepresentationPresetProvider.updateFocusRepr;
  88. const auto = StructureRepresentationPresetProvider({
  89. id: 'preset-structure-representation-auto',
  90. display: {
  91. name: 'Automatic',
  92. description: 'Show representations based on the size of the structure. Smaller structures are shown with more detail than larger ones, ranging from atomistic display to coarse surfaces.'
  93. },
  94. params: () => CommonParams,
  95. apply(ref, params, plugin) {
  96. const structure = StateObjectRef.resolveAndCheck(plugin.state.data, ref)?.obj?.data;
  97. if (!structure) return { };
  98. const thresholds = plugin.config.get(PluginConfig.Structure.SizeThresholds) || Structure.DefaultSizeThresholds;
  99. const size = Structure.getSize(structure, thresholds);
  100. switch (size) {
  101. case Structure.Size.Gigantic:
  102. case Structure.Size.Huge:
  103. return coarseSurface.apply(ref, params, plugin);
  104. case Structure.Size.Large:
  105. return polymerCartoon.apply(ref, params, plugin);
  106. case Structure.Size.Medium:
  107. return polymerAndLigand.apply(ref, params, plugin);
  108. case Structure.Size.Small:
  109. // `showCarbohydrateSymbol: true` is nice e.g. for PDB 1aga
  110. return atomicDetail.apply(ref, { ...params, showCarbohydrateSymbol: true }, plugin);
  111. }
  112. }
  113. });
  114. const empty = StructureRepresentationPresetProvider({
  115. id: 'preset-structure-representation-empty',
  116. display: { name: 'Empty', description: 'Removes all existing representations.' },
  117. async apply(ref, params, plugin) {
  118. return { };
  119. }
  120. });
  121. const BuiltInPresetGroupName = 'Basic';
  122. const polymerAndLigand = StructureRepresentationPresetProvider({
  123. id: 'preset-structure-representation-polymer-and-ligand',
  124. display: {
  125. name: 'Polymer & Ligand', group: BuiltInPresetGroupName,
  126. description: 'Shows polymers as Cartoon, ligands as Ball & Stick, carbohydrates as 3D-SNFG and water molecules semi-transparent.'
  127. },
  128. params: () => CommonParams,
  129. async apply(ref, params, plugin) {
  130. const structureCell = StateObjectRef.resolveAndCheck(plugin.state.data, ref);
  131. if (!structureCell) return {};
  132. const components = {
  133. polymer: await presetStaticComponent(plugin, structureCell, 'polymer'),
  134. ligand: await presetStaticComponent(plugin, structureCell, 'ligand'),
  135. nonStandard: await presetStaticComponent(plugin, structureCell, 'non-standard'),
  136. branched: await presetStaticComponent(plugin, structureCell, 'branched', { label: 'Carbohydrate' }),
  137. water: await presetStaticComponent(plugin, structureCell, 'water'),
  138. ion: await presetStaticComponent(plugin, structureCell, 'ion'),
  139. lipid: await presetStaticComponent(plugin, structureCell, 'lipid'),
  140. coarse: await presetStaticComponent(plugin, structureCell, 'coarse')
  141. };
  142. const structure = structureCell.obj!.data;
  143. const cartoonProps = {
  144. sizeFactor: structure.isCoarseGrained ? 0.8 : 0.2,
  145. };
  146. // TODO make configurable
  147. const waterType = (components.water?.obj?.data?.elementCount || 0) > 50_000 ? 'line' : 'ball-and-stick';
  148. const lipidType = (components.lipid?.obj?.data?.elementCount || 0) > 20_000 ? 'line' : 'ball-and-stick';
  149. const { update, builder, typeParams, color, symmetryColor, ballAndStickColor } = reprBuilder(plugin, params, structure);
  150. const representations = {
  151. polymer: builder.buildRepresentation(update, components.polymer, { type: 'cartoon', typeParams: { ...typeParams, ...cartoonProps }, color: symmetryColor }, { tag: 'polymer' }),
  152. ligand: builder.buildRepresentation(update, components.ligand, { type: 'ball-and-stick', typeParams, color, colorParams: ballAndStickColor }, { tag: 'ligand' }),
  153. nonStandard: builder.buildRepresentation(update, components.nonStandard, { type: 'ball-and-stick', typeParams, color, colorParams: ballAndStickColor }, { tag: 'non-standard' }),
  154. branchedBallAndStick: builder.buildRepresentation(update, components.branched, { type: 'ball-and-stick', typeParams: { ...typeParams, alpha: 0.3 }, color, colorParams: ballAndStickColor }, { tag: 'branched-ball-and-stick' }),
  155. branchedSnfg3d: builder.buildRepresentation(update, components.branched, { type: 'carbohydrate', typeParams, color }, { tag: 'branched-snfg-3d' }),
  156. water: builder.buildRepresentation(update, components.water, { type: waterType, typeParams: { ...typeParams, alpha: 0.6 }, color, colorParams: { carbonColor: { name: 'element-symbol', params: {} } } }, { tag: 'water' }),
  157. ion: builder.buildRepresentation(update, components.ion, { type: 'ball-and-stick', typeParams, color, colorParams: { carbonColor: { name: 'element-symbol', params: {} } } }, { tag: 'ion' }),
  158. lipid: builder.buildRepresentation(update, components.lipid, { type: lipidType, typeParams: { ...typeParams, alpha: 0.6 }, color, colorParams: { carbonColor: { name: 'element-symbol', params: {} } } }, { tag: 'lipid' }),
  159. coarse: builder.buildRepresentation(update, components.coarse, { type: 'spacefill', typeParams, color: color || 'chain-id' }, { tag: 'coarse' })
  160. };
  161. await update.commit({ revertOnError: false });
  162. await updateFocusRepr(plugin, structure, params.theme?.focus?.name, params.theme?.focus?.params);
  163. return { components, representations };
  164. }
  165. });
  166. const proteinAndNucleic = StructureRepresentationPresetProvider({
  167. id: 'preset-structure-representation-protein-and-nucleic',
  168. display: {
  169. name: 'Protein & Nucleic', group: BuiltInPresetGroupName,
  170. description: 'Shows proteins as Cartoon and RNA/DNA as Gaussian Surface.'
  171. },
  172. params: () => CommonParams,
  173. async apply(ref, params, plugin) {
  174. const structureCell = StateObjectRef.resolveAndCheck(plugin.state.data, ref);
  175. if (!structureCell) return {};
  176. const components = {
  177. protein: await presetSelectionComponent(plugin, structureCell, 'protein'),
  178. nucleic: await presetSelectionComponent(plugin, structureCell, 'nucleic'),
  179. };
  180. const structure = structureCell.obj!.data;
  181. const cartoonProps = {
  182. sizeFactor: structure.isCoarseGrained ? 0.8 : 0.2,
  183. };
  184. const gaussianProps = {
  185. radiusOffset: structure.isCoarseGrained ? 2 : 0,
  186. smoothness: structure.isCoarseGrained ? 0.5 : 1.5,
  187. };
  188. const { update, builder, typeParams, symmetryColor } = reprBuilder(plugin, params, structure);
  189. const representations = {
  190. protein: builder.buildRepresentation(update, components.protein, { type: 'cartoon', typeParams: { ...typeParams, ...cartoonProps }, color: symmetryColor }, { tag: 'protein' }),
  191. nucleic: builder.buildRepresentation(update, components.nucleic, { type: 'gaussian-surface', typeParams: { ...typeParams, ...gaussianProps }, color: symmetryColor }, { tag: 'nucleic' })
  192. };
  193. await update.commit({ revertOnError: true });
  194. await updateFocusRepr(plugin, structure, params.theme?.focus?.name, params.theme?.focus?.params);
  195. return { components, representations };
  196. }
  197. });
  198. const coarseSurface = StructureRepresentationPresetProvider({
  199. id: 'preset-structure-representation-coarse-surface',
  200. display: {
  201. name: 'Coarse Surface', group: BuiltInPresetGroupName,
  202. description: 'Shows polymers as coarse Gaussian Surface.'
  203. },
  204. params: () => CommonParams,
  205. async apply(ref, params, plugin) {
  206. const structureCell = StateObjectRef.resolveAndCheck(plugin.state.data, ref);
  207. if (!structureCell) return {};
  208. const components = {
  209. polymer: await presetStaticComponent(plugin, structureCell, 'polymer')
  210. };
  211. const structure = structureCell.obj!.data;
  212. const size = Structure.getSize(structure);
  213. const gaussianProps = Object.create(null);
  214. if (size === Structure.Size.Gigantic) {
  215. Object.assign(gaussianProps, {
  216. traceOnly: true,
  217. radiusOffset: 2,
  218. smoothness: 0.5,
  219. visuals: ['structure-gaussian-surface-mesh']
  220. });
  221. } else if(size === Structure.Size.Huge) {
  222. Object.assign(gaussianProps, {
  223. radiusOffset: structure.isCoarseGrained ? 2 : 0,
  224. smoothness: 0.5,
  225. });
  226. } else if(structure.isCoarseGrained) {
  227. Object.assign(gaussianProps, {
  228. radiusOffset: 2,
  229. smoothness: 0.5,
  230. });
  231. }
  232. const { update, builder, typeParams, symmetryColor } = reprBuilder(plugin, params, structure);
  233. const representations = {
  234. polymer: builder.buildRepresentation(update, components.polymer, { type: 'gaussian-surface', typeParams: { ...typeParams, ...gaussianProps }, color: symmetryColor }, { tag: 'polymer' })
  235. };
  236. await update.commit({ revertOnError: true });
  237. await updateFocusRepr(plugin, structure, params.theme?.focus?.name, params.theme?.focus?.params);
  238. return { components, representations };
  239. }
  240. });
  241. const polymerCartoon = StructureRepresentationPresetProvider({
  242. id: 'preset-structure-representation-polymer-cartoon',
  243. display: {
  244. name: 'Polymer Cartoon', group: BuiltInPresetGroupName,
  245. description: 'Shows polymers as Cartoon.'
  246. },
  247. params: () => CommonParams,
  248. async apply(ref, params, plugin) {
  249. const structureCell = StateObjectRef.resolveAndCheck(plugin.state.data, ref);
  250. if (!structureCell) return {};
  251. const components = {
  252. polymer: await presetStaticComponent(plugin, structureCell, 'polymer'),
  253. };
  254. const structure = structureCell.obj!.data;
  255. const cartoonProps = {
  256. sizeFactor: structure.isCoarseGrained ? 0.8 : 0.2
  257. };
  258. const { update, builder, typeParams, symmetryColor } = reprBuilder(plugin, params, structure);
  259. const representations = {
  260. polymer: builder.buildRepresentation(update, components.polymer, { type: 'cartoon', typeParams: { ...typeParams, ...cartoonProps }, color: symmetryColor }, { tag: 'polymer' })
  261. };
  262. await update.commit({ revertOnError: true });
  263. await updateFocusRepr(plugin, structure, params.theme?.focus?.name, params.theme?.focus?.params);
  264. return { components, representations };
  265. }
  266. });
  267. const atomicDetail = StructureRepresentationPresetProvider({
  268. id: 'preset-structure-representation-atomic-detail',
  269. display: {
  270. name: 'Atomic Detail', group: BuiltInPresetGroupName,
  271. description: 'Shows everything in atomic detail with Ball & Stick.'
  272. },
  273. params: () => ({
  274. ...CommonParams,
  275. showCarbohydrateSymbol: PD.Boolean(false)
  276. }),
  277. async apply(ref, params, plugin) {
  278. const structureCell = StateObjectRef.resolveAndCheck(plugin.state.data, ref);
  279. if (!structureCell) return {};
  280. const components = {
  281. all: await presetStaticComponent(plugin, structureCell, 'all'),
  282. branched: undefined
  283. };
  284. const structure = structureCell.obj!.data;
  285. const highElementCount = structure.elementCount > 100_000; // TODO make configurable
  286. const lowResidueElementRatio = structure.atomicResidueCount &&
  287. structure.elementCount > 1000 &&
  288. structure.atomicResidueCount / structure.elementCount < 3;
  289. const m = structure.models[0];
  290. const bondsGiven = !!IndexPairBonds.Provider.get(m) || StructConn.isExhaustive(m);
  291. const atomicType = lowResidueElementRatio && !bondsGiven
  292. ? 'spacefill' : highElementCount
  293. ? 'line' : 'ball-and-stick';
  294. const showCarbohydrateSymbol = params.showCarbohydrateSymbol && !highElementCount && !lowResidueElementRatio;
  295. if (showCarbohydrateSymbol) {
  296. Object.assign(components, {
  297. branched: await presetStaticComponent(plugin, structureCell, 'branched', { label: 'Carbohydrate' }),
  298. });
  299. }
  300. const { update, builder, typeParams, color, ballAndStickColor } = reprBuilder(plugin, params, structure);
  301. const colorParams = lowResidueElementRatio && !bondsGiven
  302. ? { carbonColor: { name: 'element-symbol', params: {} } }
  303. : ballAndStickColor;
  304. const representations = {
  305. all: builder.buildRepresentation(update, components.all, { type: atomicType, typeParams, color, colorParams }, { tag: 'all' }),
  306. };
  307. if (showCarbohydrateSymbol) {
  308. Object.assign(representations, {
  309. snfg3d: builder.buildRepresentation(update, components.branched, { type: 'carbohydrate', typeParams: { ...typeParams, alpha: 0.4, visuals: ['carbohydrate-symbol'] }, color }, { tag: 'snfg-3d' }),
  310. });
  311. }
  312. await update.commit({ revertOnError: true });
  313. return { components, representations };
  314. }
  315. });
  316. export function presetStaticComponent(plugin: PluginContext, structure: StateObjectRef<PluginStateObject.Molecule.Structure>, type: StaticStructureComponentType, params?: { label?: string, tags?: string[] }) {
  317. return plugin.builders.structure.tryCreateComponentStatic(structure, type, params);
  318. }
  319. export function presetSelectionComponent(plugin: PluginContext, structure: StateObjectRef<PluginStateObject.Molecule.Structure>, query: keyof typeof Q, params?: { label?: string, tags?: string[] }) {
  320. return plugin.builders.structure.tryCreateComponentFromSelection(structure, Q[query], `selection-${query}`, params);
  321. }
  322. export const PresetStructureRepresentations = {
  323. empty,
  324. auto,
  325. 'atomic-detail': atomicDetail,
  326. 'polymer-cartoon': polymerCartoon,
  327. 'polymer-and-ligand': polymerAndLigand,
  328. 'protein-and-nucleic': proteinAndNucleic,
  329. 'coarse-surface': coarseSurface
  330. };
  331. export type PresetStructureRepresentations = typeof PresetStructureRepresentations;