From ee776e6e3e011cac738e2bc74053a419ba0c6435 Mon Sep 17 00:00:00 2001
From: Alexander Rose <alex.rose@rcsb.org>
Date: Fri, 2 Aug 2019 17:18:07 -0700
Subject: [PATCH] wip, structure tools

---
 .../behavior/dynamic/representation.ts        |   2 +-
 src/mol-plugin/context.ts                     |   8 +-
 src/mol-plugin/state/actions/structure.ts     |   2 +-
 src/mol-plugin/state/transforms/model.ts      |  11 +-
 src/mol-plugin/ui/structure/overpaint.tsx     |  65 ++---------
 .../ui/structure/representation.tsx           | 102 ++----------------
 src/mol-plugin/ui/structure/selection.tsx     |  54 ++--------
 src/mol-plugin/ui/structure/util.ts           |  17 ---
 src/mol-plugin/util/interactivity.ts          |   2 +-
 .../util/structure-overpaint-helper.ts        |  85 +++++++++++++++
 .../util/structure-representation-helper.ts   | 102 ++++++++++++++++++
 .../util/structure-selection-helper.ts        |  55 ++++++++++
 12 files changed, 289 insertions(+), 216 deletions(-)
 delete mode 100644 src/mol-plugin/ui/structure/util.ts
 create mode 100644 src/mol-plugin/util/structure-overpaint-helper.ts
 create mode 100644 src/mol-plugin/util/structure-representation-helper.ts
 create mode 100644 src/mol-plugin/util/structure-selection-helper.ts

diff --git a/src/mol-plugin/behavior/dynamic/representation.ts b/src/mol-plugin/behavior/dynamic/representation.ts
index edf502302..09fab1853 100644
--- a/src/mol-plugin/behavior/dynamic/representation.ts
+++ b/src/mol-plugin/behavior/dynamic/representation.ts
@@ -49,7 +49,7 @@ export const SelectLoci = PluginBehavior.create({
                     this.spine.current = cell
                     const so = this.spine.getRootOfType(SO.Molecule.Structure)
                     if (so) {
-                        const loci = this.ctx.helpers.structureSelection.get(so.data)
+                        const loci = this.ctx.helpers.structureSelectionManager.get(so.data)
                         this.lociMarkProvider({ loci }, MarkerAction.Select)
                     }
                 }
diff --git a/src/mol-plugin/context.ts b/src/mol-plugin/context.ts
index 024c4046d..e2b6b11a2 100644
--- a/src/mol-plugin/context.ts
+++ b/src/mol-plugin/context.ts
@@ -37,6 +37,9 @@ import { ModifiersKeys } from '../mol-util/input/input-observer';
 import { isProductionMode, isDebugMode } from '../mol-util/debug';
 import { Model, Structure } from '../mol-model/structure';
 import { Interactivity } from './util/interactivity';
+import { StructureRepresentationHelper } from './util/structure-representation-helper';
+import { StructureSelectionHelper } from './util/structure-selection-helper';
+import { StructureOverpaintHelper } from './util/structure-overpaint-helper';
 
 interface Log {
     entries: List<LogEntry>
@@ -121,7 +124,10 @@ export class PluginContext {
     readonly customParamEditors = new Map<string, StateTransformParameters.Class>();
 
     readonly helpers = {
-        structureSelection: new StructureElementSelectionManager(this),
+        structureSelectionManager: new StructureElementSelectionManager(this),
+        structureSelection: new StructureSelectionHelper(this),
+        structureRepresentation: new StructureRepresentationHelper(this),
+        structureOverpaint: new StructureOverpaintHelper(this),
         substructureParent: new SubstructureParentHelper(this)
     } as const;
 
diff --git a/src/mol-plugin/state/actions/structure.ts b/src/mol-plugin/state/actions/structure.ts
index 3ecad410b..76914d041 100644
--- a/src/mol-plugin/state/actions/structure.ts
+++ b/src/mol-plugin/state/actions/structure.ts
@@ -352,7 +352,7 @@ export const StructureFromSelection = StateAction.build({
     //     return t.transformer !== CustomModelProperties;
     // }
 })(({ a, ref, params, state }, plugin: PluginContext) => {
-    const sel = plugin.helpers.structureSelection.get(a.data);
+    const sel = plugin.helpers.structureSelectionManager.get(a.data);
     if (sel.kind === 'empty-loci') return Task.constant('', void 0);
 
     const query = StructureElement.Loci.toScriptExpression(sel);
diff --git a/src/mol-plugin/state/transforms/model.ts b/src/mol-plugin/state/transforms/model.ts
index 0813edd0c..ddc20811a 100644
--- a/src/mol-plugin/state/transforms/model.ts
+++ b/src/mol-plugin/state/transforms/model.ts
@@ -9,7 +9,7 @@ import { parsePDB } from '../../../mol-io/reader/pdb/parser';
 import { Vec3, Mat4, Quat } from '../../../mol-math/linear-algebra';
 import { trajectoryFromMmCIF } from '../../../mol-model-formats/structure/mmcif';
 import { trajectoryFromPDB } from '../../../mol-model-formats/structure/pdb';
-import { Model, ModelSymmetry, Queries, QueryContext, Structure, StructureQuery, StructureSelection as Sel, StructureSymmetry, QueryFn } from '../../../mol-model/structure';
+import { Model, ModelSymmetry, Queries, QueryContext, Structure, StructureQuery, StructureSelection as Sel, StructureSymmetry, QueryFn, StructureElement } from '../../../mol-model/structure';
 import { Assembly } from '../../../mol-model/structure/model/properties/symmetry';
 import { PluginContext } from '../../../mol-plugin/context';
 import { MolScriptBuilder } from '../../../mol-script/language/builder';
@@ -27,6 +27,7 @@ import { transpileMolScript } from '../../../mol-script/script/mol-script/symbol
 import { shapeFromPly } from '../../../mol-model-formats/shape/ply';
 import { SymmetryOperator } from '../../../mol-math/geometry';
 import { ensureSecondaryStructure } from './helpers';
+import { formatMolScript } from '../../../mol-script/language/expression-formatter';
 
 export { TrajectoryFromBlob };
 export { TrajectoryFromMmCif };
@@ -385,6 +386,14 @@ const UserStructureSelection = PluginStateTransform.BuiltIn({
         (cache as { source: Structure }).source = a.data;
         const result = compiled(new QueryContext(a.data));
         const s = Sel.unionStructure(result);
+
+        // TODO for debug purposes only, later move to StructureSelection where the expression is not visible to the user
+        let loci = Structure.toStructureElementLoci(Structure.Loci(s))
+        if (s.parent) loci = StructureElement.Loci.remap(loci, s.parent)
+        const expression = formatMolScript(StructureElement.Loci.toScriptExpression(loci))
+        console.log({ before: params.query.expression, after: expression })
+        params.query.expression = expression
+
         const props = { label: `${params.label || 'Selection'}`, description: structureDesc(s) };
         return new SO.Molecule.Structure(s, props);
     },
diff --git a/src/mol-plugin/ui/structure/overpaint.tsx b/src/mol-plugin/ui/structure/overpaint.tsx
index c158f2801..5e1070213 100644
--- a/src/mol-plugin/ui/structure/overpaint.tsx
+++ b/src/mol-plugin/ui/structure/overpaint.tsx
@@ -6,20 +6,11 @@
 
 import * as React from 'react';
 import { PluginUIComponent } from '../base';
-import { PluginStateObject } from '../../../mol-plugin/state/objects';
-import { StateTransforms } from '../../../mol-plugin/state/transforms';
-import { StateSelection, StateObjectCell, StateTransform, StateBuilder } from '../../../mol-state';
 import { ParamDefinition as PD} from '../../../mol-util/param-definition';
 import { ColorNames } from '../../../mol-util/color/tables';
 import { ParameterControls } from '../controls/parameters';
-import { Structure } from '../../../mol-model/structure';
-import { isEmptyLoci } from '../../../mol-model/loci';
 import { PluginContext } from '../../context';
-import { getExpression } from './util';
-
-
-type OverpaintEachReprCallback = (update: StateBuilder.Root, repr: StateObjectCell<PluginStateObject.Molecule.Structure.Representation3D, StateTransform<typeof StateTransforms.Representation.StructureRepresentation3D>>, rootStructure: Structure, overpaint?: StateObjectCell<any, StateTransform<typeof StateTransforms.Representation.OverpaintStructureRepresentation3D>>) => void
-const OverpaintManagerTag = 'overpaint-controls'
+import { Color } from '../../../mol-util/color';
 
 export class StructureOverpaintControls extends PluginUIComponent<{}, { params: PD.Values<ReturnType<typeof StructureOverpaintControls.getParams>> }> {
     state = { params: PD.getDefaultValues(StructureOverpaintControls.getParams(this.plugin)) }
@@ -36,58 +27,20 @@ export class StructureOverpaintControls extends PluginUIComponent<{}, { params:
 
     }
 
-    private async eachRepr(callback: OverpaintEachReprCallback) {
-        const state = this.plugin.state.dataState;
-        const reprs = state.select(StateSelection.Generators.ofType(PluginStateObject.Molecule.Structure.Representation3D));
-
-        const update = state.build();
-        for (const r of reprs) {
-            const overpaint = state.select(StateSelection.Generators.ofTransformer(StateTransforms.Representation.OverpaintStructureRepresentation3D, r.transform.ref).withTag(OverpaintManagerTag));
-
-            const structure = r.obj!.data.source.data
-            const rootStructure = structure.parent || structure
-
-            callback(update, r, rootStructure, overpaint[0])
-        }
-
-        await this.plugin.runTask(state.updateTree(update, { doNotUpdateCurrent: true }));
-    }
-
-    set = async (clear: boolean) => {
-        await this.eachRepr((update, repr, rootStructure, overpaint) => {
-            if (!this.state.params.type.includes(repr.params!.values.type.name)) return
-
-            const loci = this.plugin.helpers.structureSelection.get(rootStructure)
-            if (isEmptyLoci(loci) || loci.elements.length === 0) return
-            const expression = getExpression(loci)
-
-            const layer = {
-                script: { language: 'mol-script', expression },
-                color: this.state.params.color,
-                clear
-            }
-
-            if (overpaint) {
-                update.to(overpaint).update({ layers: [ ...overpaint.params!.values.layers, layer ], alpha: 1 })
-            } else {
-                update.to(repr.transform.ref)
-                    .apply(StateTransforms.Representation.OverpaintStructureRepresentation3D, { layers: [ layer ], alpha: 1 }, { tags: OverpaintManagerTag });
-            }
-        })
+    set = (color: Color | -1) => {
+        this.plugin.helpers.structureOverpaint.set(color, this.state.params.type)
     }
 
-    add = async () => {
-        this.set(false)
+    add = () => {
+        this.set(this.state.params.color)
     }
 
-    clear = async () => {
-        this.set(true)
+    clear = () => {
+        this.set(-1)
     }
 
-    clearAll = async () => {
-        await this.eachRepr((update, repr, rootStructure, overpaint) => {
-            if (overpaint) update.delete(overpaint.transform.ref)
-        })
+    clearAll = () => {
+        this.plugin.helpers.structureOverpaint.clearAll()
     }
 
     render() {
diff --git a/src/mol-plugin/ui/structure/representation.tsx b/src/mol-plugin/ui/structure/representation.tsx
index 54e06dfce..5e05374e2 100644
--- a/src/mol-plugin/ui/structure/representation.tsx
+++ b/src/mol-plugin/ui/structure/representation.tsx
@@ -6,35 +6,9 @@
 
 import * as React from 'react';
 import { PluginUIComponent } from '../base';
-import { PluginStateObject } from '../../../mol-plugin/state/objects';
-import { StateTransforms } from '../../../mol-plugin/state/transforms';
-import { StateTransformer, StateSelection, StateObjectCell, StateTransform, StateBuilder } from '../../../mol-state';
 import { ParamDefinition as PD} from '../../../mol-util/param-definition';
 import { ParameterControls } from '../controls/parameters';
-import { StructureElement, QueryContext, StructureSelection } from '../../../mol-model/structure';
-import { isEmptyLoci } from '../../../mol-model/loci';
 import { PluginContext } from '../../context';
-import { getExpression } from './util';
-import { parseMolScript } from '../../../mol-script/language/parser';
-import { transpileMolScript } from '../../../mol-script/script/mol-script/symbols';
-import { compile } from '../../../mol-script/runtime/query/compiler';
-import { StructureRepresentation3DHelpers } from '../../state/transforms/representation';
-
-type RepresentationEachStructureCallback = (update: StateBuilder.Root, structure: StateObjectCell<PluginStateObject.Molecule.Structure, StateTransform<StateTransformer<any, PluginStateObject.Molecule.Structure, any>>>) => void
-const RepresentationManagerTag = 'representation-controls'
-
-function getRepresentationManagerTag(type: string) {
-    return `${RepresentationManagerTag}-${type}`
-}
-
-function getCombinedLoci(mode: 'add' | 'remove' | 'only' | 'all', loci: StructureElement.Loci, currentLoci: StructureElement.Loci): StructureElement.Loci {
-    switch (mode) {
-        case 'add': return StructureElement.Loci.union(loci, currentLoci)
-        case 'remove': return StructureElement.Loci.subtract(currentLoci, loci)
-        case 'only': return loci
-        case 'all': return StructureElement.Loci.all(loci.structure)
-    }
-}
 
 export class StructureRepresentationControls extends PluginUIComponent<{}, { params: PD.Values<ReturnType<typeof StructureRepresentationControls.getParams>> }> {
     state = { params: PD.getDefaultValues(StructureRepresentationControls.getParams(this.plugin)) }
@@ -50,77 +24,17 @@ export class StructureRepresentationControls extends PluginUIComponent<{}, { par
 
     }
 
-    private async eachStructure(callback: RepresentationEachStructureCallback) {
-        const state = this.plugin.state.dataState;
-        const structures = state.select(StateSelection.Generators.rootsOfType(PluginStateObject.Molecule.Structure));
-
-        const update = state.build();
-        for (const s of structures) {
-            callback(update, s)
-        }
-
-        await this.plugin.runTask(state.updateTree(update, { doNotUpdateCurrent: true }));
-    }
-
-    set = async (mode: 'add' | 'remove' | 'only' | 'all') => {
-        const state = this.plugin.state.dataState
-        const { type } = this.state.params
-
-        await this.eachStructure((update, structure) => {
-            const s = structure.obj!.data
-            const _loci = this.plugin.helpers.structureSelection.get(s)
-            const loci = isEmptyLoci(_loci) ? StructureElement.Loci(s, []) : _loci
-
-            const selections = state.select(StateSelection.Generators.ofType(PluginStateObject.Molecule.Structure, structure.transform.ref).withTag(getRepresentationManagerTag(type)));
-
-            if (selections.length > 0) {
-                const parsed = parseMolScript(selections[0].params!.values.query.expression)
-                if (parsed.length === 0) return
-
-                const query = transpileMolScript(parsed[0])
-                const compiled = compile(query)
-                const result = compiled(new QueryContext(structure.obj!.data))
-                const currentLoci = StructureSelection.toLoci2(result)
-
-                const combinedLoci = getCombinedLoci(mode, loci, currentLoci)
-
-                update.to(selections[0]).update({
-                    ...selections[0].params!.values,
-                    query: { language: 'mol-script', expression: getExpression(combinedLoci) }
-                })
-            } else {
-                const combinedLoci = getCombinedLoci(mode, loci, StructureElement.Loci(loci.structure, []))
-
-                update.to(structure.transform.ref)
-                    .apply(
-                        StateTransforms.Model.UserStructureSelection,
-                        {
-                            query: { language: 'mol-script', expression: getExpression(combinedLoci) },
-                            label: type
-                        },
-                        { tags: [ RepresentationManagerTag, getRepresentationManagerTag(type) ] }
-                    )
-                    .apply(
-                        StateTransforms.Representation.StructureRepresentation3D,
-                        StructureRepresentation3DHelpers.getDefaultParams(this.plugin, type as any, s)
-                    )
-            }
-        })
+    set = (mode: 'add' | 'remove' | 'only' | 'all') => {
+        this.plugin.helpers.structureRepresentation.setSelected(mode, this.state.params.type)
     }
 
-    show = async () => { this.set('add') }
-    hide = async () => { this.set('remove') }
-    only = async () => { this.set('only') }
-    showAll = async () => { this.set('all') }
-
-    hideAll = async () => {
-        const { type } = this.state.params
-        const state = this.plugin.state.dataState;
-        const update = state.build();
-
-        state.select(StateSelection.Generators.ofType(PluginStateObject.Molecule.Structure).withTag(getRepresentationManagerTag(type))).forEach(structure => update.delete(structure.transform.ref));
+    show = () => { this.set('add') }
+    hide = () => { this.set('remove') }
+    only = () => { this.set('only') }
+    showAll = () => { this.set('all') }
 
-        await this.plugin.runTask(state.updateTree(update, { doNotUpdateCurrent: true }));
+    hideAll = () => {
+        this.plugin.helpers.structureRepresentation.hideAll(this.state.params.type)
     }
 
     render() {
diff --git a/src/mol-plugin/ui/structure/selection.tsx b/src/mol-plugin/ui/structure/selection.tsx
index 62b48e5fb..41c23ec88 100644
--- a/src/mol-plugin/ui/structure/selection.tsx
+++ b/src/mol-plugin/ui/structure/selection.tsx
@@ -6,22 +6,9 @@
 
 import * as React from 'react';
 import { PluginUIComponent } from '../base';
-import { MolScriptBuilder as MS } from '../../../mol-script/language/builder';
-import { StateSelection } from '../../../mol-state';
-import { PluginStateObject } from '../../state/objects';
-import { QueryContext, StructureSelection, QueryFn, Queries as _Queries } from '../../../mol-model/structure';
-import { compile } from '../../../mol-script/runtime/query/compiler';
-import { ButtonsType } from '../../../mol-util/input/input-observer';
-import { EmptyLoci } from '../../../mol-model/loci';
+import { StructureSelection, QueryFn, Queries as _Queries } from '../../../mol-model/structure';
 import { formatStructureSelectionStats } from '../../util/structure-element-selection';
-
-const Queries = {
-    all: () => compile<StructureSelection>(MS.struct.generator.all()),
-    polymers: () => _Queries.internal.atomicSequence(),
-    water: () => _Queries.internal.water(),
-    ligands: () => _Queries.internal.atomicHet(),
-    coarse: () => _Queries.internal.spheres(),
-}
+import { StructureSelectionQueries } from '../../util/structure-selection-helper';
 
 export class StructureSelectionControls extends PluginUIComponent<{}, {}> {
     state = {}
@@ -33,36 +20,15 @@ export class StructureSelectionControls extends PluginUIComponent<{}, {}> {
     }
 
     get stats() {
-        return formatStructureSelectionStats(this.plugin.helpers.structureSelection.stats)
+        return formatStructureSelectionStats(this.plugin.helpers.structureSelectionManager.stats)
     }
 
     select = (query: QueryFn<StructureSelection>) => {
-        const state = this.plugin.state.dataState
-        const structures = state.select(StateSelection.Generators.rootsOfType(PluginStateObject.Molecule.Structure))
-        const { structureSelection } = this.plugin.helpers
-
-        structureSelection.clear()
-        for (const so of structures) {
-            const s = so.obj!.data
-            const result = query(new QueryContext(s))
-            const loci = StructureSelection.toLoci2(result)
-
-            // TODO use better API when available
-            this.plugin.interactivity.lociSelections.apply({
-                current: { loci },
-                buttons: ButtonsType.Flag.Secondary,
-                modifiers: { shift: false, alt: false, control: true, meta: false }
-            })
-        }
+        this.plugin.helpers.structureSelection.select(query)
     }
 
     clear = () => {
-        // TODO use better API when available
-        this.plugin.interactivity.lociSelections.apply({
-            current: { loci: EmptyLoci },
-            buttons: ButtonsType.Flag.Secondary,
-            modifiers: { shift: false, alt: false, control: true, meta: false }
-        })
+        this.plugin.helpers.structureSelection.clearSelection()
     }
 
     render() {
@@ -75,14 +41,14 @@ export class StructureSelectionControls extends PluginUIComponent<{}, {}> {
                     <div>{this.stats}</div>
                 </div>
                 <div className='msp-btn-row-group'>
-                    <button className='msp-btn msp-btn-block msp-form-control' onClick={() => this.select(Queries.all())}>All</button>
+                    <button className='msp-btn msp-btn-block msp-form-control' onClick={() => this.select(StructureSelectionQueries.all())}>All</button>
                     <button className='msp-btn msp-btn-block msp-form-control' onClick={() => this.clear()}>None</button>
                 </div>
                 <div className='msp-btn-row-group'>
-                    <button className='msp-btn msp-btn-block msp-form-control' onClick={() => this.select(Queries.polymers())}>Polymers</button>
-                    <button className='msp-btn msp-btn-block msp-form-control' onClick={() => this.select(Queries.ligands())}>Ligands</button>
-                    <button className='msp-btn msp-btn-block msp-form-control' onClick={() => this.select(Queries.water())}>Water</button>
-                    <button className='msp-btn msp-btn-block msp-form-control' onClick={() => this.select(Queries.coarse())}>Coarse</button>
+                    <button className='msp-btn msp-btn-block msp-form-control' onClick={() => this.select(StructureSelectionQueries.polymers())}>Polymers</button>
+                    <button className='msp-btn msp-btn-block msp-form-control' onClick={() => this.select(StructureSelectionQueries.ligands())}>Ligands</button>
+                    <button className='msp-btn msp-btn-block msp-form-control' onClick={() => this.select(StructureSelectionQueries.water())}>Water</button>
+                    <button className='msp-btn msp-btn-block msp-form-control' onClick={() => this.select(StructureSelectionQueries.coarse())}>Coarse</button>
                 </div>
             </div>
         </div>
diff --git a/src/mol-plugin/ui/structure/util.ts b/src/mol-plugin/ui/structure/util.ts
deleted file mode 100644
index ca17b74fe..000000000
--- a/src/mol-plugin/ui/structure/util.ts
+++ /dev/null
@@ -1,17 +0,0 @@
-/**
- * Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
- *
- * @author Alexander Rose <alexander.rose@weirdbyte.de>
- */
-
-import { StructureElement } from '../../../mol-model/structure';
-import { EmptyLoci, isEmptyLoci } from '../../../mol-model/loci';
-import { MolScriptBuilder } from '../../../mol-script/language/builder';
-import { formatMolScript } from '../../../mol-script/language/expression-formatter';
-
-export function getExpression(loci: StructureElement.Loci | EmptyLoci) {
-    const scriptExpression = isEmptyLoci(loci)
-        ? MolScriptBuilder.struct.generator.empty()
-        : StructureElement.Loci.toScriptExpression(loci)
-    return formatMolScript(scriptExpression)
-}
\ No newline at end of file
diff --git a/src/mol-plugin/util/interactivity.ts b/src/mol-plugin/util/interactivity.ts
index bce545a96..80a5cc7f0 100644
--- a/src/mol-plugin/util/interactivity.ts
+++ b/src/mol-plugin/util/interactivity.ts
@@ -115,7 +115,7 @@ namespace Interactivity {
         abstract apply(e: MarkEvent): void
 
         constructor(public readonly ctx: PluginContext, props: Partial<Props> = {}) {
-            this.sel = ctx.helpers.structureSelection
+            this.sel = ctx.helpers.structureSelectionManager
             this.setProps(props)
         }
     }
diff --git a/src/mol-plugin/util/structure-overpaint-helper.ts b/src/mol-plugin/util/structure-overpaint-helper.ts
new file mode 100644
index 000000000..9bdbce04a
--- /dev/null
+++ b/src/mol-plugin/util/structure-overpaint-helper.ts
@@ -0,0 +1,85 @@
+/**
+ * Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
+ */
+
+import { PluginStateObject } from '../../mol-plugin/state/objects';
+import { StateTransforms } from '../../mol-plugin/state/transforms';
+import { StateSelection, StateObjectCell, StateTransform, StateBuilder } from '../../mol-state';
+import { Structure, StructureElement } from '../../mol-model/structure';
+import { isEmptyLoci, EmptyLoci } from '../../mol-model/loci';
+import { PluginContext } from '../context';
+import { Color } from '../../mol-util/color';
+import { MolScriptBuilder } from '../../mol-script/language/builder';
+import { formatMolScript } from '../../mol-script/language/expression-formatter';
+
+type OverpaintEachReprCallback = (update: StateBuilder.Root, repr: StateObjectCell<PluginStateObject.Molecule.Structure.Representation3D, StateTransform<typeof StateTransforms.Representation.StructureRepresentation3D>>, rootStructure: Structure, overpaint?: StateObjectCell<any, StateTransform<typeof StateTransforms.Representation.OverpaintStructureRepresentation3D>>) => void
+const OverpaintManagerTag = 'overpaint-controls'
+
+export function getExpression(loci: StructureElement.Loci | EmptyLoci) {
+    const scriptExpression = isEmptyLoci(loci)
+        ? MolScriptBuilder.struct.generator.empty()
+        : StructureElement.Loci.toScriptExpression(loci)
+    return formatMolScript(scriptExpression)
+}
+
+export class StructureOverpaintHelper {
+    private async eachRepr(callback: OverpaintEachReprCallback) {
+        const state = this.plugin.state.dataState;
+        const reprs = state.select(StateSelection.Generators.ofType(PluginStateObject.Molecule.Structure.Representation3D));
+
+        const update = state.build();
+        for (const r of reprs) {
+            const overpaint = state.select(StateSelection.Generators.ofTransformer(StateTransforms.Representation.OverpaintStructureRepresentation3D, r.transform.ref).withTag(OverpaintManagerTag));
+
+            const structure = r.obj!.data.source.data
+            const rootStructure = structure.parent || structure
+
+            callback(update, r, rootStructure, overpaint[0])
+        }
+
+        await this.plugin.runTask(state.updateTree(update, { doNotUpdateCurrent: true }));
+    }
+
+    async set(color: Color | -1, types?: string[]) {
+        await this.eachRepr((update, repr, rootStructure, overpaint) => {
+            if (types && !types.includes(repr.params!.values.type.name)) return
+
+            const loci = this.plugin.helpers.structureSelectionManager.get(rootStructure)
+            if (isEmptyLoci(loci) || loci.elements.length === 0) return
+            const expression = getExpression(loci)
+
+            const layer = {
+                script: { language: 'mol-script', expression },
+                color: color === -1 ? Color(0) : color,
+                clear: color === -1
+            }
+
+            if (overpaint) {
+                update.to(overpaint).update({ layers: [ ...overpaint.params!.values.layers, layer ], alpha: 1 })
+            } else {
+                update.to(repr.transform.ref)
+                    .apply(StateTransforms.Representation.OverpaintStructureRepresentation3D, { layers: [ layer ], alpha: 1 }, { tags: OverpaintManagerTag });
+            }
+        })
+    }
+
+    add(color: Color, types?: string[]) {
+        this.set(color, types)
+    }
+
+    clear(types?: string[]) {
+        this.set(-1, types)
+    }
+
+    clearAll() {
+        this.eachRepr((update, repr, rootStructure, overpaint) => {
+            if (overpaint) update.delete(overpaint.transform.ref)
+        })
+    }
+
+    constructor(private plugin: PluginContext) {
+
+    }
+}
\ No newline at end of file
diff --git a/src/mol-plugin/util/structure-representation-helper.ts b/src/mol-plugin/util/structure-representation-helper.ts
new file mode 100644
index 000000000..4ada5f0db
--- /dev/null
+++ b/src/mol-plugin/util/structure-representation-helper.ts
@@ -0,0 +1,102 @@
+/**
+ * Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
+ */
+
+import { PluginStateObject } from '../../mol-plugin/state/objects';
+import { StateTransforms } from '../../mol-plugin/state/transforms';
+import { StateTransformer, StateSelection, StateObjectCell, StateTransform } from '../../mol-state';
+import { StructureElement } from '../../mol-model/structure';
+import { isEmptyLoci } from '../../mol-model/loci';
+import { PluginContext } from '../context';
+import { parseMolScript } from '../../mol-script/language/parser';
+import { StructureRepresentation3DHelpers } from '../state/transforms/representation';
+import Expression from '../../mol-script/language/expression';
+import { formatMolScript } from '../../mol-script/language/expression-formatter';
+import { MolScriptBuilder as MS } from '../../mol-script/language/builder';
+
+type StructureTransform = StateObjectCell<PluginStateObject.Molecule.Structure, StateTransform<StateTransformer<any, PluginStateObject.Molecule.Structure, any>>>
+const RepresentationManagerTag = 'representation-controls'
+
+function getRepresentationManagerTag(type: string) {
+    return `${RepresentationManagerTag}-${type}`
+}
+
+function getCombinedExpression(modifier: SelectionModifier, expression: Expression, currentExpression: Expression): Expression {
+    switch (modifier) {
+        case 'add': return MS.struct.combinator.merge([ currentExpression, expression ])
+        case 'remove': return MS.struct.modifier.exceptBy({ 0: currentExpression, by: expression })
+        case 'only': return expression
+        case 'all': return MS.struct.generator.all()
+    }
+}
+
+type SelectionModifier = 'add' | 'remove' | 'only' | 'all'
+
+export class StructureRepresentationHelper {
+    async set(modifier: SelectionModifier, type: string, expression: Expression, structure: StructureTransform) {
+        const state = this.plugin.state.dataState
+        const update = state.build();
+        const s = structure.obj!.data
+
+        const selections = state.select(StateSelection.Generators.ofType(PluginStateObject.Molecule.Structure, structure.transform.ref).withTag(getRepresentationManagerTag(type)));
+
+        if (selections.length > 0) {
+            const parsedExpressions = parseMolScript(selections[0].params!.values.query.expression)
+            if (parsedExpressions.length === 0) return
+            const currentExpression = parsedExpressions[0]
+            const combinedExpression = getCombinedExpression(modifier, expression, currentExpression)
+
+            update.to(selections[0]).update({
+                ...selections[0].params!.values,
+                query: { language: 'mol-script', expression: formatMolScript(combinedExpression) }
+            })
+        } else {
+            const combinedExpression = getCombinedExpression(modifier, expression, MS.struct.generator.empty())
+
+            update.to(structure.transform.ref)
+                .apply(
+                    StateTransforms.Model.UserStructureSelection,
+                    {
+                        query: { language: 'mol-script', expression: formatMolScript(combinedExpression) },
+                        label: type
+                    },
+                    { tags: [ RepresentationManagerTag, getRepresentationManagerTag(type) ] }
+                )
+                .apply(
+                    StateTransforms.Representation.StructureRepresentation3D,
+                    StructureRepresentation3DHelpers.getDefaultParams(this.plugin, type as any, s)
+                )
+        }
+
+        await this.plugin.runTask(state.updateTree(update, { doNotUpdateCurrent: true }));
+    }
+
+    async setSelected(modifier: SelectionModifier, type: string) {
+        const state = this.plugin.state.dataState;
+        const structures = state.select(StateSelection.Generators.rootsOfType(PluginStateObject.Molecule.Structure));
+
+        for (const structure of structures) {
+            const s = structure.obj!.data
+            const _loci = this.plugin.helpers.structureSelectionManager.get(s)
+            const loci = isEmptyLoci(_loci) ? StructureElement.Loci(s, []) : _loci
+            const expression = StructureElement.Loci.toScriptExpression(loci)
+
+            await this.set(modifier, type, expression, structure)
+        }
+    }
+
+    async hideAll(type: string) {
+        const state = this.plugin.state.dataState;
+        const update = state.build();
+
+        state.select(StateSelection.Generators.ofType(PluginStateObject.Molecule.Structure).withTag(getRepresentationManagerTag(type))).forEach(structure => update.delete(structure.transform.ref));
+
+        await this.plugin.runTask(state.updateTree(update, { doNotUpdateCurrent: true }));
+    }
+
+    constructor(private plugin: PluginContext) {
+
+    }
+}
\ No newline at end of file
diff --git a/src/mol-plugin/util/structure-selection-helper.ts b/src/mol-plugin/util/structure-selection-helper.ts
new file mode 100644
index 000000000..f159f000a
--- /dev/null
+++ b/src/mol-plugin/util/structure-selection-helper.ts
@@ -0,0 +1,55 @@
+/**
+ * Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
+ */
+
+import { MolScriptBuilder as MS } from '../../mol-script/language/builder';
+import { StateSelection } from '../../mol-state';
+import { PluginStateObject } from '../state/objects';
+import { QueryContext, StructureSelection, QueryFn, Queries as _Queries } from '../../mol-model/structure';
+import { compile } from '../../mol-script/runtime/query/compiler';
+import { ButtonsType } from '../../mol-util/input/input-observer';
+import { EmptyLoci } from '../../mol-model/loci';
+import { PluginContext } from '../context';
+
+export const StructureSelectionQueries = {
+    all: () => compile<StructureSelection>(MS.struct.generator.all()),
+    polymers: () => _Queries.internal.atomicSequence(),
+    water: () => _Queries.internal.water(),
+    ligands: () => _Queries.internal.atomicHet(),
+    coarse: () => _Queries.internal.spheres(),
+}
+
+export class StructureSelectionHelper {
+    select(query: QueryFn<StructureSelection>) {
+        const state = this.plugin.state.dataState
+        const structures = state.select(StateSelection.Generators.rootsOfType(PluginStateObject.Molecule.Structure))
+
+        for (const so of structures) {
+            const s = so.obj!.data
+            const result = query(new QueryContext(s))
+            const loci = StructureSelection.toLoci2(result)
+
+            // TODO use better API when available
+            this.plugin.interactivity.lociSelections.apply({
+                current: { loci },
+                buttons: ButtonsType.Flag.Secondary,
+                modifiers: { shift: false, alt: false, control: true, meta: false }
+            })
+        }
+    }
+
+    clearSelection() {
+        // TODO use better API when available
+        this.plugin.interactivity.lociSelections.apply({
+            current: { loci: EmptyLoci },
+            buttons: ButtonsType.Flag.Secondary,
+            modifiers: { shift: false, alt: false, control: true, meta: false }
+        })
+    }
+
+    constructor(private plugin: PluginContext) {
+
+    }
+}
\ No newline at end of file
-- 
GitLab