diff --git a/CHANGELOG.md b/CHANGELOG.md index 318ed6d4ccf2ee8d6e9cb479f5c59980683cdf3f..feec435a281f86df123200ff66344c7918cfeac7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ Note that since we don't clearly distinguish between a public and private interf ## [Unreleased] - Add PDBj as a pdb-provider option +- Move Viewer APP to a separate file to allow use without importing light theme & index.html ## [v3.0.0-dev.8] - 2021-12-31 diff --git a/src/apps/viewer/app.ts b/src/apps/viewer/app.ts new file mode 100644 index 0000000000000000000000000000000000000000..782ecd4c6cd06dc5642f2c17e880708dc942f9b5 --- /dev/null +++ b/src/apps/viewer/app.ts @@ -0,0 +1,479 @@ +/** + * Copyright (c) 2018-2022 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author David Sehnal <david.sehnal@gmail.com> + * @author Alexander Rose <alexander.rose@weirdbyte.de> + */ + +import { ANVILMembraneOrientation } from '../../extensions/anvil/behavior'; +import { CellPack } from '../../extensions/cellpack'; +import { DnatcoConfalPyramids } from '../../extensions/dnatco'; +import { G3DFormat, G3dProvider } from '../../extensions/g3d/format'; +import { GeometryExport } from '../../extensions/geo-export'; +import { MAQualityAssessment } from '../../extensions/model-archive/quality-assessment/behavior'; +import { QualityAssessmentPLDDTPreset, QualityAssessmentQmeanPreset } from '../../extensions/model-archive/quality-assessment/behavior'; +import { QualityAssessment } from '../../extensions/model-archive/quality-assessment/prop'; +import { Mp4Export } from '../../extensions/mp4-export'; +import { PDBeStructureQualityReport } from '../../extensions/pdbe'; +import { RCSBAssemblySymmetry, RCSBValidationReport } from '../../extensions/rcsb'; +import { DownloadStructure, PdbDownloadProvider } from '../../mol-plugin-state/actions/structure'; +import { DownloadDensity } from '../../mol-plugin-state/actions/volume'; +import { PresetTrajectoryHierarchy } from '../../mol-plugin-state/builder/structure/hierarchy-preset'; +import { PresetStructureRepresentations, StructureRepresentationPresetProvider } from '../../mol-plugin-state/builder/structure/representation-preset'; +import { DataFormatProvider } from '../../mol-plugin-state/formats/provider'; +import { BuildInStructureFormat } from '../../mol-plugin-state/formats/structure'; +import { BuiltInTrajectoryFormat } from '../../mol-plugin-state/formats/trajectory'; +import { BuildInVolumeFormat } from '../../mol-plugin-state/formats/volume'; +import { createVolumeRepresentationParams } from '../../mol-plugin-state/helpers/volume-representation-params'; +import { PluginStateObject } from '../../mol-plugin-state/objects'; +import { StateTransforms } from '../../mol-plugin-state/transforms'; +import { TrajectoryFromModelAndCoordinates } from '../../mol-plugin-state/transforms/model'; +import { createPluginUI } from '../../mol-plugin-ui'; +import { PluginUIContext } from '../../mol-plugin-ui/context'; +import { DefaultPluginUISpec, PluginUISpec } from '../../mol-plugin-ui/spec'; +import { PluginCommands } from '../../mol-plugin/commands'; +import { PluginConfig } from '../../mol-plugin/config'; +import { PluginLayoutControlsDisplay } from '../../mol-plugin/layout'; +import { PluginSpec } from '../../mol-plugin/spec'; +import { PluginState } from '../../mol-plugin/state'; +import { StateObjectRef, StateObjectSelector } from '../../mol-state'; +import { Asset } from '../../mol-util/assets'; +import { Color } from '../../mol-util/color'; +import '../../mol-util/polyfill'; +import { ObjectKeys } from '../../mol-util/type-helpers'; + +export { PLUGIN_VERSION as version } from '../../mol-plugin/version'; +export { setDebugMode, setProductionMode } from '../../mol-util/debug'; + +const CustomFormats = [ + ['g3d', G3dProvider] as const +]; + +const Extensions = { + 'cellpack': PluginSpec.Behavior(CellPack), + 'dnatco-confal-pyramids': PluginSpec.Behavior(DnatcoConfalPyramids), + 'pdbe-structure-quality-report': PluginSpec.Behavior(PDBeStructureQualityReport), + 'rcsb-assembly-symmetry': PluginSpec.Behavior(RCSBAssemblySymmetry), + 'rcsb-validation-report': PluginSpec.Behavior(RCSBValidationReport), + 'anvil-membrane-orientation': PluginSpec.Behavior(ANVILMembraneOrientation), + 'g3d': PluginSpec.Behavior(G3DFormat), + 'mp4-export': PluginSpec.Behavior(Mp4Export), + 'geo-export': PluginSpec.Behavior(GeometryExport), + 'ma-quality-assessment': PluginSpec.Behavior(MAQualityAssessment), +}; + +const DefaultViewerOptions = { + customFormats: CustomFormats as [string, DataFormatProvider][], + extensions: ObjectKeys(Extensions), + layoutIsExpanded: true, + layoutShowControls: true, + layoutShowRemoteState: true, + layoutControlsDisplay: 'reactive' as PluginLayoutControlsDisplay, + layoutShowSequence: true, + layoutShowLog: true, + layoutShowLeftPanel: true, + collapseLeftPanel: false, + collapseRightPanel: false, + disableAntialiasing: PluginConfig.General.DisableAntialiasing.defaultValue, + pixelScale: PluginConfig.General.PixelScale.defaultValue, + pickScale: PluginConfig.General.PickScale.defaultValue, + pickPadding: PluginConfig.General.PickPadding.defaultValue, + enableWboit: PluginConfig.General.EnableWboit.defaultValue, + preferWebgl1: PluginConfig.General.PreferWebGl1.defaultValue, + + viewportShowExpand: PluginConfig.Viewport.ShowExpand.defaultValue, + viewportShowControls: PluginConfig.Viewport.ShowControls.defaultValue, + viewportShowSettings: PluginConfig.Viewport.ShowSettings.defaultValue, + viewportShowSelectionMode: PluginConfig.Viewport.ShowSelectionMode.defaultValue, + viewportShowAnimation: PluginConfig.Viewport.ShowAnimation.defaultValue, + pluginStateServer: PluginConfig.State.DefaultServer.defaultValue, + volumeStreamingServer: PluginConfig.VolumeStreaming.DefaultServer.defaultValue, + volumeStreamingDisabled: !PluginConfig.VolumeStreaming.Enabled.defaultValue, + pdbProvider: PluginConfig.Download.DefaultPdbProvider.defaultValue, + emdbProvider: PluginConfig.Download.DefaultEmdbProvider.defaultValue, +}; +type ViewerOptions = typeof DefaultViewerOptions; + +export class Viewer { + constructor(public plugin: PluginUIContext) { + } + + static async create(elementOrId: string | HTMLElement, options: Partial<ViewerOptions> = {}) { + const o = { ...DefaultViewerOptions, ...options }; + const defaultSpec = DefaultPluginUISpec(); + + const spec: PluginUISpec = { + actions: defaultSpec.actions, + behaviors: [ + ...defaultSpec.behaviors, + ...o.extensions.map(e => Extensions[e]), + ], + animations: [...defaultSpec.animations || []], + customParamEditors: defaultSpec.customParamEditors, + customFormats: o?.customFormats, + layout: { + initial: { + isExpanded: o.layoutIsExpanded, + showControls: o.layoutShowControls, + controlsDisplay: o.layoutControlsDisplay, + regionState: { + bottom: 'full', + left: o.collapseLeftPanel ? 'collapsed' : 'full', + right: o.collapseRightPanel ? 'hidden' : 'full', + top: 'full', + } + }, + }, + components: { + ...defaultSpec.components, + controls: { + ...defaultSpec.components?.controls, + top: o.layoutShowSequence ? undefined : 'none', + bottom: o.layoutShowLog ? undefined : 'none', + left: o.layoutShowLeftPanel ? undefined : 'none', + }, + remoteState: o.layoutShowRemoteState ? 'default' : 'none', + }, + config: [ + [PluginConfig.General.DisableAntialiasing, o.disableAntialiasing], + [PluginConfig.General.PixelScale, o.pixelScale], + [PluginConfig.General.PickScale, o.pickScale], + [PluginConfig.General.PickPadding, o.pickPadding], + [PluginConfig.General.EnableWboit, o.enableWboit], + [PluginConfig.General.PreferWebGl1, o.preferWebgl1], + [PluginConfig.Viewport.ShowExpand, o.viewportShowExpand], + [PluginConfig.Viewport.ShowControls, o.viewportShowControls], + [PluginConfig.Viewport.ShowSettings, o.viewportShowSettings], + [PluginConfig.Viewport.ShowSelectionMode, o.viewportShowSelectionMode], + [PluginConfig.Viewport.ShowAnimation, o.viewportShowAnimation], + [PluginConfig.State.DefaultServer, o.pluginStateServer], + [PluginConfig.State.CurrentServer, o.pluginStateServer], + [PluginConfig.VolumeStreaming.DefaultServer, o.volumeStreamingServer], + [PluginConfig.VolumeStreaming.Enabled, !o.volumeStreamingDisabled], + [PluginConfig.Download.DefaultPdbProvider, o.pdbProvider], + [PluginConfig.Download.DefaultEmdbProvider, o.emdbProvider], + [PluginConfig.Structure.DefaultRepresentationPreset, ViewerAutoPreset.id], + ] + }; + + const element = typeof elementOrId === 'string' + ? document.getElementById(elementOrId) + : elementOrId; + if (!element) throw new Error(`Could not get element with id '${elementOrId}'`); + const plugin = await createPluginUI(element, spec, { + onBeforeUIRender: plugin => { + // the preset needs to be added before the UI renders otherwise + // "Download Structure" wont be able to pick it up + plugin.builders.structure.representation.registerPreset(ViewerAutoPreset); + } + }); + return new Viewer(plugin); + } + + 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, options?: LoadStructureOptions) { + 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, representationParams: options?.representationParams as any }, + } + } + })); + } + + async loadAllModelsOrAssemblyFromUrl(url: string, format: BuiltInTrajectoryFormat = 'mmcif', isBinary = false, options?: LoadStructureOptions) { + const plugin = this.plugin; + + const data = await plugin.builders.data.download({ url, isBinary }, { state: { isGhost: true } }); + const trajectory = await plugin.builders.structure.parseTrajectory(data, format); + + await this.plugin.builders.structure.hierarchy.applyPreset(trajectory, 'all-models', { useDefaultIfSingleModel: true, representationPresetParams: options?.representationParams }); + } + + 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, options?: LoadStructureOptions) { + 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, representationParams: options?.representationParams as any }, + } + } + })); + } + + 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, options?: { detail?: number }) { + 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: options?.detail ?? 3, + } + } + })); + } + + loadAlphaFoldDb(afdb: 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: 'alphafolddb' as const, + params: { + id: afdb, + options: { + ...params.source.params.options, + representation: 'preset-structure-representation-ma-quality-assessment-plddt' + }, + } + } + })); + } + + loadModelArchive(id: 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: 'modelarchive' as const, + params: { + id, + options: params.source.params.options, + } + } + })); + } + + /** + * @example Load X-ray density from volume server + viewer.loadVolumeFromUrl({ + url: 'https://www.ebi.ac.uk/pdbe/densities/x-ray/1tqn/cell?detail=3', + format: 'dscif', + isBinary: true + }, [{ + type: 'relative', + value: 1.5, + color: 0x3362B2 + }, { + type: 'relative', + value: 3, + color: 0x33BB33, + volumeIndex: 1 + }, { + type: 'relative', + value: -3, + color: 0xBB3333, + volumeIndex: 1 + }], { + entryId: ['2FO-FC', 'FO-FC'], + isLazy: true + }); + * ********************* + * @example Load EM density from volume server + viewer.loadVolumeFromUrl({ + url: 'https://maps.rcsb.org/em/emd-30210/cell?detail=6', + format: 'dscif', + isBinary: true + }, [{ + type: 'relative', + value: 1, + color: 0x3377aa + }], { + entryId: 'EMD-30210', + isLazy: true + }); + */ + async loadVolumeFromUrl({ url, format, isBinary }: { url: string, format: BuildInVolumeFormat, isBinary: boolean }, isovalues: VolumeIsovalueInfo[], options?: { entryId?: string | string[], isLazy?: boolean }) { + const plugin = this.plugin; + + if (!plugin.dataFormats.get(format)) { + throw new Error(`Unknown density format: ${format}`); + } + + if (options?.isLazy) { + const update = this.plugin.build(); + update.toRoot().apply(StateTransforms.Data.LazyVolume, { + url, + format, + entryId: options?.entryId, + isBinary, + isovalues: isovalues.map(v => ({ alpha: 1, volumeIndex: 0, ...v })) + }); + return update.commit(); + } + + return plugin.dataTransaction(async () => { + const data = await plugin.builders.data.download({ url, isBinary }, { state: { isGhost: true } }); + + const parsed = await plugin.dataFormats.get(format)!.parse(plugin, data, { entryId: options?.entryId }); + const firstVolume = (parsed.volume || parsed.volumes[0]) as StateObjectSelector<PluginStateObject.Volume.Data>; + if (!firstVolume?.isOk) throw new Error('Failed to parse any volume.'); + + const repr = plugin.build(); + for (const iso of isovalues) { + repr + .to(parsed.volumes?.[iso.volumeIndex ?? 0] ?? parsed.volume) + .apply(StateTransforms.Representation.VolumeRepresentation3D, createVolumeRepresentationParams(this.plugin, firstVolume.data!, { + type: 'isosurface', + typeParams: { alpha: iso.alpha ?? 1, isoValue: iso.type === 'absolute' ? { kind: 'absolute', absoluteValue: iso.value } : { kind: 'relative', relativeValue: iso.value } }, + color: 'uniform', + colorParams: { value: iso.color } + })); + } + + await repr.commit(); + }); + } + + /** + * @example + * viewer.loadTrajectory({ + * model: { kind: 'model-url', url: 'villin.gro', format: 'gro' }, + * coordinates: { kind: 'coordinates-url', url: 'villin.xtc', format: 'xtc', isBinary: true }, + * preset: 'all-models' // or 'default' + * }); + */ + async loadTrajectory(params: LoadTrajectoryParams) { + const plugin = this.plugin; + + let model: StateObjectSelector, coords: StateObjectSelector; + + if (params.model.kind === 'model-data' || params.model.kind === 'model-url') { + const data = params.model.kind === 'model-data' + ? await plugin.builders.data.rawData({ data: params.model.data, label: params.modelLabel }) + : await plugin.builders.data.download({ url: params.model.url, isBinary: params.model.isBinary, label: params.modelLabel }); + + const trajectory = await plugin.builders.structure.parseTrajectory(data, params.model.format ?? 'mmcif'); + model = await plugin.builders.structure.createModel(trajectory); + } else { + const data = params.model.kind === 'topology-data' + ? await plugin.builders.data.rawData({ data: params.model.data, label: params.modelLabel }) + : await plugin.builders.data.download({ url: params.model.url, isBinary: params.model.isBinary, label: params.modelLabel }); + + const provider = plugin.dataFormats.get(params.model.format); + model = await provider!.parse(plugin, data); + } + + { + const data = params.coordinates.kind === 'coordinates-data' + ? await plugin.builders.data.rawData({ data: params.coordinates.data, label: params.coordinatesLabel }) + : await plugin.builders.data.download({ url: params.coordinates.url, isBinary: params.coordinates.isBinary, label: params.coordinatesLabel }); + + const provider = plugin.dataFormats.get(params.coordinates.format); + coords = await provider!.parse(plugin, data); + } + + const trajectory = await plugin.build().toRoot() + .apply(TrajectoryFromModelAndCoordinates, { + modelRef: model.ref, + coordinatesRef: coords.ref + }, { dependsOn: [model.ref, coords.ref] }) + .commit(); + + const preset = await plugin.builders.structure.hierarchy.applyPreset(trajectory, params.preset ?? 'default'); + + return { model, coords, preset }; + } + + handleResize() { + this.plugin.layout.events.updated.next(void 0); + } +} + +export interface LoadStructureOptions { + representationParams?: StructureRepresentationPresetProvider.CommonParams +} + +export interface VolumeIsovalueInfo { + type: 'absolute' | 'relative', + value: number, + color: Color, + alpha?: number, + volumeIndex?: number +} + +export interface LoadTrajectoryParams { + model: { kind: 'model-url', url: string, format?: BuiltInTrajectoryFormat /* mmcif */, isBinary?: boolean } + | { kind: 'model-data', data: string | number[] | ArrayBuffer | Uint8Array, format?: BuiltInTrajectoryFormat /* mmcif */ } + | { kind: 'topology-url', url: string, format: BuildInStructureFormat, isBinary?: boolean } + | { kind: 'topology-data', data: string | number[] | ArrayBuffer | Uint8Array, format: BuildInStructureFormat }, + modelLabel?: string, + coordinates: { kind: 'coordinates-url', url: string, format: BuildInStructureFormat, isBinary?: boolean } + | { kind: 'coordinates-data', data: string | number[] | ArrayBuffer | Uint8Array, format: BuildInStructureFormat }, + coordinatesLabel?: string, + preset?: keyof PresetTrajectoryHierarchy +} + +export const ViewerAutoPreset = StructureRepresentationPresetProvider({ + id: 'preset-structure-representation-viewer-auto', + display: { + name: 'Automatic (w/ Annotation)', group: 'Annotation', + description: 'Show standard automatic representation but colored by quality assessment (if available in the model).' + }, + isApplicable(a) { + return ( + !!a.data.models.some(m => QualityAssessment.isApplicable(m, 'pLDDT')) || + !!a.data.models.some(m => QualityAssessment.isApplicable(m, 'qmean')) + ); + }, + params: () => StructureRepresentationPresetProvider.CommonParams, + async apply(ref, params, plugin) { + const structureCell = StateObjectRef.resolveAndCheck(plugin.state.data, ref); + const structure = structureCell?.obj?.data; + if (!structureCell || !structure) return {}; + + if (!!structure.models.some(m => QualityAssessment.isApplicable(m, 'pLDDT'))) { + return await QualityAssessmentPLDDTPreset.apply(ref, params, plugin); + } else if (!!structure.models.some(m => QualityAssessment.isApplicable(m, 'qmean'))) { + return await QualityAssessmentQmeanPreset.apply(ref, params, plugin); + } else { + return await PresetStructureRepresentations.auto.apply(ref, params, plugin); + } + } +}); \ No newline at end of file diff --git a/src/apps/viewer/index.ts b/src/apps/viewer/index.ts index 823de49342c70862f92985e445933e5670a03d7f..cdf34edf25b9899705c592fbc96262447df13d58 100644 --- a/src/apps/viewer/index.ts +++ b/src/apps/viewer/index.ts @@ -1,484 +1,12 @@ /** - * Copyright (c) 2018-2021 mol* contributors, licensed under MIT, See LICENSE file for more info. + * Copyright (c) 2018-2022 mol* contributors, licensed under MIT, See LICENSE file for more info. * * @author David Sehnal <david.sehnal@gmail.com> * @author Alexander Rose <alexander.rose@weirdbyte.de> */ -import { ANVILMembraneOrientation } from '../../extensions/anvil/behavior'; -import { CellPack } from '../../extensions/cellpack'; -import { DnatcoConfalPyramids } from '../../extensions/dnatco'; -import { G3DFormat, G3dProvider } from '../../extensions/g3d/format'; -import { GeometryExport } from '../../extensions/geo-export'; -import { MAQualityAssessment } from '../../extensions/model-archive/quality-assessment/behavior'; -import { QualityAssessmentPLDDTPreset, QualityAssessmentQmeanPreset } from '../../extensions/model-archive/quality-assessment/behavior'; -import { QualityAssessment } from '../../extensions/model-archive/quality-assessment/prop'; -import { Mp4Export } from '../../extensions/mp4-export'; -import { PDBeStructureQualityReport } from '../../extensions/pdbe'; -import { RCSBAssemblySymmetry, RCSBValidationReport } from '../../extensions/rcsb'; -import { DownloadStructure, PdbDownloadProvider } from '../../mol-plugin-state/actions/structure'; -import { DownloadDensity } from '../../mol-plugin-state/actions/volume'; -import { PresetTrajectoryHierarchy } from '../../mol-plugin-state/builder/structure/hierarchy-preset'; -import { PresetStructureRepresentations, StructureRepresentationPresetProvider } from '../../mol-plugin-state/builder/structure/representation-preset'; -import { DataFormatProvider } from '../../mol-plugin-state/formats/provider'; -import { BuildInStructureFormat } from '../../mol-plugin-state/formats/structure'; -import { BuiltInTrajectoryFormat } from '../../mol-plugin-state/formats/trajectory'; -import { BuildInVolumeFormat } from '../../mol-plugin-state/formats/volume'; -import { createVolumeRepresentationParams } from '../../mol-plugin-state/helpers/volume-representation-params'; -import { PluginStateObject } from '../../mol-plugin-state/objects'; -import { StateTransforms } from '../../mol-plugin-state/transforms'; -import { TrajectoryFromModelAndCoordinates } from '../../mol-plugin-state/transforms/model'; -import { createPluginUI } from '../../mol-plugin-ui'; -import { PluginUIContext } from '../../mol-plugin-ui/context'; -import { DefaultPluginUISpec, PluginUISpec } from '../../mol-plugin-ui/spec'; -import { PluginCommands } from '../../mol-plugin/commands'; -import { PluginConfig } from '../../mol-plugin/config'; -import { PluginLayoutControlsDisplay } from '../../mol-plugin/layout'; -import { PluginSpec } from '../../mol-plugin/spec'; -import { PluginState } from '../../mol-plugin/state'; -import { StateObjectRef, StateObjectSelector } from '../../mol-state'; -import { Asset } from '../../mol-util/assets'; -import { Color } from '../../mol-util/color'; -import '../../mol-util/polyfill'; -import { ObjectKeys } from '../../mol-util/type-helpers'; import './embedded.html'; import './favicon.ico'; import './index.html'; - require('mol-plugin-ui/skin/light.scss'); - -export { PLUGIN_VERSION as version } from '../../mol-plugin/version'; -export { setDebugMode, setProductionMode } from '../../mol-util/debug'; - -const CustomFormats = [ - ['g3d', G3dProvider] as const -]; - -const Extensions = { - 'cellpack': PluginSpec.Behavior(CellPack), - 'dnatco-confal-pyramids': PluginSpec.Behavior(DnatcoConfalPyramids), - 'pdbe-structure-quality-report': PluginSpec.Behavior(PDBeStructureQualityReport), - 'rcsb-assembly-symmetry': PluginSpec.Behavior(RCSBAssemblySymmetry), - 'rcsb-validation-report': PluginSpec.Behavior(RCSBValidationReport), - 'anvil-membrane-orientation': PluginSpec.Behavior(ANVILMembraneOrientation), - 'g3d': PluginSpec.Behavior(G3DFormat), - 'mp4-export': PluginSpec.Behavior(Mp4Export), - 'geo-export': PluginSpec.Behavior(GeometryExport), - 'ma-quality-assessment': PluginSpec.Behavior(MAQualityAssessment), -}; - -const DefaultViewerOptions = { - customFormats: CustomFormats as [string, DataFormatProvider][], - extensions: ObjectKeys(Extensions), - layoutIsExpanded: true, - layoutShowControls: true, - layoutShowRemoteState: true, - layoutControlsDisplay: 'reactive' as PluginLayoutControlsDisplay, - layoutShowSequence: true, - layoutShowLog: true, - layoutShowLeftPanel: true, - collapseLeftPanel: false, - collapseRightPanel: false, - disableAntialiasing: PluginConfig.General.DisableAntialiasing.defaultValue, - pixelScale: PluginConfig.General.PixelScale.defaultValue, - pickScale: PluginConfig.General.PickScale.defaultValue, - pickPadding: PluginConfig.General.PickPadding.defaultValue, - enableWboit: PluginConfig.General.EnableWboit.defaultValue, - preferWebgl1: PluginConfig.General.PreferWebGl1.defaultValue, - - viewportShowExpand: PluginConfig.Viewport.ShowExpand.defaultValue, - viewportShowControls: PluginConfig.Viewport.ShowControls.defaultValue, - viewportShowSettings: PluginConfig.Viewport.ShowSettings.defaultValue, - viewportShowSelectionMode: PluginConfig.Viewport.ShowSelectionMode.defaultValue, - viewportShowAnimation: PluginConfig.Viewport.ShowAnimation.defaultValue, - pluginStateServer: PluginConfig.State.DefaultServer.defaultValue, - volumeStreamingServer: PluginConfig.VolumeStreaming.DefaultServer.defaultValue, - volumeStreamingDisabled: !PluginConfig.VolumeStreaming.Enabled.defaultValue, - pdbProvider: PluginConfig.Download.DefaultPdbProvider.defaultValue, - emdbProvider: PluginConfig.Download.DefaultEmdbProvider.defaultValue, -}; -type ViewerOptions = typeof DefaultViewerOptions; - -export class Viewer { - constructor(public plugin: PluginUIContext) { - } - - static async create(elementOrId: string | HTMLElement, options: Partial<ViewerOptions> = {}) { - const o = { ...DefaultViewerOptions, ...options }; - const defaultSpec = DefaultPluginUISpec(); - - const spec: PluginUISpec = { - actions: defaultSpec.actions, - behaviors: [ - ...defaultSpec.behaviors, - ...o.extensions.map(e => Extensions[e]), - ], - animations: [...defaultSpec.animations || []], - customParamEditors: defaultSpec.customParamEditors, - customFormats: o?.customFormats, - layout: { - initial: { - isExpanded: o.layoutIsExpanded, - showControls: o.layoutShowControls, - controlsDisplay: o.layoutControlsDisplay, - regionState: { - bottom: 'full', - left: o.collapseLeftPanel ? 'collapsed' : 'full', - right: o.collapseRightPanel ? 'hidden' : 'full', - top: 'full', - } - }, - }, - components: { - ...defaultSpec.components, - controls: { - ...defaultSpec.components?.controls, - top: o.layoutShowSequence ? undefined : 'none', - bottom: o.layoutShowLog ? undefined : 'none', - left: o.layoutShowLeftPanel ? undefined : 'none', - }, - remoteState: o.layoutShowRemoteState ? 'default' : 'none', - }, - config: [ - [PluginConfig.General.DisableAntialiasing, o.disableAntialiasing], - [PluginConfig.General.PixelScale, o.pixelScale], - [PluginConfig.General.PickScale, o.pickScale], - [PluginConfig.General.PickPadding, o.pickPadding], - [PluginConfig.General.EnableWboit, o.enableWboit], - [PluginConfig.General.PreferWebGl1, o.preferWebgl1], - [PluginConfig.Viewport.ShowExpand, o.viewportShowExpand], - [PluginConfig.Viewport.ShowControls, o.viewportShowControls], - [PluginConfig.Viewport.ShowSettings, o.viewportShowSettings], - [PluginConfig.Viewport.ShowSelectionMode, o.viewportShowSelectionMode], - [PluginConfig.Viewport.ShowAnimation, o.viewportShowAnimation], - [PluginConfig.State.DefaultServer, o.pluginStateServer], - [PluginConfig.State.CurrentServer, o.pluginStateServer], - [PluginConfig.VolumeStreaming.DefaultServer, o.volumeStreamingServer], - [PluginConfig.VolumeStreaming.Enabled, !o.volumeStreamingDisabled], - [PluginConfig.Download.DefaultPdbProvider, o.pdbProvider], - [PluginConfig.Download.DefaultEmdbProvider, o.emdbProvider], - [PluginConfig.Structure.DefaultRepresentationPreset, ViewerAutoPreset.id], - ] - }; - - const element = typeof elementOrId === 'string' - ? document.getElementById(elementOrId) - : elementOrId; - if (!element) throw new Error(`Could not get element with id '${elementOrId}'`); - const plugin = await createPluginUI(element, spec, { - onBeforeUIRender: plugin => { - // the preset needs to be added before the UI renders otherwise - // "Download Structure" wont be able to pick it up - plugin.builders.structure.representation.registerPreset(ViewerAutoPreset); - } - }); - return new Viewer(plugin); - } - - 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, options?: LoadStructureOptions) { - 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, representationParams: options?.representationParams as any }, - } - } - })); - } - - async loadAllModelsOrAssemblyFromUrl(url: string, format: BuiltInTrajectoryFormat = 'mmcif', isBinary = false, options?: LoadStructureOptions) { - const plugin = this.plugin; - - const data = await plugin.builders.data.download({ url, isBinary }, { state: { isGhost: true } }); - const trajectory = await plugin.builders.structure.parseTrajectory(data, format); - - await this.plugin.builders.structure.hierarchy.applyPreset(trajectory, 'all-models', { useDefaultIfSingleModel: true, representationPresetParams: options?.representationParams }); - } - - 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, options?: LoadStructureOptions) { - 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, representationParams: options?.representationParams as any }, - } - } - })); - } - - 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, options?: { detail?: number }) { - 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: options?.detail ?? 3, - } - } - })); - } - - loadAlphaFoldDb(afdb: 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: 'alphafolddb' as const, - params: { - id: afdb, - options: { - ...params.source.params.options, - representation: 'preset-structure-representation-ma-quality-assessment-plddt' - }, - } - } - })); - } - - loadModelArchive(id: 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: 'modelarchive' as const, - params: { - id, - options: params.source.params.options, - } - } - })); - } - - /** - * @example Load X-ray density from volume server - viewer.loadVolumeFromUrl({ - url: 'https://www.ebi.ac.uk/pdbe/densities/x-ray/1tqn/cell?detail=3', - format: 'dscif', - isBinary: true - }, [{ - type: 'relative', - value: 1.5, - color: 0x3362B2 - }, { - type: 'relative', - value: 3, - color: 0x33BB33, - volumeIndex: 1 - }, { - type: 'relative', - value: -3, - color: 0xBB3333, - volumeIndex: 1 - }], { - entryId: ['2FO-FC', 'FO-FC'], - isLazy: true - }); - * ********************* - * @example Load EM density from volume server - viewer.loadVolumeFromUrl({ - url: 'https://maps.rcsb.org/em/emd-30210/cell?detail=6', - format: 'dscif', - isBinary: true - }, [{ - type: 'relative', - value: 1, - color: 0x3377aa - }], { - entryId: 'EMD-30210', - isLazy: true - }); - */ - async loadVolumeFromUrl({ url, format, isBinary }: { url: string, format: BuildInVolumeFormat, isBinary: boolean }, isovalues: VolumeIsovalueInfo[], options?: { entryId?: string | string[], isLazy?: boolean }) { - const plugin = this.plugin; - - if (!plugin.dataFormats.get(format)) { - throw new Error(`Unknown density format: ${format}`); - } - - if (options?.isLazy) { - const update = this.plugin.build(); - update.toRoot().apply(StateTransforms.Data.LazyVolume, { - url, - format, - entryId: options?.entryId, - isBinary, - isovalues: isovalues.map(v => ({ alpha: 1, volumeIndex: 0, ...v })) - }); - return update.commit(); - } - - return plugin.dataTransaction(async () => { - const data = await plugin.builders.data.download({ url, isBinary }, { state: { isGhost: true } }); - - const parsed = await plugin.dataFormats.get(format)!.parse(plugin, data, { entryId: options?.entryId }); - const firstVolume = (parsed.volume || parsed.volumes[0]) as StateObjectSelector<PluginStateObject.Volume.Data>; - if (!firstVolume?.isOk) throw new Error('Failed to parse any volume.'); - - const repr = plugin.build(); - for (const iso of isovalues) { - repr - .to(parsed.volumes?.[iso.volumeIndex ?? 0] ?? parsed.volume) - .apply(StateTransforms.Representation.VolumeRepresentation3D, createVolumeRepresentationParams(this.plugin, firstVolume.data!, { - type: 'isosurface', - typeParams: { alpha: iso.alpha ?? 1, isoValue: iso.type === 'absolute' ? { kind: 'absolute', absoluteValue: iso.value } : { kind: 'relative', relativeValue: iso.value } }, - color: 'uniform', - colorParams: { value: iso.color } - })); - } - - await repr.commit(); - }); - } - - /** - * @example - * viewer.loadTrajectory({ - * model: { kind: 'model-url', url: 'villin.gro', format: 'gro' }, - * coordinates: { kind: 'coordinates-url', url: 'villin.xtc', format: 'xtc', isBinary: true }, - * preset: 'all-models' // or 'default' - * }); - */ - async loadTrajectory(params: LoadTrajectoryParams) { - const plugin = this.plugin; - - let model: StateObjectSelector, coords: StateObjectSelector; - - if (params.model.kind === 'model-data' || params.model.kind === 'model-url') { - const data = params.model.kind === 'model-data' - ? await plugin.builders.data.rawData({ data: params.model.data, label: params.modelLabel }) - : await plugin.builders.data.download({ url: params.model.url, isBinary: params.model.isBinary, label: params.modelLabel }); - - const trajectory = await plugin.builders.structure.parseTrajectory(data, params.model.format ?? 'mmcif'); - model = await plugin.builders.structure.createModel(trajectory); - } else { - const data = params.model.kind === 'topology-data' - ? await plugin.builders.data.rawData({ data: params.model.data, label: params.modelLabel }) - : await plugin.builders.data.download({ url: params.model.url, isBinary: params.model.isBinary, label: params.modelLabel }); - - const provider = plugin.dataFormats.get(params.model.format); - model = await provider!.parse(plugin, data); - } - - { - const data = params.coordinates.kind === 'coordinates-data' - ? await plugin.builders.data.rawData({ data: params.coordinates.data, label: params.coordinatesLabel }) - : await plugin.builders.data.download({ url: params.coordinates.url, isBinary: params.coordinates.isBinary, label: params.coordinatesLabel }); - - const provider = plugin.dataFormats.get(params.coordinates.format); - coords = await provider!.parse(plugin, data); - } - - const trajectory = await plugin.build().toRoot() - .apply(TrajectoryFromModelAndCoordinates, { - modelRef: model.ref, - coordinatesRef: coords.ref - }, { dependsOn: [model.ref, coords.ref] }) - .commit(); - - const preset = await plugin.builders.structure.hierarchy.applyPreset(trajectory, params.preset ?? 'default'); - - return { model, coords, preset }; - } - - handleResize() { - this.plugin.layout.events.updated.next(void 0); - } -} - -export interface LoadStructureOptions { - representationParams?: StructureRepresentationPresetProvider.CommonParams -} - -export interface VolumeIsovalueInfo { - type: 'absolute' | 'relative', - value: number, - color: Color, - alpha?: number, - volumeIndex?: number -} - -export interface LoadTrajectoryParams { - model: { kind: 'model-url', url: string, format?: BuiltInTrajectoryFormat /* mmcif */, isBinary?: boolean } - | { kind: 'model-data', data: string | number[] | ArrayBuffer | Uint8Array, format?: BuiltInTrajectoryFormat /* mmcif */ } - | { kind: 'topology-url', url: string, format: BuildInStructureFormat, isBinary?: boolean } - | { kind: 'topology-data', data: string | number[] | ArrayBuffer | Uint8Array, format: BuildInStructureFormat }, - modelLabel?: string, - coordinates: { kind: 'coordinates-url', url: string, format: BuildInStructureFormat, isBinary?: boolean } - | { kind: 'coordinates-data', data: string | number[] | ArrayBuffer | Uint8Array, format: BuildInStructureFormat }, - coordinatesLabel?: string, - preset?: keyof PresetTrajectoryHierarchy -} - -export const ViewerAutoPreset = StructureRepresentationPresetProvider({ - id: 'preset-structure-representation-viewer-auto', - display: { - name: 'Automatic (w/ Annotation)', group: 'Annotation', - description: 'Show standard automatic representation but colored by quality assessment (if available in the model).' - }, - isApplicable(a) { - return ( - !!a.data.models.some(m => QualityAssessment.isApplicable(m, 'pLDDT')) || - !!a.data.models.some(m => QualityAssessment.isApplicable(m, 'qmean')) - ); - }, - params: () => StructureRepresentationPresetProvider.CommonParams, - async apply(ref, params, plugin) { - const structureCell = StateObjectRef.resolveAndCheck(plugin.state.data, ref); - const structure = structureCell?.obj?.data; - if (!structureCell || !structure) return {}; - - if (!!structure.models.some(m => QualityAssessment.isApplicable(m, 'pLDDT'))) { - return await QualityAssessmentPLDDTPreset.apply(ref, params, plugin); - } else if (!!structure.models.some(m => QualityAssessment.isApplicable(m, 'qmean'))) { - return await QualityAssessmentQmeanPreset.apply(ref, params, plugin); - } else { - return await PresetStructureRepresentations.auto.apply(ref, params, plugin); - } - } -}); \ No newline at end of file +export * from './app'; \ No newline at end of file