Newer
Older
/**
* Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
*/
import { Structure, StructureElement } from 'mol-model/structure';
import { PluginBehavior } from 'mol-plugin/behavior';
import { PluginCommands } from 'mol-plugin/command';
import { PluginContext } from 'mol-plugin/context';
import { PluginStateObject } from 'mol-plugin/state/objects';
import { StateTransforms } from 'mol-plugin/state/transforms';
import { StructureRepresentation3DHelpers } from 'mol-plugin/state/transforms/representation';
import { BuiltInStructureRepresentations } from 'mol-repr/structure/registry';
import { MolScriptBuilder as MS } from 'mol-script/language/builder';
David Sehnal
committed
import { StateObjectCell, StateSelection, StateTransform } from 'mol-state';
import { BuiltInColorThemes } from 'mol-theme/color';
import { BuiltInSizeThemes } from 'mol-theme/size';
import { ColorNames } from 'mol-util/color/tables';
import { ButtonsType } from 'mol-util/input/input-observer';
David Sehnal
committed
import { Representation } from 'mol-repr/representation';
type Params = { }
enum Tags {
Group = 'structure-interaction-group',
ResidueSel = 'structure-interaction-residue-sel',
ResidueRepr = 'structure-interaction-residue-repr',
SurrSel = 'structure-interaction-surr-sel',
SurrRepr = 'structure-interaction-surr-repr'
}
const TagSet: Set<Tags> = new Set([Tags.Group, Tags.ResidueSel, Tags.ResidueRepr, Tags.SurrSel, Tags.SurrRepr])
export class StructureRepresentationInteractionBehavior extends PluginBehavior.WithSubscribers<Params> {
private createResVisualParams(s: Structure) {
return StructureRepresentation3DHelpers.createParams(this.plugin, s, {
David Sehnal
committed
repr: BuiltInStructureRepresentations['ball-and-stick'],
size: [BuiltInSizeThemes.uniform, () => ({ value: 0.85 } )]
});
}
private createSurVisualParams(s: Structure) {
return StructureRepresentation3DHelpers.createParams(this.plugin, s, {
repr: BuiltInStructureRepresentations['ball-and-stick'],
color: [BuiltInColorThemes.uniform, () => ({ value: ColorNames.gray })],
David Sehnal
committed
size: [BuiltInSizeThemes.uniform, () => ({ value: 0.33 } )]
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
});
}
private ensureShape(cell: StateObjectCell<PluginStateObject.Molecule.Structure>) {
const state = this.plugin.state.dataState, tree = state.tree;
const builder = state.build();
const refs = StateSelection.findUniqueTagsInSubtree(tree, cell.transform.ref, TagSet);
if (!refs['structure-interaction-group']) {
refs['structure-interaction-group'] = builder.to(cell.transform.ref).group(StateTransforms.Misc.CreateGroup,
{ label: 'Current Interaction' }, { props: { tag: Tags.Group } }).ref;
}
// Selections
if (!refs[Tags.ResidueSel]) {
refs[Tags.ResidueSel] = builder.to(refs['structure-interaction-group']).apply(StateTransforms.Model.StructureSelection,
{ query: { } as any, label: 'Residue' }, { props: { tag: Tags.ResidueSel } }).ref;
}
if (!refs[Tags.SurrSel]) {
refs[Tags.SurrSel] = builder.to(refs['structure-interaction-group']).apply(StateTransforms.Model.StructureSelection,
{ query: { } as any, label: 'Surroundings' }, { props: { tag: Tags.SurrSel } }).ref;
}
// Representations
// TODO: ability to customize how it looks in the behavior params
if (!refs[Tags.ResidueRepr]) {
refs[Tags.ResidueRepr] = builder.to(refs['structure-interaction-residue-sel']!).apply(StateTransforms.Representation.StructureRepresentation3D,
this.createResVisualParams(cell.obj!.data), { props: { tag: Tags.ResidueRepr } }).ref;
}
if (!refs[Tags.SurrRepr]) {
refs[Tags.SurrRepr] = builder.to(refs['structure-interaction-surr-sel']!).apply(StateTransforms.Representation.StructureRepresentation3D,
this.createSurVisualParams(cell.obj!.data), { props: { tag: Tags.SurrRepr } }).ref;
}
return { state, builder, refs };
}
David Sehnal
committed
private clear(root: StateTransform.Ref) {
const state = this.plugin.state.dataState;
David Sehnal
committed
const groups = state.select(StateSelection.Generators.byRef(root).subtree().filter(o => o.transform.props.tag === Tags.Group));
if (groups.length === 0) return;
const update = state.build();
David Sehnal
committed
const query = MS.struct.generator.empty();
for (const g of groups) {
// TODO: update props of the group node to ghost
const res = StateSelection.findTagInSubtree(state.tree, g.transform.ref, Tags.ResidueSel);
const surr = StateSelection.findTagInSubtree(state.tree, g.transform.ref, Tags.SurrSel);
if (res) update.to(res).update(StateTransforms.Model.StructureSelection, old => ({ ...old, query }));
if (surr) update.to(surr).update(StateTransforms.Model.StructureSelection, old => ({ ...old, query }));
}
PluginCommands.State.Update.dispatch(this.plugin, { state, tree: update, options: { doNotLogTiming: true, doNotUpdateCurrent: true } });
}
David Sehnal
committed
let lastLoci: Representation.Loci = Representation.Loci.Empty;
this.subscribeObservable(this.plugin.events.state.object.removed, o => {
if (!PluginStateObject.Molecule.Structure.is(o.obj) || lastLoci.loci.kind !== 'element-loci') return;
if (lastLoci.loci.structure === o.obj.data) {
lastLoci = Representation.Loci.Empty;
}
});
this.subscribeObservable(this.plugin.events.state.object.updated, o => {
if (!PluginStateObject.Molecule.Structure.is(o.oldObj) || lastLoci.loci.kind !== 'element-loci') return;
if (lastLoci.loci.structure === o.oldObj.data) {
lastLoci = Representation.Loci.Empty;
}
});
this.subscribeObservable(this.plugin.behaviors.canvas3d.click, ({ current, buttons, modifiers }) => {
if (buttons !== ButtonsType.Flag.Secondary) return;
if (current.loci.kind === 'empty-loci') {
if (modifiers.control && buttons === ButtonsType.Flag.Secondary) {
David Sehnal
committed
this.clear(StateTransform.RootRef);
// TODO: support link loci as well?
if (!StructureElement.isLoci(current.loci)) return;
const parent = this.plugin.helpers.substructureParent.get(current.loci.structure);
if (!parent || !parent.obj) return;
David Sehnal
committed
if (Representation.Loci.areEqual(lastLoci, current)) {
lastLoci = Representation.Loci.Empty;
this.clear(parent.transform.ref);
return;
}
lastLoci = current;
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
const core = MS.struct.modifier.wholeResidues([
StructureElement.Loci.toScriptExpression(current.loci)
]);
const surroundings = MS.struct.modifier.exceptBy({
0: MS.struct.modifier.includeSurroundings({
0: core,
radius: 5,
'as-whole-residues': true
}),
by: core
});
const { state, builder, refs } = this.ensureShape(parent);
builder.to(refs[Tags.ResidueSel]!).update(StateTransforms.Model.StructureSelection, old => ({ ...old, query: core }));
builder.to(refs[Tags.SurrSel]!).update(StateTransforms.Model.StructureSelection, old => ({ ...old, query: surroundings }));
PluginCommands.State.Update.dispatch(this.plugin, { state, tree: builder, options: { doNotLogTiming: true, doNotUpdateCurrent: true } });
});
}
async update(params: Params) {
return false;
}
constructor(public plugin: PluginContext) {
super(plugin);
}
}
export const StructureRepresentationInteraction = PluginBehavior.create({
name: 'create-structure-representation-interaction',
display: { name: 'Structure Representation Interaction' },
category: 'interaction',
ctor: StructureRepresentationInteractionBehavior
});