diff --git a/src/examples/docking-viewer/index.html b/src/apps/docking-viewer/index.html similarity index 65% rename from src/examples/docking-viewer/index.html rename to src/apps/docking-viewer/index.html index 24e6504181db342b753449065b662b0377c1f575..2c0d1567925400dbef3bb702de3e39fae292e784 100644 --- a/src/examples/docking-viewer/index.html +++ b/src/apps/docking-viewer/index.html @@ -17,22 +17,9 @@ </head> <body> <div id="app"></div> - <script type="text/javascript" src="./index.js"></script> + <script type="text/javascript" src="./molstar.js"></script> <script type="text/javascript"> - var viewer = new DockingViewer('app', { - layoutIsExpanded: false, - layoutShowControls: false, - layoutShowRemoteState: false, - layoutShowSequence: true, - layoutShowLog: false, - layoutShowLeftPanel: true, - - viewportShowExpand: true, - viewportShowControls: false, - viewportShowSettings: false, - viewportShowSelectionMode: false, - viewportShowAnimation: false, - }); + var viewer = new DockingViewer('app', [0x33DD22, 0x1133EE], true); function getParam(name, regex) { var r = new RegExp(name + '=' + '(' + regex + ')[&]?', 'i'); diff --git a/src/examples/docking-viewer/index.ts b/src/apps/docking-viewer/index.ts similarity index 64% rename from src/examples/docking-viewer/index.ts rename to src/apps/docking-viewer/index.ts index 96f25a55215da3067318c0ef9d4eb4a61ff43561..c288b158acc3927f496cb4a6fc8011857f83b519 100644 --- a/src/examples/docking-viewer/index.ts +++ b/src/apps/docking-viewer/index.ts @@ -11,12 +11,8 @@ import './index.html'; import { PluginContext } from '../../mol-plugin/context'; import { PluginCommands } from '../../mol-plugin/commands'; import { PluginSpec } from '../../mol-plugin/spec'; -import { DownloadStructure, PdbDownloadProvider } from '../../mol-plugin-state/actions/structure'; import { PluginConfig } from '../../mol-plugin/config'; -import { Asset } from '../../mol-util/assets'; import { ObjectKeys } from '../../mol-util/type-helpers'; -import { PluginState } from '../../mol-plugin/state'; -import { DownloadDensity } from '../../mol-plugin-state/actions/volume'; import { PluginLayoutControlsDisplay } from '../../mol-plugin/layout'; import { BuiltInTrajectoryFormat } from '../../mol-plugin-state/formats/trajectory'; import { Structure } from '../../mol-model/structure'; @@ -24,9 +20,10 @@ import { PluginStateTransform, PluginStateObject as PSO } from '../../mol-plugin import { ParamDefinition as PD } from '../../mol-util/param-definition'; import { Task } from '../../mol-task'; import { StateObject } from '../../mol-state'; -import { ViewportComponent, StructurePreset } from './viewport'; +import { ViewportComponent, StructurePreset, ShowButtons } from './viewport'; import { PluginBehaviors } from '../../mol-plugin/behavior'; import { ColorNames } from '../../mol-util/color/names'; +import { Color } from '../../mol-util/color'; require('mol-plugin-ui/skin/light.scss'); @@ -53,13 +50,25 @@ const DefaultViewerOptions = { pdbProvider: PluginConfig.Download.DefaultPdbProvider.defaultValue, emdbProvider: PluginConfig.Download.DefaultEmdbProvider.defaultValue, }; -type ViewerOptions = typeof DefaultViewerOptions; class Viewer { plugin: PluginContext - constructor(elementOrId: string | HTMLElement, options: Partial<ViewerOptions> = {}) { - const o = { ...DefaultViewerOptions, ...options }; + constructor(elementOrId: string | HTMLElement, colors = [Color(0x992211), Color(0xDDDDDD)], showButtons = true) { + const o = { ...DefaultViewerOptions, ...{ + layoutIsExpanded: false, + layoutShowControls: false, + layoutShowRemoteState: false, + layoutShowSequence: true, + layoutShowLog: false, + layoutShowLeftPanel: true, + + viewportShowExpand: true, + viewportShowControls: false, + viewportShowSettings: false, + viewportShowSelectionMode: false, + viewportShowAnimation: false, + } }; const spec: PluginSpec = { actions: [...DefaultPluginSpec.actions], @@ -104,7 +113,8 @@ class Viewer { [PluginConfig.State.CurrentServer, o.pluginStateServer], [PluginConfig.VolumeStreaming.DefaultServer, o.volumeStreamingServer], [PluginConfig.Download.DefaultPdbProvider, o.pdbProvider], - [PluginConfig.Download.DefaultEmdbProvider, o.emdbProvider] + [PluginConfig.Download.DefaultEmdbProvider, o.emdbProvider], + [ShowButtons, showButtons] ] }; @@ -114,40 +124,27 @@ class Viewer { if (!element) throw new Error(`Could not get element with id '${elementOrId}'`); this.plugin = createPlugin(element, spec); - PluginCommands.Canvas3D.SetSettings(this.plugin, { settings: { - renderer: { - ...this.plugin.canvas3d!.props.renderer, - backgroundColor: ColorNames.white, - }, - camera: { - ...this.plugin.canvas3d!.props.camera, - helper: { axes: { name: 'off', params: {} } } + (this.plugin.customState as any) = { + colorPalette: { + name: 'colors', + params: { list: { colors } } } - } }); - } - - setRemoteSnapshot(id: string) { - const url = `${this.plugin.config.get(PluginConfig.State.CurrentServer)}/get/${id}`; - return PluginCommands.State.Snapshots.Fetch(this.plugin, { url }); - } - - loadSnapshotFromUrl(url: string, type: PluginState.SnapshotType) { - return PluginCommands.State.Snapshots.OpenUrl(this.plugin, { url, type }); - } + }; - loadStructureFromUrl(url: string, format: BuiltInTrajectoryFormat = 'mmcif', isBinary = false) { - const params = DownloadStructure.createDefaultParams(this.plugin.state.data.root.obj!, this.plugin); - return this.plugin.runTask(this.plugin.state.data.applyAction(DownloadStructure, { - source: { - name: 'url', - params: { - url: Asset.Url(url), - format: format as any, - isBinary, - options: params.source.params.options, - } + this.plugin.behaviors.canvas3d.initialized.subscribe(v => { + if (v) { + PluginCommands.Canvas3D.SetSettings(this.plugin, { settings: { + renderer: { + ...this.plugin.canvas3d!.props.renderer, + backgroundColor: ColorNames.white, + }, + camera: { + ...this.plugin.canvas3d!.props.camera, + helper: { axes: { name: 'off', params: {} } } + } + } }); } - })); + }); } async loadStructuresFromUrlsAndMerge(sources: { url: string, format: BuiltInTrajectoryFormat, isBinary?: boolean }[]) { @@ -162,69 +159,19 @@ class Viewer { structures.push({ ref: structureProperties?.ref || structure.ref }); } + + // remove current structuresfrom hierarchy as they will be merged + // TODO only works with using loadStructuresFromUrlsAndMerge once + // need some more API metho to work with the hierarchy + this.plugin.managers.structure.hierarchy.updateCurrent(this.plugin.managers.structure.hierarchy.current.structures, 'remove'); + const dependsOn = structures.map(({ ref }) => ref); const data = this.plugin.state.data.build().toRoot().apply(MergeStructures, { structures }, { dependsOn }); const structure = await data.commit(); const structureProperties = await this.plugin.builders.structure.insertStructureProperties(structure); - await this.plugin.builders.structure.representation.applyPreset(structureProperties || structure, StructurePreset); - } - - async loadStructureFromData(data: string | number[], format: BuiltInTrajectoryFormat, options?: { dataLabel?: string }) { - const _data = await this.plugin.builders.data.rawData({ data, label: options?.dataLabel }); - const trajectory = await this.plugin.builders.structure.parseTrajectory(_data, format); - await this.plugin.builders.structure.hierarchy.applyPreset(trajectory, 'default'); - } - - loadPdb(pdb: string) { - const params = DownloadStructure.createDefaultParams(this.plugin.state.data.root.obj!, this.plugin); - const provider = this.plugin.config.get(PluginConfig.Download.DefaultPdbProvider)!; - return this.plugin.runTask(this.plugin.state.data.applyAction(DownloadStructure, { - source: { - name: 'pdb' as const, - params: { - provider: { - id: pdb, - server: { - name: provider, - params: PdbDownloadProvider[provider].defaultValue as any - } - }, - options: params.source.params.options, - } - } - })); - } - - loadPdbDev(pdbDev: string) { - const params = DownloadStructure.createDefaultParams(this.plugin.state.data.root.obj!, this.plugin); - return this.plugin.runTask(this.plugin.state.data.applyAction(DownloadStructure, { - source: { - name: 'pdb-dev' as const, - params: { - provider: { - id: pdbDev, - encoding: 'bcif', - }, - options: params.source.params.options, - } - } - })); - } - - loadEmdb(emdb: string) { - const provider = this.plugin.config.get(PluginConfig.Download.DefaultEmdbProvider)!; - return this.plugin.runTask(this.plugin.state.data.applyAction(DownloadDensity, { - source: { - name: 'pdb-emd-ds' as const, - params: { - provider: { - id: emdb, - server: provider, - }, - detail: 3, - } - } - })); + this.plugin.behaviors.canvas3d.initialized.subscribe(async v => { + await this.plugin.builders.structure.representation.applyPreset(structureProperties || structure, StructurePreset); + }); } } @@ -260,4 +207,5 @@ const MergeStructures = PluginStateTransform.BuiltIn({ } }); -(window as any).DockingViewer = Viewer; \ No newline at end of file +(window as any).DockingViewer = Viewer; +export { Viewer as DockingViewer }; \ No newline at end of file diff --git a/src/examples/docking-viewer/viewport.tsx b/src/apps/docking-viewer/viewport.tsx similarity index 63% rename from src/examples/docking-viewer/viewport.tsx rename to src/apps/docking-viewer/viewport.tsx index 8e11e6b5ef168bc2efeac6615ac7effdc461b909..41fae189f3eb60f65527b424e854c5e49643eb45 100644 --- a/src/examples/docking-viewer/viewport.tsx +++ b/src/apps/docking-viewer/viewport.tsx @@ -17,10 +17,11 @@ import { StructureSelectionQueries, StructureSelectionQuery } from '../../mol-pl import { MolScriptBuilder as MS } from '../../mol-script/language/builder'; import { InteractionsRepresentationProvider } from '../../mol-model-props/computed/representations/interactions'; import { InteractionTypeColorThemeProvider } from '../../mol-model-props/computed/themes/interaction-type'; -import { compile } from '../../mol-script/runtime/query/compiler'; -import { StructureSelection, QueryContext, Structure } from '../../mol-model/structure'; import { PluginCommands } from '../../mol-plugin/commands'; import { PluginContext } from '../../mol-plugin/context'; +import { StructureRef } from '../../mol-plugin-state/manager/structure/hierarchy-state'; +import { Color } from '../../mol-util/color'; +import { PluginConfig } from '../../mol-plugin/config'; function shinyStyle(plugin: PluginContext) { return PluginCommands.Canvas3D.SetSettings(plugin, { settings: { @@ -76,6 +77,8 @@ const PresetParams = { ...StructureRepresentationPresetProvider.CommonParams, }; + + export const StructurePreset = StructureRepresentationPresetProvider({ id: 'preset-structure', display: { name: 'Structure' }, @@ -89,10 +92,10 @@ export const StructurePreset = StructureRepresentationPresetProvider({ polymer: await presetStaticComponent(plugin, structureCell, 'polymer'), }; - const { update, builder, typeParams, color } = StructureRepresentationPresetProvider.reprBuilder(plugin, params); + const { update, builder, typeParams } = StructureRepresentationPresetProvider.reprBuilder(plugin, params); const representations = { - ligand: builder.buildRepresentation(update, components.ligand, { type: 'ball-and-stick', typeParams: { ...typeParams, sizeFactor: 0.26 }, color }, { tag: 'ligand' }), - polymer: builder.buildRepresentation(update, components.polymer, { type: 'cartoon', typeParams: { ...typeParams }, color }, { tag: 'polymer' }), + ligand: builder.buildRepresentation(update, components.ligand, { type: 'ball-and-stick', typeParams: { ...typeParams, sizeFactor: 0.35 }, color: 'element-symbol', colorParams: { carbonByChainId: { name: 'off', params: {} } } }, { tag: 'ligand' }), + polymer: builder.buildRepresentation(update, components.polymer, { type: 'cartoon', typeParams: { ...typeParams }, color: 'chain-id', colorParams: { palette: (plugin.customState as any).colorPalette } }, { tag: 'polymer' }), }; await update.commit({ revertOnError: true }); @@ -112,12 +115,14 @@ export const IllustrativePreset = StructureRepresentationPresetProvider({ if (!structureCell) return {}; const components = { - all: await presetStaticComponent(plugin, structureCell, 'all') + ligand: await presetStaticComponent(plugin, structureCell, 'ligand'), + polymer: await presetStaticComponent(plugin, structureCell, 'polymer'), }; const { update, builder, typeParams } = StructureRepresentationPresetProvider.reprBuilder(plugin, params); const representations = { - all: builder.buildRepresentation(update, components.all, { type: 'spacefill', typeParams: { ...typeParams }, color: 'illustrative' }, { tag: 'all' }), + ligand: builder.buildRepresentation(update, components.ligand, { type: 'spacefill', typeParams: { ...typeParams }, color: 'element-symbol', colorParams: { carbonByChainId: { name: 'off', params: {} } } }, { tag: 'ligand' }), + polymer: builder.buildRepresentation(update, components.polymer, { type: 'spacefill', typeParams: { ...typeParams }, color: 'illustrative', colorParams: { palette: (plugin.customState as any).colorPalette } }, { tag: 'polymer' }), }; await update.commit({ revertOnError: true }); @@ -128,6 +133,34 @@ export const IllustrativePreset = StructureRepresentationPresetProvider({ } }); +const SurfacePreset = StructureRepresentationPresetProvider({ + id: 'preset-surface', + display: { name: 'Surface' }, + params: () => PresetParams, + async apply(ref, params, plugin) { + const structureCell = StateObjectRef.resolveAndCheck(plugin.state.data, ref); + const structure = structureCell?.obj?.data; + if (!structureCell || !structure) return {}; + + const components = { + ligand: await presetStaticComponent(plugin, structureCell, 'ligand'), + polymer: await presetStaticComponent(plugin, structureCell, 'polymer'), + }; + + const { update, builder, typeParams } = StructureRepresentationPresetProvider.reprBuilder(plugin, params); + const representations = { + ligand: builder.buildRepresentation(update, components.ligand, { type: 'ball-and-stick', typeParams: { ...typeParams, sizeFactor: 0.26 }, color: 'element-symbol', colorParams: { carbonByChainId: { name: 'off', params: {} } } }, { tag: 'ligand' }), + polymer: builder.buildRepresentation(update, components.polymer, { type: 'molecular-surface', typeParams: { ...typeParams, quality: 'custom', resolution: 0.5, doubleSided: true }, color: 'partial-charge' }, { tag: 'polymer' }), + }; + + await update.commit({ revertOnError: true }); + await shinyStyle(plugin); + plugin.managers.interactivity.setProps({ granularity: 'residue' }); + + return { components, representations }; + } +}); + const PocketPreset = StructureRepresentationPresetProvider({ id: 'preset-pocket', display: { name: 'Pocket' }, @@ -144,7 +177,7 @@ const PocketPreset = StructureRepresentationPresetProvider({ const { update, builder, typeParams } = StructureRepresentationPresetProvider.reprBuilder(plugin, params); const representations = { - ligand: builder.buildRepresentation(update, components.ligand, { type: 'ball-and-stick', typeParams: { ...typeParams, sizeFactor: 0.26 }, color: 'partial-charge' }, { tag: 'ligand' }), + ligand: builder.buildRepresentation(update, components.ligand, { type: 'ball-and-stick', typeParams: { ...typeParams, sizeFactor: 0.26 }, color: 'element-symbol', colorParams: { carbonByChainId: { name: 'off', params: {} } } }, { tag: 'ligand' }), surroundings: builder.buildRepresentation(update, components.surroundings, { type: 'molecular-surface', typeParams: { ...typeParams, includeParent: true, quality: 'custom', resolution: 0.2, doubleSided: true }, color: 'partial-charge' }, { tag: 'surroundings' }), }; @@ -152,11 +185,6 @@ const PocketPreset = StructureRepresentationPresetProvider({ await shinyStyle(plugin); plugin.managers.interactivity.setProps({ granularity: 'element' }); - const compiled = compile<StructureSelection>(StructureSelectionQueries.ligand.expression); - const result = compiled(new QueryContext(structure)); - const selection = StructureSelection.unionStructure(result); - plugin.managers.camera.focusLoci(Structure.toStructureElementLoci(selection)); - return { components, representations }; } }); @@ -172,56 +200,46 @@ const InteractionsPreset = StructureRepresentationPresetProvider({ const components = { ligand: await presetStaticComponent(plugin, structureCell, 'ligand'), - selection: await plugin.builders.structure.tryCreateComponentFromSelection(structureCell, ligandPlusSurroundings, `selection`) + surroundings: await plugin.builders.structure.tryCreateComponentFromSelection(structureCell, ligandSurroundings, `surroundings`), + interactions: await plugin.builders.structure.tryCreateComponentFromSelection(structureCell, ligandPlusSurroundings, `interactions`) }; const { update, builder, typeParams } = StructureRepresentationPresetProvider.reprBuilder(plugin, params); const representations = { - ligand: builder.buildRepresentation(update, components.ligand, { type: 'ball-and-stick', typeParams: { ...typeParams, sizeFactor: 0.26 }, color: 'partial-charge' }, { tag: 'ligand' }), - ballAndStick: builder.buildRepresentation(update, components.selection, { type: 'ball-and-stick', typeParams: { ...typeParams, sizeFactor: 0.1, sizeAspectRatio: 1 }, color: 'partial-charge' }, { tag: 'ball-and-stick' }), - interactions: builder.buildRepresentation(update, components.selection, { type: InteractionsRepresentationProvider, typeParams: { ...typeParams }, color: InteractionTypeColorThemeProvider }, { tag: 'interactions' }), + ligand: builder.buildRepresentation(update, components.ligand, { type: 'ball-and-stick', typeParams: { ...typeParams, sizeFactor: 0.3 }, color: 'element-symbol', colorParams: { carbonByChainId: { name: 'off', params: {} } } }, { tag: 'ligand' }), + ballAndStick: builder.buildRepresentation(update, components.surroundings, { type: 'ball-and-stick', typeParams: { ...typeParams, sizeFactor: 0.1, sizeAspectRatio: 1 }, color: 'element-symbol', colorParams: { carbonByChainId: { name: 'off', params: {} } } }, { tag: 'ball-and-stick' }), + interactions: builder.buildRepresentation(update, components.interactions, { type: InteractionsRepresentationProvider, typeParams: { ...typeParams }, color: InteractionTypeColorThemeProvider }, { tag: 'interactions' }), + label: builder.buildRepresentation(update, components.surroundings, { type: 'label', typeParams: { ...typeParams, background: false, borderWidth: 0.1 }, color: 'uniform', colorParams: { value: Color(0x000000) } }, { tag: 'label' }), }; await update.commit({ revertOnError: true }); await shinyStyle(plugin); plugin.managers.interactivity.setProps({ granularity: 'element' }); - const compiled = compile<StructureSelection>(StructureSelectionQueries.ligand.expression); - const result = compiled(new QueryContext(structure)); - const selection = StructureSelection.unionStructure(result); - plugin.managers.camera.focusLoci(Structure.toStructureElementLoci(selection)); - return { components, representations }; } }); +export const ShowButtons = PluginConfig.item('showButtons', true); + export class ViewportComponent extends PluginUIComponent { - structurePreset = () => { - this.plugin.managers.structure.component.applyPreset( - this.plugin.managers.structure.hierarchy.selection.structures, - StructurePreset - ); + async _set(structures: readonly StructureRef[], preset: StructureRepresentationPresetProvider) { + await this.plugin.managers.structure.component.clear(structures); + await this.plugin.managers.structure.component.applyPreset(structures, preset); } - illustrativePreset = () => { - this.plugin.managers.structure.component.applyPreset( - this.plugin.managers.structure.hierarchy.selection.structures, - IllustrativePreset - ); + set = async (preset: StructureRepresentationPresetProvider) => { + await this._set(this.plugin.managers.structure.hierarchy.selection.structures, preset); } - pocketPreset = () => { - this.plugin.managers.structure.component.applyPreset( - this.plugin.managers.structure.hierarchy.selection.structures, - PocketPreset - ); - } + structurePreset = () => this.set(StructurePreset); + illustrativePreset = () => this.set(IllustrativePreset); + surfacePreset = () => this.set(SurfacePreset); + pocketPreset = () => this.set(PocketPreset); + interactionsPreset = () => this.set(InteractionsPreset); - interactionsPreset = () => { - this.plugin.managers.structure.component.applyPreset( - this.plugin.managers.structure.hierarchy.selection.structures, - InteractionsPreset - ); + get showButtons () { + return this.plugin.config.get(ShowButtons); } render() { @@ -229,7 +247,7 @@ export class ViewportComponent extends PluginUIComponent { return <> <Viewport /> - <div className='msp-viewport-top-left-controls'> + {this.showButtons && <div className='msp-viewport-top-left-controls'> <div style={{ marginBottom: '4px' }}> <Button onClick={this.structurePreset} >Structure</Button> </div> @@ -237,12 +255,15 @@ export class ViewportComponent extends PluginUIComponent { <Button onClick={this.illustrativePreset}>Illustrative</Button> </div> <div style={{ marginBottom: '4px' }}> - <Button onClick={this.pocketPreset}>Pocket</Button> + <Button onClick={this.surfacePreset}>Surface</Button> </div> + {/* <div style={{ marginBottom: '4px' }}> + <Button onClick={this.pocketPreset}>Pocket</Button> + </div> */} <div style={{ marginBottom: '4px' }}> <Button onClick={this.interactionsPreset}>Interactions</Button> </div> - </div> + </div>} <VPControls /> <BackgroundTaskProgress /> <div className='msp-highlight-toast-wrapper'> diff --git a/webpack.config.js b/webpack.config.js index f4cdb9f476a93b24a76405c2f722c92cc6b226cb..47813da69b9151f09f5d267436f96e88027c931d 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -1,6 +1,6 @@ const { createApp, createExample, createBrowserTest } = require('./webpack.config.common.js'); -const examples = ['proteopedia-wrapper', 'basic-wrapper', 'lighting', 'docking-viewer']; +const examples = ['proteopedia-wrapper', 'basic-wrapper', 'lighting']; const tests = [ 'font-atlas', 'marching-cubes', @@ -10,6 +10,7 @@ const tests = [ module.exports = [ createApp('viewer', 'molstar'), + createApp('docking-viewer', 'molstar'), ...examples.map(createExample), ...tests.map(createBrowserTest) ] \ No newline at end of file diff --git a/webpack.config.production.js b/webpack.config.production.js index a5298d65ed0430b7bde4cf6587df78833146c389..94952ee5519c428d0a33dba676142cc6582d1d28 100644 --- a/webpack.config.production.js +++ b/webpack.config.production.js @@ -4,5 +4,6 @@ const examples = ['proteopedia-wrapper', 'basic-wrapper', 'lighting']; module.exports = [ createApp('viewer', 'molstar'), + createApp('docking-viewer', 'molstar'), ...examples.map(createExample) ] \ No newline at end of file