From 9ee014bcffca2287af44b17fa7d99bf836bcc011 Mon Sep 17 00:00:00 2001 From: David Sehnal <david.sehnal@gmail.com> Date: Wed, 20 Feb 2019 10:59:13 +0100 Subject: [PATCH] mol-plugin: improved model index animation; added animation to BasicWrapper --- src/apps/basic-wrapper/index.html | 53 ++++++++++++++++++---- src/apps/basic-wrapper/index.ts | 9 ++++ src/mol-plugin/state/animation/built-in.ts | 13 ++++-- src/mol-plugin/state/animation/manager.ts | 13 +++++- src/mol-plugin/state/animation/model.ts | 12 ++--- 5 files changed, 81 insertions(+), 19 deletions(-) diff --git a/src/apps/basic-wrapper/index.html b/src/apps/basic-wrapper/index.html index b4a202a2d..38c2bf792 100644 --- a/src/apps/basic-wrapper/index.html +++ b/src/apps/basic-wrapper/index.html @@ -12,23 +12,39 @@ } #app { position: absolute; - left: 100px; + left: 160px; top: 100px; width: 600px; height: 600px; border: 1px solid #ccc; } + + #controls { + position: absolute; + width: 130px; + top: 10px; + left: 10px; + } + + #controls > button { + display: block; + width: 100%; + } + + #controls > hr { + margin: 5px 0; + } </style> <link rel="stylesheet" type="text/css" href="app.css" /> <script type="text/javascript" src="./index.js"></script> </head> <body> - <button id='spin'>Toggle Spin</button> - <button id='asym'>Load Asym Unit</button> - <button id='asm'>Load Assemly 1</button> + <div id='controls'> + + </div> <div id="app"></div> <script> - var pdbId = '5ire', assemblyId= '1'; + var pdbId = '1grm', assemblyId= '1'; var url = 'https://www.ebi.ac.uk/pdbe/static/entry/' + pdbId + '_updated.cif'; var format = 'cif'; @@ -40,9 +56,30 @@ BasicMolStarWrapper.load({ url: url, format: format, assemblyId: assemblyId }); BasicMolStarWrapper.toggleSpin(); - document.getElementById('spin').onclick = () => BasicMolStarWrapper.toggleSpin(); - document.getElementById('asym').onclick = () => BasicMolStarWrapper.load({ url: url, format: format }); - document.getElementById('asm').onclick = () => BasicMolStarWrapper.load({ url: url, format: format, assemblyId: assemblyId }); + addControl('Toggle Spin', () => BasicMolStarWrapper.toggleSpin()); + + addSeparator(); + + addControl('Load Asym Unit', () => BasicMolStarWrapper.load({ url: url, format: format })); + addControl('Load Assembly 1', () => BasicMolStarWrapper.load({ url: url, format: format, assemblyId: assemblyId })); + + addSeparator(); + + addControl('Play Forward', () => BasicMolStarWrapper.animate.modelIndex.forward(8 /** maxFPS */)); + addControl('Stop', () => BasicMolStarWrapper.animate.modelIndex.stop()); + addControl('Play Backward', () => BasicMolStarWrapper.animate.modelIndex.backward(8 /** maxFPS */)); + + function addControl(label, action) { + var btn = document.createElement('button'); + btn.onclick = action; + btn.innerText = label; + document.getElementById('controls').appendChild(btn); + } + + function addSeparator() { + var hr = document.createElement('hr'); + document.getElementById('controls').appendChild(hr); + } </script> </body> </html> \ No newline at end of file diff --git a/src/apps/basic-wrapper/index.ts b/src/apps/basic-wrapper/index.ts index b45753f91..f5104d8e6 100644 --- a/src/apps/basic-wrapper/index.ts +++ b/src/apps/basic-wrapper/index.ts @@ -13,6 +13,7 @@ import { StructureRepresentation3DHelpers } from 'mol-plugin/state/transforms/re import { Color } from 'mol-util/color'; import { StateTreeBuilder } from 'mol-state/tree/builder'; import { PluginStateObject as PSO } from 'mol-plugin/state/objects'; +import { AnimateModelIndex } from 'mol-plugin/state/animation/built-in'; require('mol-plugin/skin/light.scss') type SupportedFormats = 'cif' | 'pdb' @@ -98,6 +99,14 @@ class BasicWrapper { PluginCommands.Canvas3D.SetSettings.dispatch(this.plugin, { settings: { trackball: { ...trackball, spin: !trackball.spin } } }); if (!spinning) PluginCommands.Camera.Reset.dispatch(this.plugin, { }); } + + animate = { + modelIndex: { + forward: (maxFPS = 8) => { this.plugin.state.animation.play(AnimateModelIndex, { maxFPS: Math.max(0.5, maxFPS | 0), direction: 'forward' }) }, + backward: (maxFPS = 8) => { this.plugin.state.animation.play(AnimateModelIndex, { maxFPS: Math.max(0.5, maxFPS | 0), direction: 'backward' }) }, + stop: () => this.plugin.state.animation.stop() + } + } } (window as any).BasicMolStarWrapper = new BasicWrapper(); \ No newline at end of file diff --git a/src/mol-plugin/state/animation/built-in.ts b/src/mol-plugin/state/animation/built-in.ts index cc6ef04c6..4da3661e9 100644 --- a/src/mol-plugin/state/animation/built-in.ts +++ b/src/mol-plugin/state/animation/built-in.ts @@ -14,8 +14,11 @@ import { ParamDefinition } from 'mol-util/param-definition'; export const AnimateModelIndex = PluginStateAnimation.create({ name: 'built-in.animate-model-index', display: { name: 'Animate Model Index' }, - params: () => ({ maxFPS: ParamDefinition.Numeric(3, { min: 0.5, max: 30, step: 0.5 }) }), - initialState: () => ({ frame: 1 }), + params: () => ({ + direction: ParamDefinition.Select('forward', [['forward', 'Forward'], ['backward', 'Backward']]), + maxFPS: ParamDefinition.Numeric(3, { min: 0.5, max: 30, step: 0.5 }) + }), + initialState: () => ({ }), async apply(animState, t, ctx) { // limit fps if (t.current > 0 && t.current - t.lastApplied < 1000 / ctx.params.maxFPS) { @@ -28,19 +31,21 @@ export const AnimateModelIndex = PluginStateAnimation.create({ const update = state.build(); + const dir = ctx.params.direction === 'backward' ? -1 : 1; + for (const m of models) { const parent = StateSelection.findAncestorOfType(state.tree, state.cells, m.transform.ref, [PluginStateObject.Molecule.Trajectory]); if (!parent || !parent.obj) continue; const traj = parent.obj as PluginStateObject.Molecule.Trajectory; update.to(m.transform.ref).update(StateTransforms.Model.ModelFromTrajectory, old => { - let modelIndex = animState.frame % traj.data.length; + let modelIndex = (old.modelIndex + dir) % traj.data.length; if (modelIndex < 0) modelIndex += traj.data.length; return { modelIndex }; }); } await PluginCommands.State.Update.dispatch(ctx.plugin, { state, tree: update }); - return { kind: 'next', state: { frame: animState.frame + 1 } }; + return { kind: 'next', state: {} }; } }) \ No newline at end of file diff --git a/src/mol-plugin/state/animation/manager.ts b/src/mol-plugin/state/animation/manager.ts index 8ba64a749..7504d54bd 100644 --- a/src/mol-plugin/state/animation/manager.ts +++ b/src/mol-plugin/state/animation/manager.ts @@ -13,6 +13,7 @@ export { PluginAnimationManager } // TODO: pause functionality (this needs to reset if the state tree changes) // TODO: handle unregistered animations on state restore +// TODO: better API class PluginAnimationManager extends PluginComponent<PluginAnimationManager.State> { private map = new Map<string, PluginStateAnimation>(); @@ -37,7 +38,7 @@ class PluginAnimationManager extends PluginComponent<PluginAnimationManager.Stat updateParams(newParams: Partial<PluginAnimationManager.State['params']>) { this.updateState({ params: { ...this.latestState.params, ...newParams } }); const anim = this.map.get(this.latestState.params.current)!; - const params = anim.params(this.context); + const params = anim.params(this.context) as PD.Params; this._current = { anim, params, @@ -69,6 +70,16 @@ class PluginAnimationManager extends PluginComponent<PluginAnimationManager.Stat } } + play<P>(animation: PluginStateAnimation<P>, params: P) { + this.stop(); + if (!this.map.has(animation.name)) { + this.register(animation); + } + this.updateParams({ current: animation.name }); + this.updateParams(params); + this.start(); + } + start() { this.updateState({ animationState: 'playing' }); this.triggerUpdate(); diff --git a/src/mol-plugin/state/animation/model.ts b/src/mol-plugin/state/animation/model.ts index 82ba43ca9..88d99c653 100644 --- a/src/mol-plugin/state/animation/model.ts +++ b/src/mol-plugin/state/animation/model.ts @@ -12,11 +12,11 @@ export { PluginStateAnimation } // TODO: helpers for building animations (once more animations are added) // for example "composite animation" -interface PluginStateAnimation<P extends PD.Params = any, S = any> { +interface PluginStateAnimation<P = any, S = any> { name: string, readonly display: { readonly name: string, readonly description?: string }, - params: (ctx: PluginContext) => P, - initialState(params: PD.Values<P>, ctx: PluginContext): S, + params: (ctx: PluginContext) => PD.For<P>, + initialState(params: P, ctx: PluginContext): S, /** * Apply the current frame and modify the state. @@ -38,12 +38,12 @@ namespace PluginStateAnimation { } export type ApplyResult<S> = { kind: 'finished' } | { kind: 'skip' } | { kind: 'next', state: S } - export interface Context<P extends PD.Params> { - params: PD.Values<P>, + export interface Context<P> { + params: P, plugin: PluginContext } - export function create<P extends PD.Params, S>(params: PluginStateAnimation<P, S>) { + export function create<P, S>(params: PluginStateAnimation<P, S>) { return params; } } \ No newline at end of file -- GitLab