diff --git a/src/apps/basic-wrapper/controls.tsx b/src/apps/basic-wrapper/controls.tsx deleted file mode 100644 index c45a76010edd1bc18d52f4635fd482ae1b9ee7cd..0000000000000000000000000000000000000000 --- a/src/apps/basic-wrapper/controls.tsx +++ /dev/null @@ -1,30 +0,0 @@ -/** - * Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info. - * - * @author David Sehnal <david.sehnal@gmail.com> - */ - -import { PluginUIComponent } from '../../mol-plugin-ui/base'; -import * as React from 'react'; -import { TransformUpdaterControl } from '../../mol-plugin-ui/state/update-transform'; - -export class BasicWrapperControls extends PluginUIComponent { - - render() { - return <div style={{ overflowY: 'auto', display: 'block', height: '100%' }}> - <TransformUpdaterControl nodeRef='asm' /> - <TransformUpdaterControl nodeRef='seq-visual' header={{ name: 'Sequence Visual' }} /> - <TransformUpdaterControl nodeRef='het-visual' header={{ name: 'HET Visual' }} /> - <TransformUpdaterControl nodeRef='water-visual' header={{ name: 'Water Visual' }} initiallyCollapsed={true} /> - <TransformUpdaterControl nodeRef='ihm-visual' header={{ name: 'I/HM Visual' }} initiallyCollapsed={true} /> - </div>; - } -} - -export class CustomToastMessage extends PluginUIComponent { - render() { - return <> - Custom <i>Toast</i> content. No timeout. - </>; - } -} \ No newline at end of file diff --git a/src/apps/basic-wrapper/helpers.ts b/src/apps/basic-wrapper/helpers.ts deleted file mode 100644 index b0b364e73b432527d018844dc5befd194a64e252..0000000000000000000000000000000000000000 --- a/src/apps/basic-wrapper/helpers.ts +++ /dev/null @@ -1,99 +0,0 @@ -/** - * Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info. - * - * @author David Sehnal <david.sehnal@gmail.com> - */ - -import { Mat4, Vec3 } from '../../mol-math/linear-algebra'; -import { PluginContext } from '../../mol-plugin/context'; -import { PluginStateObject as PSO } from '../../mol-plugin-state/objects'; -import { StateTransforms } from '../../mol-plugin-state/transforms'; -import { MolScriptBuilder as MS } from '../../mol-script/language/builder'; -import { StateBuilder } from '../../mol-state'; -import Expression from '../../mol-script/language/expression'; -import { ColorTheme } from '../../mol-theme/color'; -import { createStructureRepresentationParams } from '../../mol-plugin-state/helpers/structure-representation-params'; -type SupportedFormats = 'cif' | 'pdb' - -export namespace StateHelper { - export function download(b: StateBuilder.To<PSO.Root>, url: string, ref?: string) { - return b.apply(StateTransforms.Data.Download, { url, isBinary: false }, { ref }); - } - - export function getModel(b: StateBuilder.To<PSO.Data.Binary | PSO.Data.String>, format: SupportedFormats, modelIndex = 0) { - const parsed = format === 'cif' - ? b.apply(StateTransforms.Data.ParseCif).apply(StateTransforms.Model.TrajectoryFromMmCif) - : b.apply(StateTransforms.Model.TrajectoryFromPDB); - - return parsed.apply(StateTransforms.Model.ModelFromTrajectory, { modelIndex }); - } - - export function structure(b: StateBuilder.To<PSO.Molecule.Model>) { - return b.apply(StateTransforms.Model.StructureFromModel, void 0, { tags: 'structure' }) - }; - - export function selectChain(b: StateBuilder.To<PSO.Molecule.Structure>, auth_asym_id: string) { - const expression = MS.struct.generator.atomGroups({ - 'chain-test': MS.core.rel.eq([MS.struct.atomProperty.macromolecular.auth_asym_id(), auth_asym_id]) - }) - return b.apply(StateTransforms.Model.StructureSelectionFromExpression, { expression, label: `Chain ${auth_asym_id}` }); - } - - export function select(b: StateBuilder.To<PSO.Molecule.Structure>, expression: Expression) { - return b.apply(StateTransforms.Model.StructureSelectionFromExpression, { expression }); - } - - export function selectSurroundingsOfFirstResidue(b: StateBuilder.To<PSO.Molecule.Structure>, comp_id: string, radius: number) { - const expression = MS.struct.modifier.includeSurroundings({ - 0: MS.struct.filter.first([ - MS.struct.generator.atomGroups({ - 'residue-test': MS.core.rel.eq([MS.struct.atomProperty.macromolecular.label_comp_id(), comp_id]), - 'group-by': MS.struct.atomProperty.macromolecular.residueKey() - }) - ]), - radius - }) - return b.apply(StateTransforms.Model.StructureSelectionFromExpression, { expression, label: `Surr. ${comp_id} (${radius} ang)` }); - } - - export function identityTransform(b: StateBuilder.To<PSO.Molecule.Structure>, m: Mat4) { - return b.apply(StateTransforms.Model.TransformStructureConformation, - { transform: { name: 'components', params: { axis: Vec3.create(1, 0, 0), angle: 0, translation: Vec3.zero() } } }, - { tags: 'transform' }); - } - - export function transform(b: StateBuilder.To<PSO.Molecule.Structure>, matrix: Mat4) { - return b.apply(StateTransforms.Model.TransformStructureConformation, { - transform: { name: 'matrix', params: matrix } - }, { tags: 'transform' }); - } - - export function assemble(b: StateBuilder.To<PSO.Molecule.Model>, id?: string) { - const props = { - type: { - name: 'assembly' as const, - params: { id: id || 'deposited' } - } - } - return b.apply(StateTransforms.Model.StructureFromModel, props, { tags: 'asm' }) - } - - export function visual(ctx: PluginContext, visualRoot: StateBuilder.To<PSO.Molecule.Structure>) { - visualRoot.apply(StateTransforms.Model.StructureComplexElement, { type: 'atomic-sequence' }) - .apply(StateTransforms.Representation.StructureRepresentation3D, - createStructureRepresentationParams(ctx, void 0, { type: 'cartoon' }), { tags: 'seq-visual' }); - visualRoot.apply(StateTransforms.Model.StructureComplexElement, { type: 'atomic-het' }) - .apply(StateTransforms.Representation.StructureRepresentation3D, - createStructureRepresentationParams(ctx, void 0, { type: 'ball-and-stick' }), { tags: 'het-visual' }); - return visualRoot; - } - - export function ballsAndSticks(ctx: PluginContext, visualRoot: StateBuilder.To<PSO.Molecule.Structure>, expression: Expression, color?: ColorTheme.BuiltIn) { - visualRoot - .apply(StateTransforms.Model.StructureSelectionFromExpression, { expression }) - .apply(StateTransforms.Representation.StructureRepresentation3D, - createStructureRepresentationParams(ctx, void 0, { type: 'ball-and-stick', color }), { tags: 'het-visual' }); - return visualRoot; - } - -} \ No newline at end of file diff --git a/src/apps/basic-wrapper/index.ts b/src/apps/basic-wrapper/index.ts deleted file mode 100644 index 1d30fbf1e5db8bf5a3937dddfa02375fada5c119..0000000000000000000000000000000000000000 --- a/src/apps/basic-wrapper/index.ts +++ /dev/null @@ -1,219 +0,0 @@ -/** - * Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info. - * - * @author David Sehnal <david.sehnal@gmail.com> - */ - -import { createPlugin, DefaultPluginSpec } from '../../mol-plugin'; -import './index.html' -import { PluginContext } from '../../mol-plugin/context'; -import { PluginCommands } from '../../mol-plugin/commands'; -import { StateTransforms } from '../../mol-plugin-state/transforms'; -import { Color } from '../../mol-util/color'; -import { PluginStateObject as PSO, PluginStateObject } from '../../mol-plugin-state/objects'; -import { AnimateModelIndex } from '../../mol-plugin-state/animation/built-in'; -import { StateBuilder, StateTransform } from '../../mol-state'; -import { StripedResidues } from './coloring'; -import { StaticSuperpositionTestData, buildStaticSuperposition, dynamicSuperpositionTest } from './superposition'; -import { PDBeStructureQualityReport } from '../../mol-plugin/behavior/dynamic/custom-props'; -import { CustomToastMessage } from './controls'; -import { EmptyLoci } from '../../mol-model/loci'; -import { StructureSelection } from '../../mol-model/structure'; -import { Script } from '../../mol-script/script'; -import { createStructureRepresentationParams } from '../../mol-plugin-state/helpers/structure-representation-params'; -require('mol-plugin-ui/skin/light.scss') - -type SupportedFormats = 'cif' | 'pdb' -type LoadParams = { url: string, format?: SupportedFormats, assemblyId?: string } - -class BasicWrapper { - plugin: PluginContext; - - init(target: string | HTMLElement) { - this.plugin = createPlugin(typeof target === 'string' ? document.getElementById(target)! : target, { - ...DefaultPluginSpec, - layout: { - initial: { - isExpanded: false, - showControls: false - }, - controls: { - // left: 'none', - // right: BasicWrapperControls - } - }, - components: { - remoteState: 'none' - } - }); - - this.plugin.representation.structure.themes.colorThemeRegistry.add(StripedResidues.colorThemeProvider!); - this.plugin.managers.lociLabels.addProvider(StripedResidues.labelProvider!); - this.plugin.customModelProperties.register(StripedResidues.propertyProvider, true); - } - - private download(b: StateBuilder.To<PSO.Root>, url: string) { - return b.apply(StateTransforms.Data.Download, { url, isBinary: false }) - } - - private parse(b: StateBuilder.To<PSO.Data.Binary | PSO.Data.String>, format: SupportedFormats, assemblyId: string) { - const parsed = format === 'cif' - ? b.apply(StateTransforms.Data.ParseCif).apply(StateTransforms.Model.TrajectoryFromMmCif) - : b.apply(StateTransforms.Model.TrajectoryFromPDB); - - const props = { - type: { - name: 'assembly' as const, - params: { id: assemblyId || 'deposited' } - } - } - return parsed - .apply(StateTransforms.Model.ModelFromTrajectory, { modelIndex: 0 }) - .apply(StateTransforms.Model.CustomModelProperties, { autoAttach: [StripedResidues.propertyProvider.descriptor.name], properties: {} }, { ref: 'props', state: { isGhost: false } }) - .apply(StateTransforms.Model.StructureFromModel, props, { ref: 'asm' }); - } - - private visual(visualRoot: StateBuilder.To<PSO.Molecule.Structure>) { - visualRoot.apply(StateTransforms.Model.StructureComplexElement, { type: 'atomic-sequence' }, { ref: 'seq' }) - .apply(StateTransforms.Representation.StructureRepresentation3D, - createStructureRepresentationParams(this.plugin, void 0, { type: 'cartoon' }), { ref: 'seq-visual' }); - visualRoot.apply(StateTransforms.Model.StructureComplexElement, { type: 'atomic-het' }) - .apply(StateTransforms.Representation.StructureRepresentation3D, - createStructureRepresentationParams(this.plugin, void 0, { type: 'ball-and-stick' }), { ref: 'het-visual' }); - visualRoot.apply(StateTransforms.Model.StructureComplexElement, { type: 'water' }) - .apply(StateTransforms.Representation.StructureRepresentation3D, - createStructureRepresentationParams(this.plugin, void 0, { type: 'ball-and-stick', typeParams: { alpha: 0.51 } }), { ref: 'water-visual' }); - visualRoot.apply(StateTransforms.Model.StructureComplexElement, { type: 'spheres' }) - .apply(StateTransforms.Representation.StructureRepresentation3D, - createStructureRepresentationParams(this.plugin, void 0, { type: 'spacefill' }), { ref: 'ihm-visual' }); - return visualRoot; - } - - private loadedParams: LoadParams = { url: '', format: 'cif', assemblyId: '' }; - async load({ url, format = 'cif', assemblyId = '' }: LoadParams) { - let loadType: 'full' | 'update' = 'full'; - - const state = this.plugin.state.data; - - if (this.loadedParams.url !== url || this.loadedParams.format !== format) { - loadType = 'full'; - } else if (this.loadedParams.url === url) { - if (state.select('asm').length > 0) loadType = 'update'; - } - - let tree: StateBuilder.Root; - if (loadType === 'full') { - await PluginCommands.State.RemoveObject(this.plugin, { state, ref: state.tree.root.ref }); - tree = state.build(); - this.visual(this.parse(this.download(tree.toRoot(), url), format, assemblyId)); - } else { - const props = { - type: { - name: 'assembly' as const, - params: { id: assemblyId || 'deposited' } - } - } - - tree = state.build(); - tree.to('asm').update(StateTransforms.Model.StructureFromModel, p => ({ ...p, ...props })); - } - - await PluginCommands.State.Update(this.plugin, { state: this.plugin.state.data, tree }); - this.loadedParams = { url, format, assemblyId }; - PluginCommands.Camera.Reset(this.plugin, { }); - } - - setBackground(color: number) { - const renderer = this.plugin.canvas3d!.props.renderer; - PluginCommands.Canvas3D.SetSettings(this.plugin, { settings: { renderer: { ...renderer, backgroundColor: Color(color) } } }); - } - - toggleSpin() { - if (!this.plugin.canvas3d) return; - - const trackball = this.plugin.canvas3d.props.trackball; - const spinning = trackball.spin; - PluginCommands.Canvas3D.SetSettings(this.plugin, { settings: { trackball: { ...trackball, spin: !trackball.spin } } }); - if (!spinning) PluginCommands.Camera.Reset(this.plugin, { }); - } - - animate = { - modelIndex: { - maxFPS: 8, - onceForward: () => { this.plugin.state.animation.play(AnimateModelIndex, { maxFPS: Math.max(0.5, this.animate.modelIndex.maxFPS | 0), mode: { name: 'once', params: { direction: 'forward' } } }) }, - onceBackward: () => { this.plugin.state.animation.play(AnimateModelIndex, { maxFPS: Math.max(0.5, this.animate.modelIndex.maxFPS | 0), mode: { name: 'once', params: { direction: 'backward' } } }) }, - palindrome: () => { this.plugin.state.animation.play(AnimateModelIndex, { maxFPS: Math.max(0.5, this.animate.modelIndex.maxFPS | 0), mode: { name: 'palindrome', params: {} } }) }, - loop: () => { this.plugin.state.animation.play(AnimateModelIndex, { maxFPS: Math.max(0.5, this.animate.modelIndex.maxFPS | 0), mode: { name: 'loop', params: {} } }) }, - stop: () => this.plugin.state.animation.stop() - } - } - - coloring = { - applyStripes: async () => { - const state = this.plugin.state.data; - - const visuals = state.selectQ(q => q.ofTransformer(StateTransforms.Representation.StructureRepresentation3D)); - const tree = state.build(); - const colorTheme = { name: StripedResidues.propertyProvider.descriptor.name, params: this.plugin.representation.structure.themes.colorThemeRegistry.get(StripedResidues.propertyProvider.descriptor.name).defaultValues }; - - for (const v of visuals) { - tree.to(v).update(old => ({ ...old, colorTheme })); - } - - await PluginCommands.State.Update(this.plugin, { state, tree }); - } - } - - interactivity = { - highlightOn: () => { - const seq_id = 7; - const data = (this.plugin.state.data.select('asm')[0].obj as PluginStateObject.Molecule.Structure).data; - const sel = Script.getStructureSelection(Q => Q.struct.generator.atomGroups({ - 'residue-test': Q.core.rel.eq([Q.struct.atomProperty.macromolecular.label_seq_id(), seq_id]), - 'group-by': Q.struct.atomProperty.macromolecular.residueKey() - }), data); - const loci = StructureSelection.toLociWithSourceUnits(sel); - this.plugin.managers.interactivity.lociHighlights.highlightOnly({ loci }); - }, - clearHighlight: () => { - this.plugin.managers.interactivity.lociHighlights.highlightOnly({ loci: EmptyLoci }); - } - } - - tests = { - staticSuperposition: async () => { - const state = this.plugin.state.data; - const tree = buildStaticSuperposition(this.plugin, StaticSuperpositionTestData); - await PluginCommands.State.RemoveObject(this.plugin, { state, ref: StateTransform.RootRef }); - await PluginCommands.State.Update(this.plugin, { state, tree }); - }, - dynamicSuperposition: async () => { - await PluginCommands.State.RemoveObject(this.plugin, { state: this.plugin.state.data, ref: StateTransform.RootRef }); - await dynamicSuperpositionTest(this.plugin, ['1tqn', '2hhb', '4hhb'], 'HEM'); - }, - toggleValidationTooltip: async () => { - const state = this.plugin.state.behaviors; - const tree = state.build().to(PDBeStructureQualityReport.id).update(PDBeStructureQualityReport, p => ({ ...p, showTooltip: !p.showTooltip })); - await PluginCommands.State.Update(this.plugin, { state, tree }); - }, - showToasts: () => { - PluginCommands.Toast.Show(this.plugin, { - title: 'Toast 1', - message: 'This is an example text, timeout 3s', - key: 'toast-1', - timeoutMs: 3000 - }); - PluginCommands.Toast.Show(this.plugin, { - title: 'Toast 2', - message: CustomToastMessage, - key: 'toast-2' - }); - }, - hideToasts: () => { - PluginCommands.Toast.Hide(this.plugin, { key: 'toast-1' }); - PluginCommands.Toast.Hide(this.plugin, { key: 'toast-2' }); - } - } -} - -(window as any).BasicMolStarWrapper = new BasicWrapper(); \ No newline at end of file diff --git a/src/apps/basic-wrapper/superposition.ts b/src/apps/basic-wrapper/superposition.ts deleted file mode 100644 index 2fd361526afeff62872aae0a5831fc36f3765419..0000000000000000000000000000000000000000 --- a/src/apps/basic-wrapper/superposition.ts +++ /dev/null @@ -1,108 +0,0 @@ -/** - * Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info. - * - * @author David Sehnal <david.sehnal@gmail.com> - */ - -// TODO: move to an "example" - -import { PluginContext } from '../../mol-plugin/context'; -import { Mat4 } from '../../mol-math/linear-algebra'; -import { StateHelper } from './helpers'; -import { PluginCommands } from '../../mol-plugin/commands'; -import { StateSelection, StateBuilder } from '../../mol-state'; -import { PluginStateObject as PSO } from '../../mol-plugin-state/objects'; -import { MolScriptBuilder as MS } from '../../mol-script/language/builder'; -import { compile } from '../../mol-script/runtime/query/compiler'; -import { StructureSelection, QueryContext } from '../../mol-model/structure'; -import { superposeStructures } from '../../mol-model/structure/structure/util/superposition'; -import Expression from '../../mol-script/language/expression'; - -export type SuperpositionTestInput = { - pdbId: string, - auth_asym_id: string, - matrix: Mat4 -}[]; - -// function getAxisAngleTranslation(m: Mat4) { -// const translation = Mat4.getTranslation(Vec3.zero(), m); -// const axis = Vec3.zero(); -// const angle = 180 / Math.PI * Quat.getAxisAngle(axis, Mat4.getRotation(Quat.zero(), m)); -// return { translation, axis, angle }; -// } - -export function buildStaticSuperposition(ctx: PluginContext, src: SuperpositionTestInput) { - const b = ctx.state.data.build().toRoot(); - for (const s of src) { - StateHelper.visual(ctx, - StateHelper.transform( - StateHelper.selectChain( - StateHelper.structure( - StateHelper.getModel(StateHelper.download(b, `https://www.ebi.ac.uk/pdbe/static/entry/${s.pdbId}_updated.cif`), 'cif')), - s.auth_asym_id - ), - s.matrix - ) - ); - } - return b; -} - -export const StaticSuperpositionTestData: SuperpositionTestInput = [ - { pdbId: '1aj5', auth_asym_id: 'A', matrix: Mat4.identity() }, - { pdbId: '1df0', auth_asym_id: 'B', matrix: Mat4.ofRows([ - [0.406, 0.879, 0.248, -200.633], - [0.693, -0.473, 0.544, 73.403], - [0.596, -0.049, -0.802, -14.209], - [0, 0, 0, 1]] )}, - { pdbId: '1dvi', auth_asym_id: 'A', matrix: Mat4.ofRows([ - [-0.053, -0.077, 0.996, -45.633], - [-0.312, 0.949, 0.057, -12.255], - [-0.949, -0.307, -0.074, 53.562], - [0, 0, 0, 1]] )} -]; - -export async function dynamicSuperpositionTest(ctx: PluginContext, src: string[], comp_id: string) { - const state = ctx.state.data; - - const structures = state.build().toRoot(); - for (const s of src) { - StateHelper.structure( - StateHelper.getModel(StateHelper.download(structures, `https://www.ebi.ac.uk/pdbe/static/entry/${s}_updated.cif`), 'cif')); - } - - await PluginCommands.State.Update(ctx, { state, tree: structures }); - - const pivot = MS.struct.filter.first([ - MS.struct.generator.atomGroups({ - 'residue-test': MS.core.rel.eq([MS.struct.atomProperty.macromolecular.label_comp_id(), comp_id]), - 'group-by': MS.struct.atomProperty.macromolecular.residueKey() - }) - ]); - const rest = MS.struct.modifier.exceptBy({ - 0: MS.struct.generator.all(), - by: pivot - }); - - const query = compile<StructureSelection>(pivot); - const xs = state.select(StateSelection.Generators.rootsOfType(PSO.Molecule.Structure)); - const selections = xs.map(s => StructureSelection.toLociWithCurrentUnits(query(new QueryContext(s.obj!.data)))); - - const transforms = superposeStructures(selections); - const visuals = state.build(); - - siteVisual(ctx, StateHelper.selectSurroundingsOfFirstResidue(visuals.to(xs[0].transform.ref), 'HEM', 7), pivot, rest); - for (let i = 1; i < selections.length; i++) { - const root = visuals.to(xs[i].transform.ref); - siteVisual(ctx, - StateHelper.transform(StateHelper.selectSurroundingsOfFirstResidue(root, 'HEM', 7), transforms[i - 1].bTransform), - pivot, rest); - } - - await PluginCommands.State.Update(ctx, { state, tree: visuals }); -} - -function siteVisual(ctx: PluginContext, b: StateBuilder.To<PSO.Molecule.Structure>, pivot: Expression, rest: Expression) { - StateHelper.ballsAndSticks(ctx, b, pivot, 'residue-name'); - StateHelper.ballsAndSticks(ctx, b, rest, 'uniform'); -} \ No newline at end of file diff --git a/src/apps/demos/lighting/index.ts b/src/apps/demos/lighting/index.ts deleted file mode 100644 index 1a50d7f640f5dc2030defa653d21bfe2f3d54097..0000000000000000000000000000000000000000 --- a/src/apps/demos/lighting/index.ts +++ /dev/null @@ -1,178 +0,0 @@ -/** - * Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info. - * - * @author Alexander Rose <alexander.rose@weirdbyte.de> - */ - -import { createPlugin, DefaultPluginSpec } from '../../../mol-plugin'; -import './index.html' -import { PluginContext } from '../../../mol-plugin/context'; -import { PluginCommands } from '../../../mol-plugin/commands'; -import { StateTransforms } from '../../../mol-plugin-state/transforms'; -import { PluginStateObject as PSO } from '../../../mol-plugin-state/objects'; -import { StateBuilder } from '../../../mol-state'; -import { Canvas3DProps } from '../../../mol-canvas3d/canvas3d'; -import { createStructureRepresentationParams } from '../../../mol-plugin-state/helpers/structure-representation-params'; -require('mol-plugin-ui/skin/light.scss') - -type SupportedFormats = 'cif' | 'pdb' -type LoadParams = { url: string, format?: SupportedFormats, assemblyId?: string } - -const Canvas3DPresets = { - illustrative: { - multiSample: { - mode: 'temporal' as Canvas3DProps['multiSample']['mode'] - }, - postprocessing: { - occlusionEnable: true, - occlusionBias: 0.8, - occlusionKernelSize: 6, - outlineEnable: true, - }, - renderer: { - ambientIntensity: 1, - lightIntensity: 0, - } - }, - occlusion: { - multiSample: { - mode: 'temporal' as Canvas3DProps['multiSample']['mode'] - }, - postprocessing: { - occlusionEnable: true, - occlusionBias: 0.8, - occlusionKernelSize: 6, - outlineEnable: false, - }, - renderer: { - ambientIntensity: 0.4, - lightIntensity: 0.6, - } - }, - standard: { - multiSample: { - mode: 'off' as Canvas3DProps['multiSample']['mode'] - }, - postprocessing: { - occlusionEnable: false, - outlineEnable: false, - }, - renderer: { - ambientIntensity: 0.4, - lightIntensity: 0.6, - } - } -} - -type Canvas3DPreset = keyof typeof Canvas3DPresets - -function getPreset(preset: Canvas3DPreset) { - switch (preset) { - case 'illustrative': return Canvas3DPresets['illustrative'] - case 'standard': return Canvas3DPresets['standard'] - case 'occlusion': return Canvas3DPresets['occlusion'] - } -} - -class LightingDemo { - plugin: PluginContext; - - init(target: string | HTMLElement) { - this.plugin = createPlugin(typeof target === 'string' ? document.getElementById(target)! : target, { - ...DefaultPluginSpec, - layout: { - initial: { - isExpanded: false, - showControls: false - }, - controls: { left: 'none', right: 'none', top: 'none', bottom: 'none' } - } - }); - - this.setPreset('illustrative'); - } - - setPreset(preset: Canvas3DPreset) { - const props = getPreset(preset) - PluginCommands.Canvas3D.SetSettings(this.plugin, { settings: { - ...props, - multiSample: { - ...this.plugin.canvas3d!.props.multiSample, - ...props.multiSample - }, - renderer: { - ...this.plugin.canvas3d!.props.renderer, - ...props.renderer - }, - postprocessing: { - ...this.plugin.canvas3d!.props.postprocessing, - ...props.postprocessing - }, - }}); - } - - private download(b: StateBuilder.To<PSO.Root>, url: string) { - return b.apply(StateTransforms.Data.Download, { url, isBinary: false }) - } - - private parse(b: StateBuilder.To<PSO.Data.Binary | PSO.Data.String>, format: SupportedFormats, assemblyId: string) { - const parsed = format === 'cif' - ? b.apply(StateTransforms.Data.ParseCif).apply(StateTransforms.Model.TrajectoryFromMmCif) - : b.apply(StateTransforms.Model.TrajectoryFromPDB); - - const props = { - type: { - name: 'assembly' as const, - params: { id: assemblyId || 'deposited' } - } - } - return parsed - .apply(StateTransforms.Model.ModelFromTrajectory, { modelIndex: 0 }) - .apply(StateTransforms.Model.StructureFromModel, props, { ref: 'asm' }); - } - - private visual(visualRoot: StateBuilder.To<PSO.Molecule.Structure>) { - visualRoot.apply(StateTransforms.Model.StructureComplexElement, { type: 'atomic-sequence' }) - .apply(StateTransforms.Representation.StructureRepresentation3D, - createStructureRepresentationParams(this.plugin, void 0, { type: 'spacefill', color: 'illustrative' }), { ref: 'seq-visual' }); - visualRoot.apply(StateTransforms.Model.StructureComplexElement, { type: 'atomic-het' }) - .apply(StateTransforms.Representation.StructureRepresentation3D, - createStructureRepresentationParams(this.plugin, void 0, { type: 'ball-and-stick' }), { ref: 'het-visual' }); - return visualRoot; - } - - private loadedParams: LoadParams = { url: '', format: 'cif', assemblyId: '' }; - async load({ url, format = 'cif', assemblyId = '' }: LoadParams) { - let loadType: 'full' | 'update' = 'full'; - - const state = this.plugin.state.data; - - if (this.loadedParams.url !== url || this.loadedParams.format !== format) { - loadType = 'full'; - } else if (this.loadedParams.url === url) { - if (state.select('asm').length > 0) loadType = 'update'; - } - - let tree: StateBuilder.Root; - if (loadType === 'full') { - await PluginCommands.State.RemoveObject(this.plugin, { state, ref: state.tree.root.ref }); - tree = state.build(); - this.visual(this.parse(this.download(tree.toRoot(), url), format, assemblyId)); - } else { - const props = { - type: { - name: 'assembly' as const, - params: { id: assemblyId || 'deposited' } - } - } - tree = state.build(); - tree.to('asm').update(StateTransforms.Model.StructureFromModel, p => ({ ...p, ...props })); - } - - await PluginCommands.State.Update(this.plugin, { state: this.plugin.state.data, tree }); - this.loadedParams = { url, format, assemblyId }; - PluginCommands.Camera.Reset(this.plugin, { }); - } -} - -(window as any).LightingDemo = new LightingDemo(); \ No newline at end of file diff --git a/src/apps/basic-wrapper/coloring.ts b/src/examples/basic-wrapper/coloring.ts similarity index 100% rename from src/apps/basic-wrapper/coloring.ts rename to src/examples/basic-wrapper/coloring.ts diff --git a/src/examples/basic-wrapper/controls.tsx b/src/examples/basic-wrapper/controls.tsx new file mode 100644 index 0000000000000000000000000000000000000000..59e1bdb7c995918e046b65c5867e5464ae842736 --- /dev/null +++ b/src/examples/basic-wrapper/controls.tsx @@ -0,0 +1,16 @@ +/** + * Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author David Sehnal <david.sehnal@gmail.com> + */ + +import { PluginUIComponent } from '../../mol-plugin-ui/base'; +import * as React from 'react'; + +export class CustomToastMessage extends PluginUIComponent { + render() { + return <> + Custom <i>Toast</i> content. No timeout. + </>; + } +} \ No newline at end of file diff --git a/src/apps/basic-wrapper/index.html b/src/examples/basic-wrapper/index.html similarity index 96% rename from src/apps/basic-wrapper/index.html rename to src/examples/basic-wrapper/index.html index 7c9a25da6738203aa084b2851e851c649b1441ba..e803d34244d39482e3fc7c96de5e6e2078312e5d 100644 --- a/src/apps/basic-wrapper/index.html +++ b/src/examples/basic-wrapper/index.html @@ -50,7 +50,7 @@ <input type='text' id='url' placeholder='url' /> <input type='text' id='assemblyId' placeholder='assembly id' /> <select id='format'> - <option value='cif' selected>CIF</option> + <option value='mmcif' selected>mmCIF</option> <option value='pdb'>PDB</option> </select> </div> @@ -60,7 +60,7 @@ var pdbId = '1grm', assemblyId= '1'; var url = 'https://www.ebi.ac.uk/pdbe/static/entry/' + pdbId + '_updated.cif'; - var format = 'cif'; + var format = 'mmcif'; $('url').value = url; $('url').onchange = function (e) { url = e.target.value; } @@ -104,6 +104,7 @@ addHeader('Misc'); addControl('Apply Stripes', () => BasicMolStarWrapper.coloring.applyStripes()); + addControl('Default Coloring', () => BasicMolStarWrapper.coloring.applyDefault()); addHeader('Interactivity'); addControl('Highlight seq_id=7', () => BasicMolStarWrapper.interactivity.highlightOn()); diff --git a/src/examples/basic-wrapper/index.ts b/src/examples/basic-wrapper/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..4fed9b455644763a68fd4bb11837dafcb85cb3c9 --- /dev/null +++ b/src/examples/basic-wrapper/index.ts @@ -0,0 +1,155 @@ +/** + * Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author David Sehnal <david.sehnal@gmail.com> + */ + +import { EmptyLoci } from '../../mol-model/loci'; +import { StructureSelection } from '../../mol-model/structure'; +import { createPlugin, DefaultPluginSpec } from '../../mol-plugin'; +import { AnimateModelIndex } from '../../mol-plugin-state/animation/built-in'; +import { BuiltInTrajectoryFormat } from '../../mol-plugin-state/formats/trajectory'; +import { PluginStateObject } from '../../mol-plugin-state/objects'; +import { PDBeStructureQualityReport } from '../../mol-plugin/behavior/dynamic/custom-props'; +import { PluginCommands } from '../../mol-plugin/commands'; +import { PluginContext } from '../../mol-plugin/context'; +import { Script } from '../../mol-script/script'; +import { Color } from '../../mol-util/color'; +import { StripedResidues } from './coloring'; +import { CustomToastMessage } from './controls'; +import './index.html'; +import { buildStaticSuperposition, dynamicSuperpositionTest, StaticSuperpositionTestData } from './superposition'; +require('mol-plugin-ui/skin/light.scss') + +type LoadParams = { url: string, format?: BuiltInTrajectoryFormat, isBinary?: boolean, assemblyId?: string } + +class BasicWrapper { + plugin: PluginContext; + + init(target: string | HTMLElement) { + this.plugin = createPlugin(typeof target === 'string' ? document.getElementById(target)! : target, { + ...DefaultPluginSpec, + layout: { + initial: { + isExpanded: false, + showControls: false + }, + controls: { + // left: 'none' + } + }, + components: { + remoteState: 'none' + } + }); + + this.plugin.representation.structure.themes.colorThemeRegistry.add(StripedResidues.colorThemeProvider!); + this.plugin.managers.lociLabels.addProvider(StripedResidues.labelProvider!); + this.plugin.customModelProperties.register(StripedResidues.propertyProvider, true); + } + + async load({ url, format = 'mmcif', isBinary = false, assemblyId = '' }: LoadParams) { + await this.plugin.clear(); + + const data = await this.plugin.builders.data.download({ url, isBinary }, { state: { isGhost: true } }); + const trajectory = await this.plugin.builders.structure.parseTrajectory(data, format); + + await this.plugin.builders.structure.hierarchy.applyPreset(trajectory, 'default', { + structure: assemblyId ? { name: 'assembly', params: { id: assemblyId } } : { name: 'deposited', params: { } }, + showUnitcell: false, + representationPreset: 'auto' + }); + } + + setBackground(color: number) { + PluginCommands.Canvas3D.SetSettings(this.plugin, { settings: props => { props.renderer.backgroundColor = Color(color) } }); + } + + toggleSpin() { + if (!this.plugin.canvas3d) return; + + PluginCommands.Canvas3D.SetSettings(this.plugin, { + settings: props => { + props.trackball.spin = !props.trackball.spin; + } + }); + if (!this.plugin.canvas3d.props.trackball.spin) PluginCommands.Camera.Reset(this.plugin, {}); + } + + animate = { + modelIndex: { + maxFPS: 8, + onceForward: () => { this.plugin.state.animation.play(AnimateModelIndex, { maxFPS: Math.max(0.5, this.animate.modelIndex.maxFPS | 0), mode: { name: 'once', params: { direction: 'forward' } } }) }, + onceBackward: () => { this.plugin.state.animation.play(AnimateModelIndex, { maxFPS: Math.max(0.5, this.animate.modelIndex.maxFPS | 0), mode: { name: 'once', params: { direction: 'backward' } } }) }, + palindrome: () => { this.plugin.state.animation.play(AnimateModelIndex, { maxFPS: Math.max(0.5, this.animate.modelIndex.maxFPS | 0), mode: { name: 'palindrome', params: {} } }) }, + loop: () => { this.plugin.state.animation.play(AnimateModelIndex, { maxFPS: Math.max(0.5, this.animate.modelIndex.maxFPS | 0), mode: { name: 'loop', params: {} } }) }, + stop: () => this.plugin.state.animation.stop() + } + } + + coloring = { + applyStripes: async () => { + this.plugin.dataTransaction(async () => { + for (const s of this.plugin.managers.structure.hierarchy.current.structures) { + await this.plugin.managers.structure.component.updateRepresentationsTheme(s.components, { color: StripedResidues.propertyProvider.descriptor.name as any }) + } + }); + }, + applyDefault: async () => { + this.plugin.dataTransaction(async () => { + for (const s of this.plugin.managers.structure.hierarchy.current.structures) { + await this.plugin.managers.structure.component.updateRepresentationsTheme(s.components, { color: 'default' }) + } + }); + } + } + + interactivity = { + highlightOn: () => { + const seq_id = 7; + const data = (this.plugin.state.data.select('asm')[0].obj as PluginStateObject.Molecule.Structure).data; + const sel = Script.getStructureSelection(Q => Q.struct.generator.atomGroups({ + 'residue-test': Q.core.rel.eq([Q.struct.atomProperty.macromolecular.label_seq_id(), seq_id]), + 'group-by': Q.struct.atomProperty.macromolecular.residueKey() + }), data); + const loci = StructureSelection.toLociWithSourceUnits(sel); + this.plugin.managers.interactivity.lociHighlights.highlightOnly({ loci }); + }, + clearHighlight: () => { + this.plugin.managers.interactivity.lociHighlights.highlightOnly({ loci: EmptyLoci }); + } + } + + tests = { + staticSuperposition: async () => { + await this.plugin.clear(); + return buildStaticSuperposition(this.plugin, StaticSuperpositionTestData); + }, + dynamicSuperposition: async () => { + await this.plugin.clear(); + return dynamicSuperpositionTest(this.plugin, ['1tqn', '2hhb', '4hhb'], 'HEM'); + }, + toggleValidationTooltip: () => { + return this.plugin.state.updateBehavior(PDBeStructureQualityReport, params => { params.showTooltip = !params.showTooltip }); + }, + showToasts: () => { + PluginCommands.Toast.Show(this.plugin, { + title: 'Toast 1', + message: 'This is an example text, timeout 3s', + key: 'toast-1', + timeoutMs: 3000 + }); + PluginCommands.Toast.Show(this.plugin, { + title: 'Toast 2', + message: CustomToastMessage, + key: 'toast-2' + }); + }, + hideToasts: () => { + PluginCommands.Toast.Hide(this.plugin, { key: 'toast-1' }); + PluginCommands.Toast.Hide(this.plugin, { key: 'toast-2' }); + } + } +} + +(window as any).BasicMolStarWrapper = new BasicWrapper(); \ No newline at end of file diff --git a/src/examples/basic-wrapper/superposition.ts b/src/examples/basic-wrapper/superposition.ts new file mode 100644 index 0000000000000000000000000000000000000000..9c45bc0391b5bae28bc76d93db28636114c3f5ba --- /dev/null +++ b/src/examples/basic-wrapper/superposition.ts @@ -0,0 +1,118 @@ +/** + * Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author David Sehnal <david.sehnal@gmail.com> + */ + +import { Mat4 } from '../../mol-math/linear-algebra'; +import { QueryContext, StructureSelection } from '../../mol-model/structure'; +import { superposeStructures } from '../../mol-model/structure/structure/util/superposition'; +import { PluginStateObject as PSO } from '../../mol-plugin-state/objects'; +import { PluginContext } from '../../mol-plugin/context'; +import { MolScriptBuilder as MS } from '../../mol-script/language/builder'; +import Expression from '../../mol-script/language/expression'; +import { compile } from '../../mol-script/runtime/query/compiler'; +import { StateObjectRef } from '../../mol-state'; +import { BuiltInTrajectoryFormat } from '../../mol-plugin-state/formats/trajectory'; +import { StateTransforms } from '../../mol-plugin-state/transforms'; + +export type SuperpositionTestInput = { + pdbId: string, + auth_asym_id: string, + matrix: Mat4 +}[]; + +export function buildStaticSuperposition(plugin: PluginContext, src: SuperpositionTestInput) { + return plugin.dataTransaction(async () => { + for (const s of src) { + const { structure } = await loadStructure(plugin, `https://www.ebi.ac.uk/pdbe/static/entry/${s.pdbId}_updated.cif`, 'mmcif'); + await transform(plugin, structure, s.matrix); + const chain = await plugin.builders.structure.tryCreateComponentFromExpression(structure, chainSelection(s.auth_asym_id), `Chain ${s.auth_asym_id}`); + if (chain) await plugin.builders.structure.representation.addRepresentation(chain, { type: 'cartoon' }); + } + }) +} + +export const StaticSuperpositionTestData: SuperpositionTestInput = [ + { + pdbId: '1aj5', auth_asym_id: 'A', matrix: Mat4.identity() + }, + { + pdbId: '1df0', auth_asym_id: 'B', matrix: Mat4.ofRows([ + [0.406, 0.879, 0.248, -200.633], + [0.693, -0.473, 0.544, 73.403], + [0.596, -0.049, -0.802, -14.209], + [0, 0, 0, 1]]) + }, + { + pdbId: '1dvi', auth_asym_id: 'A', matrix: Mat4.ofRows([ + [-0.053, -0.077, 0.996, -45.633], + [-0.312, 0.949, 0.057, -12.255], + [-0.949, -0.307, -0.074, 53.562], + [0, 0, 0, 1]]) + } +]; + +export function dynamicSuperpositionTest(plugin: PluginContext, src: string[], comp_id: string) { + return plugin.dataTransaction(async () => { + for (const s of src) { + await loadStructure(plugin, `https://www.ebi.ac.uk/pdbe/static/entry/${s}_updated.cif`, 'mmcif'); + } + + const pivot = MS.struct.filter.first([ + MS.struct.generator.atomGroups({ + 'residue-test': MS.core.rel.eq([MS.struct.atomProperty.macromolecular.label_comp_id(), comp_id]), + 'group-by': MS.struct.atomProperty.macromolecular.residueKey() + }) + ]); + + const rest = MS.struct.modifier.exceptBy({ + 0: MS.struct.modifier.includeSurroundings({ + 0: pivot, + radius: 5 + }), + by: pivot + }); + + const query = compile<StructureSelection>(pivot); + const xs = plugin.managers.structure.hierarchy.current.structures; + const selections = xs.map(s => StructureSelection.toLociWithCurrentUnits(query(new QueryContext(s.cell.obj!.data)))); + + const transforms = superposeStructures(selections); + + await siteVisual(plugin, xs[0].cell, pivot, rest); + for (let i = 1; i < selections.length; i++) { + await transform(plugin, xs[i].cell, transforms[i - 1].bTransform); + await siteVisual(plugin, xs[i].cell, pivot, rest); + } + }); +} + +async function siteVisual(plugin: PluginContext, s: StateObjectRef<PSO.Molecule.Structure>, pivot: Expression, rest: Expression) { + const center = await plugin.builders.structure.tryCreateComponentFromExpression(s, pivot, 'pivot'); + if (center) await plugin.builders.structure.representation.addRepresentation(center, { type: 'ball-and-stick', color: 'residue-name' }); + + const surr = await plugin.builders.structure.tryCreateComponentFromExpression(s, rest, 'rest'); + if (surr) await plugin.builders.structure.representation.addRepresentation(surr, { type: 'ball-and-stick', color: 'uniform', size: 'uniform', sizeParams: { value: 0.33 } }); +} + +async function loadStructure(plugin: PluginContext, url: string, format: BuiltInTrajectoryFormat, assemblyId?: string) { + const data = await plugin.builders.data.download({ url }); + const trajectory = await plugin.builders.structure.parseTrajectory(data, format); + const model = await plugin.builders.structure.createModel(trajectory); + const structure = await plugin.builders.structure.createStructure(model, assemblyId ? { name: 'assembly', params: { id: assemblyId } } : void 0); + + return { data, trajectory, model, structure }; +} + +function chainSelection(auth_asym_id: string) { + return MS.struct.generator.atomGroups({ + 'chain-test': MS.core.rel.eq([MS.struct.atomProperty.macromolecular.auth_asym_id(), auth_asym_id]) + }); +} + +function transform(plugin: PluginContext, s: StateObjectRef<PSO.Molecule.Structure>, matrix: Mat4) { + const b = plugin.state.data.build().to(s) + .insert(StateTransforms.Model.TransformStructureConformation, { transform: { name: 'matrix', params: matrix } }); + return plugin.runTask(plugin.state.data.updateTree(b)); +} \ No newline at end of file diff --git a/src/apps/domain-annotation-server/mapping.ts b/src/examples/domain-annotation-server/mapping.ts similarity index 100% rename from src/apps/domain-annotation-server/mapping.ts rename to src/examples/domain-annotation-server/mapping.ts diff --git a/src/apps/domain-annotation-server/schemas.ts b/src/examples/domain-annotation-server/schemas.ts similarity index 100% rename from src/apps/domain-annotation-server/schemas.ts rename to src/examples/domain-annotation-server/schemas.ts diff --git a/src/apps/domain-annotation-server/server.ts b/src/examples/domain-annotation-server/server.ts similarity index 100% rename from src/apps/domain-annotation-server/server.ts rename to src/examples/domain-annotation-server/server.ts diff --git a/src/apps/domain-annotation-server/test.ts b/src/examples/domain-annotation-server/test.ts similarity index 100% rename from src/apps/domain-annotation-server/test.ts rename to src/examples/domain-annotation-server/test.ts diff --git a/src/apps/demos/lighting/index.html b/src/examples/lighting/index.html similarity index 100% rename from src/apps/demos/lighting/index.html rename to src/examples/lighting/index.html diff --git a/src/examples/lighting/index.ts b/src/examples/lighting/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..3c52b3e5d855907624990ab0f96a990f73e8179a --- /dev/null +++ b/src/examples/lighting/index.ts @@ -0,0 +1,117 @@ +/** + * Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author Alexander Rose <alexander.rose@weirdbyte.de> + */ + +import { Canvas3DProps } from '../../mol-canvas3d/canvas3d'; +import { createPlugin, DefaultPluginSpec } from '../../mol-plugin'; +import { BuiltInTrajectoryFormat } from '../../mol-plugin-state/formats/trajectory'; +import { PluginCommands } from '../../mol-plugin/commands'; +import { PluginContext } from '../../mol-plugin/context'; +import './index.html'; +require('mol-plugin-ui/skin/light.scss') + +type LoadParams = { url: string, format?: BuiltInTrajectoryFormat, isBinary?: boolean, assemblyId?: string } + +type _Preset = Pick<Canvas3DProps, 'multiSample' | 'postprocessing' | 'renderer'> +type Preset = { [K in keyof _Preset]: Partial<_Preset[K]> } + +const Canvas3DPresets = { + illustrative: <Preset> { + multiSample: { + mode: 'temporal' as Canvas3DProps['multiSample']['mode'] + }, + postprocessing: { + occlusion: { name: 'on', params: { bias: 0.8, kernelSize: 6, radius: 64 } }, + outline: { name: 'on', params: { scale: 1, threshold: 0.8 } } + }, + renderer: { + ambientIntensity: 1, + lightIntensity: 0, + } + }, + occlusion: <Preset> { + multiSample: { + mode: 'temporal' as Canvas3DProps['multiSample']['mode'] + }, + postprocessing: { + occlusion: { name: 'on', params: { bias: 0.8, kernelSize: 6, radius: 64 } }, + outline: { name: 'off', params: { } } + }, + renderer: { + ambientIntensity: 0.4, + lightIntensity: 0.6, + } + }, + standard: <Preset> { + multiSample: { + mode: 'off' as Canvas3DProps['multiSample']['mode'] + }, + postprocessing: { + occlusion: { name: 'off', params: { } }, + outline: { name: 'off', params: { } } + }, + renderer: { + ambientIntensity: 0.4, + lightIntensity: 0.6, + } + } +} + +type Canvas3DPreset = keyof typeof Canvas3DPresets + +class LightingDemo { + plugin: PluginContext; + + init(target: string | HTMLElement) { + this.plugin = createPlugin(typeof target === 'string' ? document.getElementById(target)! : target, { + ...DefaultPluginSpec, + layout: { + initial: { + isExpanded: false, + showControls: false + }, + controls: { left: 'none', right: 'none', top: 'none', bottom: 'none' } + } + }); + + this.setPreset('illustrative'); + } + + setPreset(preset: Canvas3DPreset) { + const props = Canvas3DPresets[preset] + PluginCommands.Canvas3D.SetSettings(this.plugin, { settings: { + ...props, + multiSample: { + ...this.plugin.canvas3d!.props.multiSample, + ...props.multiSample + }, + renderer: { + ...this.plugin.canvas3d!.props.renderer, + ...props.renderer + }, + postprocessing: { + ...this.plugin.canvas3d!.props.postprocessing, + ...props.postprocessing + }, + }}); + } + + async load({ url, format = 'mmcif', isBinary = false, assemblyId = '' }: LoadParams) { + await this.plugin.clear(); + + const data = await this.plugin.builders.data.download({ url, isBinary }, { state: { isGhost: true } }); + const trajectory = await this.plugin.builders.structure.parseTrajectory(data, format); + const model = await this.plugin.builders.structure.createModel(trajectory); + const structure = await this.plugin.builders.structure.createStructure(model, assemblyId ? { name: 'assembly', params: { id: assemblyId } } : { name: 'deposited', params: { } }); + + const polymer = await this.plugin.builders.structure.tryCreateComponentStatic(structure, 'polymer'); + if (polymer) await this.plugin.builders.structure.representation.addRepresentation(polymer, { type: 'spacefill', color: 'illustrative' }); + + const ligand = await this.plugin.builders.structure.tryCreateComponentStatic(structure, 'ligand'); + if (ligand) await this.plugin.builders.structure.representation.addRepresentation(ligand, { type: 'ball-and-stick' }); + } +} + +(window as any).LightingDemo = new LightingDemo(); \ No newline at end of file diff --git a/src/mol-canvas3d/canvas3d.ts b/src/mol-canvas3d/canvas3d.ts index 1b282a4cd4c8952024ab0b096bc8035d76b37836..c2f6f1ba919dbd83f275fcf3dd6c8bdb6dd0ba2b 100644 --- a/src/mol-canvas3d/canvas3d.ts +++ b/src/mol-canvas3d/canvas3d.ts @@ -35,6 +35,7 @@ import { ImagePass, ImageProps } from './passes/image'; import { Sphere3D } from '../mol-math/geometry'; import { isDebugMode } from '../mol-util/debug'; import { CameraHelperParams } from './helper/camera-helper'; +import { produce } from 'immer'; export const Canvas3DParams = { camera: PD.Group({ @@ -93,9 +94,8 @@ interface Canvas3D { requestCameraReset(options?: { durationMs?: number, snapshot?: Partial<Camera.Snapshot> }): void readonly camera: Camera readonly boundingSphere: Readonly<Sphere3D> - downloadScreenshot(): void getPixelData(variant: GraphicsRenderVariant): PixelData - setProps(props: Partial<Canvas3DProps>): void + setProps(props: Partial<Canvas3DProps> | ((old: Canvas3DProps) => Partial<Canvas3DProps> | void)): void getImagePass(props: Partial<ImageProps>): ImagePass /** Returns a copy of the current Canvas3D instance props */ @@ -412,6 +412,31 @@ namespace Canvas3D { } } + function getProps(): Canvas3DProps { + const radius = scene.boundingSphere.radius > 0 + ? 100 - Math.round((camera.transition.target.radius / scene.boundingSphere.radius) * 100) + : 0 + + return { + camera: { + mode: camera.state.mode, + helper: { ...drawPass.props.cameraHelper } + }, + cameraFog: camera.state.fog > 0 + ? { name: 'on' as const, params: { intensity: camera.state.fog } } + : { name: 'off' as const, params: {} }, + cameraClipping: { far: camera.state.clipFar, radius }, + cameraResetDurationMs: p.cameraResetDurationMs, + transparentBackground: p.transparentBackground, + + postprocessing: { ...postprocessing.props }, + multiSample: { ...multiSample.props }, + renderer: { ...renderer.props }, + trackball: { ...controls.props }, + debug: { ...debugHelper.props } + } + } + handleResize() return { @@ -463,9 +488,6 @@ namespace Canvas3D { }, camera, boundingSphere: scene.boundingSphere, - downloadScreenshot: () => { - // TODO - }, getPixelData: (variant: GraphicsRenderVariant) => { switch (variant) { case 'color': return webgl.getDrawingBufferPixelData() @@ -477,7 +499,11 @@ namespace Canvas3D { }, didDraw, reprCount, - setProps: (props: Partial<Canvas3DProps>) => { + setProps: (properties) => { + const props: Partial<Canvas3DProps> = typeof properties === 'function' + ? produce(getProps(), properties) + : properties; + const cameraState: Partial<Camera.Snapshot> = Object.create(null) if (props.camera && props.camera.mode !== undefined && props.camera.mode !== camera.state.mode) { cameraState.mode = props.camera.mode diff --git a/src/mol-plugin-state/builder/structure.ts b/src/mol-plugin-state/builder/structure.ts index 0e7301343b287f31920853e626652d9b345cb6ca..82bb1af27c6d479f883a788893e5d16eeb6f0dad 100644 --- a/src/mol-plugin-state/builder/structure.ts +++ b/src/mol-plugin-state/builder/structure.ts @@ -149,12 +149,12 @@ export class StructureBuilder { }, key, params?.tags); } - tryCreateComponentStatic(structure: StateObjectRef<SO.Molecule.Structure>, type: StaticStructureComponentType, key: string, params?: { label?: string, tags?: string[] }) { + tryCreateComponentStatic(structure: StateObjectRef<SO.Molecule.Structure>, type: StaticStructureComponentType, params?: { label?: string, tags?: string[] }) { return this.tryCreateComponent(structure, { type: { name: 'static', params: type }, nullIfEmpty: true, label: (params?.label || '').trim() - }, key, params?.tags); + }, `static-${type}`, params?.tags); } tryCreateComponentFromSelection(structure: StateObjectRef<SO.Molecule.Structure>, selection: StructureSelectionQuery, key: string, params?: { label?: string, tags?: string[] }): Promise<StateObjectSelector<SO.Molecule.Structure> | undefined> { diff --git a/src/mol-plugin-state/builder/structure/representation-preset.ts b/src/mol-plugin-state/builder/structure/representation-preset.ts index 9e67d7dc857d22009337e6e2f78bfb3ee6dc351b..a9088281f6afe32730a01f5784ca224b3c539de7 100644 --- a/src/mol-plugin-state/builder/structure/representation-preset.ts +++ b/src/mol-plugin-state/builder/structure/representation-preset.ts @@ -245,7 +245,7 @@ const atomicDetail = StructureRepresentationPresetProvider({ }); export function presetStaticComponent(plugin: PluginContext, structure: StateObjectRef<PluginStateObject.Molecule.Structure>, type: StaticStructureComponentType, params?: { label?: string, tags?: string[] }) { - return plugin.builders.structure.tryCreateComponentStatic(structure, type, `static-${type}`, params); + return plugin.builders.structure.tryCreateComponentStatic(structure, type, params); } export function presetSelectionComponent(plugin: PluginContext, structure: StateObjectRef<PluginStateObject.Molecule.Structure>, query: keyof typeof Q, params?: { label?: string, tags?: string[] }) { diff --git a/src/mol-plugin-state/manager/structure/hierarchy.ts b/src/mol-plugin-state/manager/structure/hierarchy.ts index 3c2f4d284b572e974eed63e45dbd33e439a847bd..4eef242c5336979492db34327c679b2f57fff19f 100644 --- a/src/mol-plugin-state/manager/structure/hierarchy.ts +++ b/src/mol-plugin-state/manager/structure/hierarchy.ts @@ -82,7 +82,7 @@ export class StructureHierarchyManager extends PluginComponent { } private sync(notify: boolean) { - if (!notify && this.dataState.behaviors.isUpdating.value) return; + if (!notify && this.dataState.inUpdate) return; if (this.state.syncedTree === this.dataState.tree) { if (notify && !this.state.notified) { diff --git a/src/mol-plugin/behavior/static/misc.ts b/src/mol-plugin/behavior/static/misc.ts index b9b09330d6c08416c0d0947fbacba4a6d53aef3c..32f568479d7e4743a7e39ef6ab4e000a25d8017e 100644 --- a/src/mol-plugin/behavior/static/misc.ts +++ b/src/mol-plugin/behavior/static/misc.ts @@ -7,14 +7,21 @@ import { PluginContext } from '../../../mol-plugin/context'; import { PluginCommands } from '../../commands'; +import { DefaultCanvas3DParams } from '../../../mol-canvas3d/canvas3d'; export function registerDefault(ctx: PluginContext) { Canvas3DSetSettings(ctx); } export function Canvas3DSetSettings(ctx: PluginContext) { + PluginCommands.Canvas3D.ResetSettings.subscribe(ctx, () => { + ctx.canvas3d?.setProps(DefaultCanvas3DParams); + }); + PluginCommands.Canvas3D.SetSettings.subscribe(ctx, e => { + if (!ctx.canvas3d) return; + ctx.canvas3d?.setProps(e.settings); ctx.events.canvas3d.settingsUpdated.next(); - }) + }); } diff --git a/src/mol-plugin/commands.ts b/src/mol-plugin/commands.ts index be2cfa91432b791b7fd159c691705686ee524ce4..be1b855480a69be6d808cf14a05209beb3d543d8 100644 --- a/src/mol-plugin/commands.ts +++ b/src/mol-plugin/commands.ts @@ -70,6 +70,7 @@ export const PluginCommands = { } }, Canvas3D: { - SetSettings: PluginCommand<{ settings: Partial<Canvas3DProps> }>() + SetSettings: PluginCommand<{ settings: Partial<Canvas3DProps> | ((old: Canvas3DProps) => Partial<Canvas3DProps> | void) }>(), + ResetSettings: PluginCommand<{ }>() } } \ No newline at end of file diff --git a/src/mol-plugin/context.ts b/src/mol-plugin/context.ts index db6cc468b5dcd9be824bc6819a6cb835ccbad198..1fd0735ec242773a71ea7581e7a7e29e3e4c6375 100644 --- a/src/mol-plugin/context.ts +++ b/src/mol-plugin/context.ts @@ -8,7 +8,7 @@ import { setAutoFreeze } from 'immer'; import { List } from 'immutable'; import { merge } from 'rxjs'; -import { Canvas3D } from '../mol-canvas3d/canvas3d'; +import { Canvas3D, DefaultCanvas3DParams } from '../mol-canvas3d/canvas3d'; import { CustomProperty } from '../mol-model-props/common/custom-property'; import { Model, Structure } from '../mol-model/structure'; import { DataFormatRegistry } from '../mol-plugin-state/actions/data-format'; @@ -30,7 +30,7 @@ import { StateTransformParameters } from '../mol-plugin-ui/state/common'; import { Representation } from '../mol-repr/representation'; import { StructureRepresentationRegistry } from '../mol-repr/structure/registry'; import { VolumeRepresentationRegistry } from '../mol-repr/volume/registry'; -import { State, StateBuilder, StateTree } from '../mol-state'; +import { State, StateBuilder, StateTree, StateTransform } from '../mol-state'; import { Progress, Task } from '../mol-task'; import { ColorTheme } from '../mol-theme/color'; import { SizeTheme } from '../mol-theme/size'; @@ -234,6 +234,11 @@ export class PluginContext { this.tasks.requestAbort(progress, reason); } + clear(resetViewportSettings = false) { + if (resetViewportSettings) this.canvas3d?.setProps(DefaultCanvas3DParams); + return PluginCommands.State.RemoveObject(this, { state: this.state.data, ref: StateTransform.RootRef }); + } + dispose() { if (this.disposed) return; this.commands.dispose(); diff --git a/src/mol-state/state.ts b/src/mol-state/state.ts index 42554a7d7dc837ca2bb72609c03e3b3b3eb8c6d1..4383be5045aed5093b9775dc80391210ccf26c56 100644 --- a/src/mol-state/state.ts +++ b/src/mol-state/state.ts @@ -218,6 +218,13 @@ class State { }); } + private _inUpdate = false; + /** + * Determines whether the state is currently "inside" updateTree function. + * This is different from "isUpdating" which wraps entire transactions. + */ + get inUpdate() { return this._inUpdate; } + /** * Queues up a reconciliation of the existing state tree. * @@ -233,6 +240,8 @@ class State { const removed = await this.updateQueue.enqueue(params); if (!removed) return; + this._inUpdate = true; + const snapshot = options?.canUndo ? this._tree.asImmutable() : void 0; let reverted = false; @@ -248,6 +257,7 @@ class State { return ret.cell; } finally { + this._inUpdate = false; this.updateQueue.handled(params); if (!this.inTransaction) { this.behaviors.isUpdating.next(false); diff --git a/webpack.config.common.js b/webpack.config.common.js index e3c610c72b04cd52fa46ff710c085bdc52579e38..3834422caa0127e29daf99b4ebb98c38dfec0871 100644 --- a/webpack.config.common.js +++ b/webpack.config.common.js @@ -91,12 +91,14 @@ function createNodeEntryPoint(name, dir, out) { } function createApp(name) { return createEntryPoint('index', `apps/${name}`, name) } +function createExample(name) { return createEntry(`examples/${name}/index`, `examples/${name}`, 'index') } function createBrowserTest(name) { return createEntryPoint(name, 'tests/browser', 'tests') } function createNodeApp(name) { return createNodeEntryPoint('index', `apps/${name}`, name) } module.exports = { createApp, createEntry, + createExample, createBrowserTest, createNodeEntryPoint, createNodeApp diff --git a/webpack.config.js b/webpack.config.js index 349c8dd6e3831b434d6f4bf87a3895792df9912e..5412a363045d9c12fdf61d240e1a47832c122ebf 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -1,20 +1,15 @@ -const common = require('./webpack.config.common.js'); -const createApp = common.createApp; -const createEntry = common.createEntry; -const createBrowserTest = common.createBrowserTest; +const { createApp, createExample, createBrowserTest } = require('./webpack.config.common.js'); -module.exports = [ - createApp('viewer'), - createApp('basic-wrapper'), - createEntry('examples/proteopedia-wrapper/index', 'examples/proteopedia-wrapper', 'index'), - createEntry('apps/demos/lighting/index', 'demos/lighting', 'index'), +const apps = ['viewer']; +const examples = ['proteopedia-wrapper', 'basic-wrapper', 'lighting']; +const tests = [ + 'font-atlas', + 'marching-cubes', + 'render-lines', 'render-mesh', 'render-shape', 'render-spheres', 'render-structure', 'render-text' +]; - createBrowserTest('font-atlas'), - createBrowserTest('marching-cubes'), - createBrowserTest('render-lines'), - createBrowserTest('render-mesh'), - createBrowserTest('render-shape'), - createBrowserTest('render-spheres'), - createBrowserTest('render-structure'), - createBrowserTest('render-text'), +module.exports = [ + ...apps.map(createApp), + ...examples.map(createExample), + ...tests.map(createBrowserTest) ] \ No newline at end of file