Skip to content
Snippets Groups Projects
Commit 9ee014bc authored by David Sehnal's avatar David Sehnal
Browse files

mol-plugin: improved model index animation; added animation to BasicWrapper

parent c41d4656
No related branches found
No related tags found
No related merge requests found
......@@ -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
......@@ -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
......@@ -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
......@@ -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();
......
......@@ -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
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment