From e2c9b601a64ecbb912f19e795666f7e1c8642524 Mon Sep 17 00:00:00 2001 From: Alexander Rose <alex.rose@rcsb.org> Date: Fri, 26 Jul 2019 16:36:27 -0700 Subject: [PATCH] wip, structure tools refactoring --- src/mol-plugin/ui/controls.tsx | 245 +----------------- src/mol-plugin/ui/structure/overpaint.tsx | 112 ++++++++ .../ui/structure/representation.tsx | 147 +++++++++++ src/mol-plugin/ui/structure/util.ts | 17 ++ 4 files changed, 279 insertions(+), 242 deletions(-) create mode 100644 src/mol-plugin/ui/structure/overpaint.tsx create mode 100644 src/mol-plugin/ui/structure/representation.tsx create mode 100644 src/mol-plugin/ui/structure/util.ts diff --git a/src/mol-plugin/ui/controls.tsx b/src/mol-plugin/ui/controls.tsx index b092ab5ed..d8c2af001 100644 --- a/src/mol-plugin/ui/controls.tsx +++ b/src/mol-plugin/ui/controls.tsx @@ -13,21 +13,11 @@ import { LociLabelEntry } from '../../mol-plugin/util/loci-label-manager'; import { IconButton, Icon } from './controls/common'; import { PluginStateObject } from '../../mol-plugin/state/objects'; import { StateTransforms } from '../../mol-plugin/state/transforms'; -import { StateTransformer, StateSelection, StateObjectCell, StateTransform, StateBuilder } from '../../mol-state'; +import { StateTransformer } from '../../mol-state'; import { ModelFromTrajectory } from '../../mol-plugin/state/transforms/model'; import { AnimationControls } from './state/animation'; -import { ParamDefinition as PD} from '../../mol-util/param-definition'; -import { ColorNames } from '../../mol-util/color/tables'; -import { ParameterControls } from './controls/parameters'; -import { formatMolScript } from '../../mol-script/language/expression-formatter'; -import { StructureElement, Structure, QueryContext, StructureSelection } from '../../mol-model/structure'; -import { isEmptyLoci, EmptyLoci } from '../../mol-model/loci'; -import { MolScriptBuilder } from '../../mol-script/language/builder'; -import { PluginContext } from '../context'; -import { StructureRepresentation3DHelpers } from '../state/transforms/representation'; -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 { OverpaintControls } from './structure/overpaint'; +import { RepresentationControls } from './structure/representation'; export class TrajectoryViewportControls extends PluginUIComponent<{}, { show: boolean, label: string }> { state = { show: false, label: '' } @@ -264,235 +254,6 @@ export class LociLabelControl extends PluginUIComponent<{}, { entries: ReadonlyA } } -// - -function getExpression(loci: StructureElement.Loci | EmptyLoci) { - const scriptExpression = isEmptyLoci(loci) - ? MolScriptBuilder.struct.generator.empty() - : StructureElement.Loci.toScriptExpression(loci) - return formatMolScript(scriptExpression) -} - -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 class OverpaintControls extends PluginUIComponent<{}, { params: PD.Values<ReturnType<typeof OverpaintControls.getParams>> }> { - state = { params: PD.getDefaultValues(OverpaintControls.getParams(this.plugin)) } - - static getParams = (plugin: PluginContext) => { - const { types } = plugin.structureRepresentation.registry - return { - color: PD.Color(ColorNames.cyan), - type: PD.MultiSelect(types.map(t => t[0]), types) - } - } - - componentDidMount() { - - } - - 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 }); - } - }) - } - - add = async () => { - this.set(false) - } - - clear = async () => { - this.set(true) - } - - clearAll = async () => { - await this.eachRepr((update, repr, rootStructure, overpaint) => { - if (overpaint) update.delete(overpaint.transform.ref) - }) - } - - render() { - return <div className='msp-transform-wrapper'> - <div className='msp-transform-header'> - <button className='msp-btn msp-btn-block'>Current Selection Overpaint</button> - </div> - <div> - <ParameterControls params={OverpaintControls.getParams(this.plugin)} values={this.state.params} onChange={p => { - const params = { ...this.state.params, [p.name]: p.value }; - this.setState({ params }); - }}/> - - <div className='msp-btn-row-group'> - <button className='msp-btn msp-btn-block msp-form-control' onClick={this.add}>Add</button> - <button className='msp-btn msp-btn-block msp-form-control' onClick={this.clear}>Clear</button> - <button className='msp-btn msp-btn-block msp-form-control' onClick={this.clearAll}>Clear All</button> - </div> - </div> - </div> - } -} - -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 RepresentationControls extends PluginUIComponent<{}, { params: PD.Values<ReturnType<typeof RepresentationControls.getParams>> }> { - state = { params: PD.getDefaultValues(RepresentationControls.getParams(this.plugin)) } - - static getParams = (plugin: PluginContext) => { - const { types } = plugin.structureRepresentation.registry - return { - type: PD.Select(types[0][0], types) - } - } - - componentDidMount() { - - } - - 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) - ) - } - }) - } - - 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)); - - await this.plugin.runTask(state.updateTree(update, { doNotUpdateCurrent: true })); - } - - render() { - return <div className='msp-transform-wrapper'> - <div className='msp-transform-header'> - <button className='msp-btn msp-btn-block'>Current Selection Representation</button> - </div> - <div> - <ParameterControls params={RepresentationControls.getParams(this.plugin)} values={this.state.params} onChange={p => { - const params = { ...this.state.params, [p.name]: p.value }; - this.setState({ params }); - }}/> - - <div className='msp-btn-row-group'> - <button className='msp-btn msp-btn-block msp-form-control' onClick={this.show}>Show</button> - <button className='msp-btn msp-btn-block msp-form-control' onClick={this.hide}>Hide</button> - <button className='msp-btn msp-btn-block msp-form-control' onClick={this.only}>Only</button> - <button className='msp-btn msp-btn-block msp-form-control' onClick={this.showAll}>Show All</button> - <button className='msp-btn msp-btn-block msp-form-control' onClick={this.hideAll}>Hide All</button> - </div> - </div> - </div> - } -} - export class StructureToolsWrapper extends PluginUIComponent { render() { return <div> diff --git a/src/mol-plugin/ui/structure/overpaint.tsx b/src/mol-plugin/ui/structure/overpaint.tsx new file mode 100644 index 000000000..12f845a8d --- /dev/null +++ b/src/mol-plugin/ui/structure/overpaint.tsx @@ -0,0 +1,112 @@ +/** + * Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author Alexander Rose <alexander.rose@weirdbyte.de> + */ + +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' + +export class OverpaintControls extends PluginUIComponent<{}, { params: PD.Values<ReturnType<typeof OverpaintControls.getParams>> }> { + state = { params: PD.getDefaultValues(OverpaintControls.getParams(this.plugin)) } + + static getParams = (plugin: PluginContext) => { + const { types } = plugin.structureRepresentation.registry + return { + color: PD.Color(ColorNames.cyan), + type: PD.MultiSelect(types.map(t => t[0]), types) + } + } + + componentDidMount() { + + } + + 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 }); + } + }) + } + + add = async () => { + this.set(false) + } + + clear = async () => { + this.set(true) + } + + clearAll = async () => { + await this.eachRepr((update, repr, rootStructure, overpaint) => { + if (overpaint) update.delete(overpaint.transform.ref) + }) + } + + render() { + return <div className='msp-transform-wrapper'> + <div className='msp-transform-header'> + <button className='msp-btn msp-btn-block'>Current Selection Overpaint</button> + </div> + <div> + <ParameterControls params={OverpaintControls.getParams(this.plugin)} values={this.state.params} onChange={p => { + const params = { ...this.state.params, [p.name]: p.value }; + this.setState({ params }); + }}/> + + <div className='msp-btn-row-group'> + <button className='msp-btn msp-btn-block msp-form-control' onClick={this.add}>Add</button> + <button className='msp-btn msp-btn-block msp-form-control' onClick={this.clear}>Clear</button> + <button className='msp-btn msp-btn-block msp-form-control' onClick={this.clearAll}>Clear All</button> + </div> + </div> + </div> + } +} \ No newline at end of file diff --git a/src/mol-plugin/ui/structure/representation.tsx b/src/mol-plugin/ui/structure/representation.tsx new file mode 100644 index 000000000..74d35f0be --- /dev/null +++ b/src/mol-plugin/ui/structure/representation.tsx @@ -0,0 +1,147 @@ +/** + * Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author Alexander Rose <alexander.rose@weirdbyte.de> + */ + +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 RepresentationControls extends PluginUIComponent<{}, { params: PD.Values<ReturnType<typeof RepresentationControls.getParams>> }> { + state = { params: PD.getDefaultValues(RepresentationControls.getParams(this.plugin)) } + + static getParams = (plugin: PluginContext) => { + const { types } = plugin.structureRepresentation.registry + return { + type: PD.Select(types[0][0], types) + } + } + + componentDidMount() { + + } + + 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) + ) + } + }) + } + + 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)); + + await this.plugin.runTask(state.updateTree(update, { doNotUpdateCurrent: true })); + } + + render() { + return <div className='msp-transform-wrapper'> + <div className='msp-transform-header'> + <button className='msp-btn msp-btn-block'>Current Selection Representation</button> + </div> + <div> + <ParameterControls params={RepresentationControls.getParams(this.plugin)} values={this.state.params} onChange={p => { + const params = { ...this.state.params, [p.name]: p.value }; + this.setState({ params }); + }}/> + + <div className='msp-btn-row-group'> + <button className='msp-btn msp-btn-block msp-form-control' onClick={this.show}>Show</button> + <button className='msp-btn msp-btn-block msp-form-control' onClick={this.hide}>Hide</button> + <button className='msp-btn msp-btn-block msp-form-control' onClick={this.only}>Only</button> + <button className='msp-btn msp-btn-block msp-form-control' onClick={this.showAll}>Show All</button> + <button className='msp-btn msp-btn-block msp-form-control' onClick={this.hideAll}>Hide All</button> + </div> + </div> + </div> + } +} \ No newline at end of file diff --git a/src/mol-plugin/ui/structure/util.ts b/src/mol-plugin/ui/structure/util.ts new file mode 100644 index 000000000..ca17b74fe --- /dev/null +++ b/src/mol-plugin/ui/structure/util.ts @@ -0,0 +1,17 @@ +/** + * 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 -- GitLab