diff --git a/src/mol-model/structure/structure/element.ts b/src/mol-model/structure/structure/element.ts index db379cf93e76b8e56567412b55a79d71931059ec..e514c6b558405bcb276d994e5422dd3678909f35 100644 --- a/src/mol-model/structure/structure/element.ts +++ b/src/mol-model/structure/structure/element.ts @@ -15,6 +15,8 @@ import Structure from './structure'; import Unit from './unit'; import { Boundary } from './util/boundary'; import { StructureProperties } from '../structure'; +import { sortArray } from 'mol-data/util'; +import Expression from 'mol-script/language/expression'; interface StructureElement<U = Unit> { readonly kind: 'element-location', @@ -249,6 +251,8 @@ namespace StructureElement { console.warn('toScriptExpression is only supported for Structure with single model, returning empty expression.'); return MS.struct.generator.empty(); } + if (loci.elements.length === 0) return MS.struct.generator.empty(); + const sourceIndices = UniqueArray.create<number, number>(); const el = StructureElement.create(), p = StructureProperties.atom.sourceIndex; for (const e of loci.elements) { @@ -262,8 +266,43 @@ namespace StructureElement { UniqueArray.add(sourceIndices, idx, idx); } } + + const xs = sourceIndices.array; + sortArray(xs); + + const ranges: number[] = []; + const set: number[] = []; + + let i = 0, len = xs.length; + while (i < len) { + const start = i; + i++; + while (i < len && xs[i - 1] + 1 === xs[i]) i++; + const end = i; + // TODO: is this a good value? + if (end - start > 12) { + ranges[ranges.length] = xs[start]; + ranges[ranges.length] = xs[end - 1]; + } else { + for (let j = start; j < end; j++) { + set[set.length] = xs[j]; + } + } + } + + const siProp = MS.struct.atomProperty.core.sourceIndex(); + const tests: Expression[] = []; + + // TODO: add set.ofRanges constructor to MolQL??? + if (set.length > 0) { + tests[tests.length] = MS.core.set.has([MS.set.apply(null, set), siProp]); + } + for (let rI = 0, _rI = ranges.length / 2; rI < _rI; rI++) { + tests[tests.length] = MS.core.rel.inRange([siProp, ranges[2 * rI], ranges[2 * rI + 1]]); + } + return MS.struct.generator.atomGroups({ - 'atom-test': MS.core.set.has([MS.set.apply(null, sourceIndices.array), MS.struct.atomProperty.core.sourceIndex()]), + 'atom-test': tests.length > 1 ? MS.core.logic.or.apply(null, tests) : tests[0], 'group-by': 0 }); } diff --git a/src/mol-plugin/index.ts b/src/mol-plugin/index.ts index 91e102c327bbbdeae1112a966fe3c00ba90a1c6f..a81f06d2ab99c29dacf10305c5c80be9a2b79d1c 100644 --- a/src/mol-plugin/index.ts +++ b/src/mol-plugin/index.ts @@ -47,6 +47,8 @@ export const DefaultPluginSpec: PluginSpec = { PluginSpec.Action(StateTransforms.Representation.StructureRepresentation3D), PluginSpec.Action(StateTransforms.Representation.ExplodeStructureRepresentation3D), PluginSpec.Action(StateTransforms.Representation.VolumeRepresentation3D), + + PluginSpec.Action(StateActions.Structure.StructureFromSelection), ], behaviors: [ PluginSpec.Behavior(PluginBehaviors.Representation.HighlightLoci), diff --git a/src/mol-plugin/state/actions/structure.ts b/src/mol-plugin/state/actions/structure.ts index 0a5e93d99a5ae340aecfa9a967c671808f2f60ab..c4737d976ea37b9401586ca0520a6a96ebf85e82 100644 --- a/src/mol-plugin/state/actions/structure.ts +++ b/src/mol-plugin/state/actions/structure.ts @@ -12,10 +12,11 @@ import { PluginStateObject } from '../objects'; import { StateTransforms } from '../transforms'; import { Download } from '../transforms/data'; import { StructureRepresentation3DHelpers } from '../transforms/representation'; -import { CustomModelProperties } from '../transforms/model'; +import { CustomModelProperties, StructureSelection } from '../transforms/model'; import { DataFormatProvider } from './data-format'; import { FileInfo } from 'mol-util/file-info'; import { Task } from 'mol-task'; +import { StructureElement } from 'mol-model/structure'; export const MmcifProvider: DataFormatProvider<any> = { label: 'mmCIF', @@ -230,3 +231,21 @@ export const EnableModelCustomProps = StateAction.build({ const root = state.build().to(ref).insert(CustomModelProperties, params); return state.updateTree(root); }); + +export const StructureFromSelection = StateAction.build({ + display: { name: 'Selection Structure', description: 'Create a new Structure from the current selection.' }, + from: PluginStateObject.Molecule.Structure, + params: { + label: PD.Text() + } + // isApplicable(a, t, ctx: PluginContext) { + // return t.transformer !== CustomModelProperties; + // } +})(({ a, ref, params, state }, plugin: PluginContext) => { + const sel = plugin.helpers.structureSelection.get(a.data); + if (sel.kind === 'empty-loci') return Task.constant('', void 0); + + const query = StructureElement.Loci.toScriptExpression(sel); + const root = state.build().to(ref).apply(StructureSelection, { query, label: params.label }); + return state.updateTree(root); +});