瀏覽代碼

motif preset

JonStargaryen 3 年之前
父節點
當前提交
ff046817b5

+ 28 - 288
src/viewer/helpers/preset.ts

@@ -5,8 +5,6 @@
  */
 
 import { PluginContext } from 'molstar/lib/mol-plugin/context';
-import { MolScriptBuilder as MS } from 'molstar/lib/mol-script/language/builder';
-import { Expression } from 'molstar/lib/mol-script/language/expression';
 import { ParamDefinition as PD } from 'molstar/lib/mol-util/param-definition';
 import { TrajectoryHierarchyPresetProvider } from 'molstar/lib/mol-plugin-state/builder/structure/hierarchy-preset';
 import { ValidationReportGeometryQualityPreset } from 'molstar/lib/extensions/rcsb/validation-report/behavior';
@@ -14,87 +12,24 @@ import { AssemblySymmetryPreset } from 'molstar/lib/extensions/rcsb/assembly-sym
 import { PluginStateObject } from 'molstar/lib/mol-plugin-state/objects';
 import { RootStructureDefinition } from 'molstar/lib/mol-plugin-state/helpers/root-structure';
 import { StructureRepresentationPresetProvider } from 'molstar/lib/mol-plugin-state/builder/structure/representation-preset';
-import {
-    Structure,
-    StructureSelection,
-    QueryContext,
-    StructureElement
-} from 'molstar/lib/mol-model/structure';
-import { compile } from 'molstar/lib/mol-script/runtime/query/compiler';
+import { StructureElement } from 'molstar/lib/mol-model/structure';
 import { InitVolumeStreaming } from 'molstar/lib/mol-plugin/behavior/dynamic/volume-streaming/transformers';
 import { ViewerState } from '../types';
 import {
     StateSelection,
     StateObjectSelector,
     StateObject,
-    StateTransformer,
-    StateObjectRef
+    StateTransformer
 } from 'molstar/lib/mol-state';
 import { VolumeStreaming } from 'molstar/lib/mol-plugin/behavior/dynamic/volume-streaming/behavior';
 import { Mat4 } from 'molstar/lib/mol-math/linear-algebra';
 import { CustomStructureProperties } from 'molstar/lib/mol-plugin-state/transforms/model';
 import { FlexibleStructureFromModel } from './superpose/flexible-structure';
-import { StructureRepresentationRegistry } from 'molstar/lib/mol-repr/structure/registry';
-import { StructureSelectionQueries as Q } from 'molstar/lib/mol-plugin-state/helpers/structure-selection-query';
 import { PluginCommands } from 'molstar/lib/mol-plugin/commands';
 import { InteractivityManager } from 'molstar/lib/mol-plugin-state/manager/interactivity';
 import { MembraneOrientationPreset } from 'molstar/lib/extensions/anvil/behavior';
-import { EmptyLoci, Loci } from 'molstar/lib/mol-model/loci';
-import { StructureComponentManager } from 'molstar/lib/mol-plugin-state/manager/structure/component';
-
-type Target = {
-    readonly auth_seq_id?: number
-    readonly label_seq_id?: number
-    readonly label_comp_id?: string
-    readonly label_asym_id?: string
-}
-
-function targetToTest(target: Target) {
-    const residueTests: Expression[] = [];
-    const tests: { 'residue-test': Expression, 'chain-test': Expression } = Object.create(null);
-
-    if (target.auth_seq_id) {
-        residueTests.push(MS.core.rel.eq([target.auth_seq_id, MS.ammp('auth_seq_id')]));
-    } else if (target.label_seq_id) {
-        residueTests.push(MS.core.rel.eq([target.label_seq_id, MS.ammp('label_seq_id')]));
-    }
-    if (target.label_comp_id) {
-        residueTests.push(MS.core.rel.eq([target.label_comp_id, MS.ammp('label_comp_id')]));
-    }
-    if (residueTests.length === 1) {
-        tests['residue-test'] = residueTests[0];
-    } else if (residueTests.length > 1) {
-        tests['residue-test'] = MS.core.logic.and(residueTests);
-    }
-
-    if (target.label_asym_id) {
-        tests['chain-test'] = MS.core.rel.eq([target.label_asym_id, MS.ammp('label_asym_id')]);
-    }
-    return tests;
-}
-
-function targetToExpression(target: Target): Expression {
-    const tests = targetToTest(target);
-    if (Object.keys(tests).length > 0) {
-        return MS.struct.modifier.union([
-            MS.struct.generator.atomGroups(tests)
-        ]);
-    } else {
-        return MS.struct.generator.empty;
-    }
-}
-
-function targetToLoci(target: Target, structure: Structure): StructureElement.Loci {
-    const expression = targetToExpression(target);
-    const query = compile<StructureSelection>(expression);
-    const selection = query(new QueryContext(structure));
-    return StructureSelection.toLociWithSourceUnits(selection);
-}
-
-type Range = {
-    label_asym_id: string
-    label_seq_id?: { beg: number, end?: number }
-}
+import { createSelectionExpression, Range, SelectionExpression, Target, targetToLoci, toRange } from './selection';
+import { RcsbSuperpositionRepresentationPreset } from './superpose/preset';
 
 type BaseProps = {
     assemblyId?: string
@@ -159,9 +94,10 @@ export type MotifProps = {
     color?: number
 } & BaseProps
 
-export type PresetProps = ValidationProps | StandardProps | SymmetryProps | FeatureProps | DensityProps | PropsetProps | MembraneProps | FeatureDensityProps | MotifProps | EmptyProps;
+export type PresetProps = ValidationProps | StandardProps | SymmetryProps | FeatureProps | DensityProps | PropsetProps |
+MembraneProps | FeatureDensityProps | MotifProps | EmptyProps;
 
-const RcsbParams = (a: PluginStateObject.Molecule.Trajectory | undefined, plugin: PluginContext) => ({
+const RcsbParams = () => ({
     preset: PD.Value<PresetProps>({ kind: 'standard', assemblyId: '' }, { isHidden: true })
 });
 
@@ -169,68 +105,10 @@ type StructureObject = StateObjectSelector<PluginStateObject.Molecule.Structure,
 
 const CommonParams = StructureRepresentationPresetProvider.CommonParams;
 
-const reprBuilder = StructureRepresentationPresetProvider.reprBuilder;
-const updateFocusRepr = StructureRepresentationPresetProvider.updateFocusRepr;
-
-type SelectionExpression = {
-    tag: string
-    type: StructureRepresentationRegistry.BuiltIn
-    label: string
-    expression: Expression
-};
-
-export const RcsbSuperpositionRepresentationPreset = StructureRepresentationPresetProvider({
-    id: 'preset-superposition-representation-rcsb',
-    display: {
-        group: 'Superposition',
-        name: 'Alignment',
-        description: 'Show representations based on the structural alignment data.'
-    },
-    params: () => ({
-        ...CommonParams,
-        selectionExpressions: PD.Value<SelectionExpression[]>([])
-    }),
-    async apply(ref, params, plugin) {
-
-        const structureCell = StateObjectRef.resolveAndCheck(plugin.state.data, ref);
-        if (!structureCell) return Object.create(null);
-
-        const structure = structureCell.obj!.data;
-        const cartoonProps = {sizeFactor: structure.isCoarseGrained ? 0.8 : 0.2};
-
-        let components = Object.create(null);
-        let representations = Object.create(null);
-        for (const expr of params.selectionExpressions) {
-
-            const comp = await plugin.builders.structure.tryCreateComponentFromExpression(structureCell, expr.expression, expr.label, { label: expr.label });
-            Object.assign(components, {[expr.label]: comp});
-
-            const { update, builder, typeParams, color } = reprBuilder(plugin, params);
-
-            let typeProps = {...typeParams};
-            if (expr.type === 'cartoon') {
-                Object.assign(typeProps, {...cartoonProps});
-            }
-
-            Object.assign(representations, {
-                [expr.label]: builder.buildRepresentation(update, comp, {type: expr.type,
-                    typeParams: typeProps, color: color as any}, { tag: expr.tag }),
-            });
-
-            await update.commit({ revertOnError: false });
-
-        }
-        // needed to apply same coloring scheme to focus representation
-        await updateFocusRepr(plugin, structure, params.theme?.focus?.name, params.theme?.focus?.params);
-
-        return representations;
-    }
-});
-
 export const RcsbPreset = TrajectoryHierarchyPresetProvider({
     id: 'preset-trajectory-rcsb',
     display: { name: 'RCSB' },
-    isApplicable: o => {
+    isApplicable: () => {
         return true;
     },
     params: RcsbParams,
@@ -256,11 +134,15 @@ export const RcsbPreset = TrajectoryHierarchyPresetProvider({
         let unitcell: StateObjectSelector | undefined = undefined;
         // If flexible transformation is allowed, we may need to create a single structure component
         // from transformed substructures
-        const allowsFlexTransform = p.kind === 'prop-set'/* || p.kind === 'motif' */;
+        const allowsFlexTransform = p.kind === 'prop-set';
         if (!allowsFlexTransform) {
             structure = await builder.createStructure(modelProperties || model, structureParams);
             structureProperties = await builder.insertStructureProperties(structure);
-            unitcell = await builder.tryCreateUnitcell(modelProperties, undefined, { isHidden: true });
+
+            // hide unit cell when dealing with motifs
+            if (p.kind !== 'motif') {
+                unitcell = await builder.tryCreateUnitcell(modelProperties, undefined, { isHidden: true });
+            }
         }
 
         let representation: StructureRepresentationPresetProvider.Result | undefined = undefined;
@@ -311,32 +193,19 @@ export const RcsbPreset = TrajectoryHierarchyPresetProvider({
                 theme: { globalName: 'superpose' as any, focus: { name: 'superpose' } },
                 selectionExpressions: selectionExpressions
             };
-            representation = await RcsbSuperpositionRepresentationPreset.apply(structure, params, plugin);
+            representation = await plugin.builders.structure.representation.applyPreset(structureProperties!, RcsbSuperpositionRepresentationPreset, params);
         } else if (p.kind === 'motif') {
-            // // This creates a single structure from selections/transformations as specified
-            // const _structure = plugin.state.data.build().to(modelProperties)
-            //     .apply(SubstructureFromModel, { targets: p.targets });
-            // structure = await _structure.commit();
-            //
-            // const _structureProperties = plugin.state.data.build().to(structure)
-            //     .apply(CustomStructureProperties);
-            // structureProperties = await _structureProperties.commit();
-            //
-            // // At this we have a structure that contains only the transformed substructres,
-            // // creating structure selections to have multiple components per each flexible part
-            // let selectionExpressions: SelectionExpression[] = [];
-            // for (const target of p.targets) {
-            //     selectionExpressions = selectionExpressions.concat(createSelectionExpression2(p.label, target));
-            // }
-            //
-            // const params = {
-            //     ignoreHydrogens: true,
-            //     quality: CommonParams.quality.defaultValue,
-            //     theme: { globalName: 'superpose' as any, focus: { name: 'superpose' } },
-            //     selectionExpressions: selectionExpressions
-            // };
-            // representation = await RcsbSuperpositionRepresentationPreset.apply(structure, params, plugin);
-            await select(plugin, { label: p.label, targets: p.targets, focus: false });
+            let selectionExpressions = createSelectionExpression(p.label, p.targets);
+            const globalExpressions = createSelectionExpression(p.label); // global reps, to be hidden
+            selectionExpressions = selectionExpressions.concat(globalExpressions.map(e => { return { ...e, isHidden: true }; }));
+
+            const params = {
+                ignoreHydrogens: true,
+                quality: CommonParams.quality.defaultValue,
+                theme: { globalName: 'superpose' as any, focus: { name: 'superpose' } },
+                selectionExpressions: selectionExpressions
+            };
+            representation = await plugin.builders.structure.representation.applyPreset(structureProperties!, RcsbSuperpositionRepresentationPreset, params);
         } else if (p.kind === 'validation') {
             representation = await plugin.builders.structure.representation.applyPreset(structureProperties!, ValidationReportGeometryQualityPreset);
 
@@ -356,7 +225,7 @@ export const RcsbPreset = TrajectoryHierarchyPresetProvider({
         }
 
         if ((p.kind === 'feature' || p.kind === 'feature-density') && structure?.obj) {
-            let loci = targetToLoci(p.target, structure.obj.data);
+            let loci = targetToLoci(p.target, structure!.obj.data);
             // if target is only defined by chain: then don't force first residue
             const chainMode = p.target.label_asym_id && !p.target.auth_seq_id && !p.target.label_seq_id && !p.target.label_comp_id;
             // HELP-16678: check for rare case where ligand is not present in requested assembly
@@ -366,7 +235,7 @@ export const RcsbPreset = TrajectoryHierarchyPresetProvider({
                 const s = selection.structures[0];
                 await plugin.managers.structure.hierarchy.updateStructure(s, { ...params, preset: { ...params.preset, assemblyId: void 0 } });
                 // update loci
-                loci = targetToLoci(p.target, structure.obj.data);
+                loci = targetToLoci(p.target, structure!.obj.data);
             }
             const target = chainMode ? loci : StructureElement.Loci.firstResidue(loci);
 
@@ -420,132 +289,3 @@ async function initVolumeStreaming(plugin: PluginContext, structure: StructureOb
         volume: false
     });
 }
-
-export function createSelectionExpression(entryId: string, range?: Range): SelectionExpression[] {
-    if (range) {
-        const residues: number[] = (range.label_seq_id) ? toRange(range.label_seq_id.beg, range.label_seq_id.end) : [];
-        const test = selectionTest(range.label_asym_id, residues);
-        const label = labelFromProps(entryId, range);
-        return [{
-            expression: MS.struct.generator.atomGroups(test),
-            label: `${label}`,
-            type: 'cartoon',
-            tag: 'polymer'
-        }];
-    } else {
-        return [
-            {
-                expression: Q.polymer.expression,
-                label: `${entryId} - Polymers`,
-                type: 'cartoon',
-                tag: 'polymer'
-            },
-            {
-                expression: Q.ligand.expression,
-                label: `${entryId} - Ligands`,
-                type: 'ball-and-stick',
-                tag: 'ligand'
-            },
-            {
-                expression: Q.ion.expression,
-                label: `${entryId} - Ions`,
-                type: 'ball-and-stick',
-                tag: 'ion'
-            },
-            {
-                expression: Q.branched.expression,
-                label: `${entryId} - Carbohydrates`,
-                type: 'carbohydrate',
-                tag: 'branched-snfg-3d'
-            },
-            {
-                expression: Q.lipid.expression,
-                label: `${entryId} - Lipids`,
-                type: 'ball-and-stick',
-                tag: 'lipid'
-            },
-            {
-                expression: Q.water.expression,
-                label: `${entryId} - Waters`,
-                type: 'ball-and-stick',
-                tag: 'water'
-            }
-        ];
-    }
-}
-
-// function createSelectionExpression2(label: string, target: Target): SelectionExpression {
-//     const test = targetToTest(target);
-//     return {
-//         expression: MS.struct.generator.atomGroups(test),
-//         label: label,
-//         type: 'ball-and-stick',
-//         tag: 'polymer'
-//     };
-// }
-
-export const selectionTest = (asymId: string, residues: number[]) => {
-    if (residues.length > 0) {
-        return {
-            'chain-test': MS.core.rel.eq([MS.ammp('label_asym_id'), asymId]),
-            'residue-test': MS.core.set.has([MS.set(...residues), MS.ammp('label_seq_id')])
-        };
-    } else {
-        return { 'chain-test': MS.core.rel.eq([MS.ammp('label_asym_id'), asymId]) };
-    }
-};
-
-export const toRange = (start: number, end?: number) => {
-    if (!end) return [start];
-    const b = start < end ? start : end;
-    const e = start < end ? end : start;
-    return [...Array(e - b + 1)].map((_, i) => b + i);
-};
-
-const labelFromProps = (entryId: string, range: Range) => {
-
-    const residues: number[] = (range.label_seq_id) ? toRange(range.label_seq_id.beg, range.label_seq_id.end) : [];
-    const label = entryId + (range.label_asym_id ? `.${range.label_asym_id}` : '') +
-        (residues && residues.length > 0 ? `:${residues[0]}` : '') +
-        (residues && residues.length > 1 ? `-${residues[residues.length - 1]}` : '');
-    return label;
-};
-
-async function select(plugin: PluginContext, params: { label: string, targets: Target[], focus?: boolean }) {
-    const loci = getLociForTargets(plugin, params.targets);
-    if (Loci.isEmpty(loci)) return;
-
-    plugin.managers.interactivity.lociSelects.selectOnly({ loci });
-    const defaultParams = StructureComponentManager.getAddParams(plugin, { allowNone: false, hideSelection: true, checkExisting: true });
-    const defaultValues = PD.getDefaultValues(defaultParams);
-    defaultValues.options = { label: params.label, checkExisting: true };
-    const values = { ...defaultValues, representation: 'ball-and-stick' };
-    const structures = plugin.managers.structure.hierarchy.getStructuresWithSelection();
-    await plugin.managers.structure.component.add(values, structures);
-    if (params.focus) plugin.managers.camera.focusLoci(loci);
-    plugin.managers.interactivity.lociSelects.deselect({ loci });
-}
-
-export function getLociForTargets(plugin: PluginContext, targets: Target[]): Loci {
-    if (!targets.length) return EmptyLoci;
-    const pivotIndex = plugin.managers.structure.hierarchy.selection.structures.length - 1;
-    const pivot = plugin.managers.structure.hierarchy.selection.structures[pivotIndex];
-    const assemblyRef = (pivot && pivot.cell.parent) ? pivot.cell.transform.ref : '';
-    const data = (plugin.state.data.select(assemblyRef)[0].obj as PluginStateObject.Molecule.Structure).data;
-    if (!data) return EmptyLoci;
-
-    const expressions = [];
-    for (let i = 0, l = targets.length; i < l; i++) {
-        const target = targets[i];
-        expressions.push(targetToExpression(target));
-    }
-    const composed = MS.struct.modifier.union([
-        expressions.length === 1
-            ? expressions[0]
-            : MS.struct.combinator.merge(expressions.map(q => MS.struct.modifier.union([ q ])))
-    ]);
-
-    const query = compile<StructureSelection>(composed);
-    const sel = query(new QueryContext(data));
-    return StructureSelection.toLociWithSourceUnits(sel);
-}

+ 175 - 0
src/viewer/helpers/selection.ts

@@ -0,0 +1,175 @@
+import { MolScriptBuilder as MS } from 'molstar/lib/mol-script/language/builder';
+import { StructureSelectionQueries as Q } from 'molstar/lib/mol-plugin-state/helpers/structure-selection-query';
+import { StructureRepresentationRegistry } from 'molstar/lib/mol-repr/structure/registry';
+import { Expression } from 'molstar/lib/mol-script/language/expression';
+import { QueryContext, Structure, StructureElement, StructureSelection } from 'molstar/lib/mol-model/structure';
+import { compile } from 'molstar/lib/mol-script/runtime/query/compiler';
+
+export type Target = {
+    readonly auth_seq_id?: number
+    readonly label_seq_id?: number
+    readonly label_comp_id?: string
+    readonly label_asym_id?: string
+    readonly operatorName?: string
+}
+
+export type Range = {
+    label_asym_id: string
+    label_seq_id?: { beg: number, end?: number }
+}
+
+export const toRange = (start: number, end?: number) => {
+    if (!end) return [start];
+    const b = start < end ? start : end;
+    const e = start < end ? end : start;
+    return [...Array(e - b + 1)].map((_, i) => b + i);
+};
+
+export type SelectionExpression = {
+    tag: string
+    type: StructureRepresentationRegistry.BuiltIn
+    label: string
+    expression: Expression
+    isHidden?: boolean
+};
+
+/**
+ * Convert a selection to an array of selection expressions.
+ * @param labelBase the base label that will appear in the UI (e.g., the entry ID)
+ * @param selection a selection by Range or a set of Targets
+ */
+export function createSelectionExpression(labelBase: string, selection?: Range | Target[]): SelectionExpression[] {
+    if (selection) {
+        if (selection.hasOwnProperty('label_asym_id') && selection.hasOwnProperty('label_seq_id')) {
+            const range = selection as Range;
+            const residues: number[] = (range.label_seq_id) ? toRange(range.label_seq_id.beg, range.label_seq_id.end) : [];
+            const test = rangeToTest(range.label_asym_id, residues);
+            const label = labelFromProps(labelBase, range);
+            return [{
+                expression: MS.struct.generator.atomGroups(test),
+                label: `${label}`,
+                type: 'cartoon',
+                tag: 'polymer'
+            }];
+        } else if (Array.isArray(selection)) {
+            const expression = targetsToExpression(selection);
+            return [{
+                expression: expression,
+                label: `${labelBase}`,
+                type: 'ball-and-stick',
+                tag: 'polymer'
+            }];
+        } else {
+            throw Error('Unable to handle selection: ' + selection);
+        }
+    } else {
+        return [
+            {
+                expression: Q.polymer.expression,
+                label: `${labelBase} - Polymers`,
+                type: 'cartoon',
+                tag: 'polymer'
+            },
+            {
+                expression: Q.ligand.expression,
+                label: `${labelBase} - Ligands`,
+                type: 'ball-and-stick',
+                tag: 'ligand'
+            },
+            {
+                expression: Q.ion.expression,
+                label: `${labelBase} - Ions`,
+                type: 'ball-and-stick',
+                tag: 'ion'
+            },
+            {
+                expression: Q.branched.expression,
+                label: `${labelBase} - Carbohydrates`,
+                type: 'carbohydrate',
+                tag: 'branched-snfg-3d'
+            },
+            {
+                expression: Q.lipid.expression,
+                label: `${labelBase} - Lipids`,
+                type: 'ball-and-stick',
+                tag: 'lipid'
+            },
+            {
+                expression: Q.water.expression,
+                label: `${labelBase} - Waters`,
+                type: 'ball-and-stick',
+                tag: 'water'
+            }
+        ];
+    }
+}
+
+const labelFromProps = (entryId: string, range: Range) => {
+    const residues: number[] = (range.label_seq_id) ? toRange(range.label_seq_id.beg, range.label_seq_id.end) : [];
+    return entryId + (range.label_asym_id ? `.${range.label_asym_id}` : '') +
+        (residues && residues.length > 0 ? `:${residues[0]}` : '') +
+        (residues && residues.length > 1 ? `-${residues[residues.length - 1]}` : '');
+};
+
+export function rangeToTest(asymId: string, residues: number[]) {
+    if (residues.length > 0) {
+        return {
+            'chain-test': MS.core.rel.eq([MS.ammp('label_asym_id'), asymId]),
+            'residue-test': MS.core.set.has([MS.set(...residues), MS.ammp('label_seq_id')])
+        };
+    } else {
+        return { 'chain-test': MS.core.rel.eq([MS.ammp('label_asym_id'), asymId]) };
+    }
+}
+
+export function targetToLoci(target: Target, structure: Structure): StructureElement.Loci {
+    const expression = targetToExpression(target);
+    const query = compile<StructureSelection>(expression);
+    const selection = query(new QueryContext(structure));
+    return StructureSelection.toLociWithSourceUnits(selection);
+}
+
+function targetsToExpression(targets: Target[]): Expression {
+    const expressions = targets.map(t => targetToExpression(t));
+    return MS.struct.combinator.merge(expressions);
+}
+
+function targetToExpression(target: Target): Expression {
+    const residueTests: Expression[] = [];
+    const chainTests: Expression[] = [];
+    const tests: { 'residue-test': Expression, 'chain-test': Expression } = Object.create(null);
+
+    if (target.auth_seq_id) {
+        residueTests.push(MS.core.rel.eq([target.auth_seq_id, MS.ammp('auth_seq_id')]));
+    } else if (target.label_seq_id) {
+        residueTests.push(MS.core.rel.eq([target.label_seq_id, MS.ammp('label_seq_id')]));
+    }
+    if (target.label_comp_id) {
+        residueTests.push(MS.core.rel.eq([target.label_comp_id, MS.ammp('label_comp_id')]));
+    }
+    if (residueTests.length === 1) {
+        tests['residue-test'] = residueTests[0];
+    } else if (residueTests.length > 1) {
+        tests['residue-test'] = MS.core.logic.and(residueTests);
+    }
+
+    if (target.label_asym_id) {
+        chainTests.push(MS.core.rel.eq([target.label_asym_id, MS.ammp('label_asym_id')]));
+    }
+    if (target.operatorName) {
+        chainTests.push(MS.core.rel.eq([target.operatorName, MS.acp('operatorName')]));
+    }
+    if (chainTests.length === 1) {
+        tests['chain-test'] = chainTests[0];
+    } else if (chainTests.length > 1) {
+        tests['chain-test'] = MS.core.logic.and(chainTests);
+    }
+
+    if (Object.keys(tests).length > 0) {
+        return MS.struct.modifier.union([
+            MS.struct.generator.atomGroups(tests)
+        ]);
+    } else {
+        return MS.struct.generator.empty;
+    }
+}

+ 3 - 2
src/viewer/helpers/superpose/flexible-structure.ts

@@ -3,10 +3,11 @@ import { RootStructureDefinition } from 'molstar/lib/mol-plugin-state/helpers/ro
 import { PluginContext } from 'molstar/lib/mol-plugin/context';
 import { Task } from 'molstar/lib/mol-task';
 import { ParamDefinition as PD } from 'molstar/lib/mol-util/param-definition';
-import { PropsetProps, selectionTest, toRange } from '../preset';
+import { PropsetProps } from '../preset';
 import { StructureQueryHelper } from 'molstar/lib/mol-plugin-state/helpers/structure-query';
 import { MolScriptBuilder as MS } from 'molstar/lib/mol-script/language/builder';
 import { StructureSelection, Structure } from 'molstar/lib/mol-model/structure';
+import { rangeToTest, toRange } from '../selection';
 
 export { FlexibleStructureFromModel as FlexibleStructureFromModel };
 type FlexibleStructureFromModel = typeof FlexibleStructureFromModel
@@ -36,7 +37,7 @@ const FlexibleStructureFromModel = PluginStateTransform.BuiltIn({
                     selectBlocks.push([]);
                 }
                 const residues: number[] = (p.label_seq_id) ? toRange(p.label_seq_id.beg, p.label_seq_id.end) : [];
-                const test = selectionTest(p.label_asym_id, residues);
+                const test = rangeToTest(p.label_asym_id, residues);
                 const expression = MS.struct.generator.atomGroups(test);
                 const { selection: sele } = StructureQueryHelper.createAndRun(base.data, expression);
                 const s = StructureSelection.unionStructure(sele);

+ 71 - 0
src/viewer/helpers/superpose/preset.ts

@@ -0,0 +1,71 @@
+/**
+ * Copyright (c) 2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author Yana Rose
+ */
+
+import { StructureRepresentationPresetProvider } from 'molstar/lib/mol-plugin-state/builder/structure/representation-preset';
+import { ParamDefinition as PD } from 'molstar/lib/mol-util/param-definition';
+import { SelectionExpression } from '../selection';
+import { StateObjectRef } from 'molstar/lib/mol-state';
+import reprBuilder = StructureRepresentationPresetProvider.reprBuilder;
+import updateFocusRepr = StructureRepresentationPresetProvider.updateFocusRepr;
+import { StateTransform } from 'molstar/lib/mol-state/transform';
+
+export const RcsbSuperpositionRepresentationPreset = StructureRepresentationPresetProvider({
+    id: 'preset-superposition-representation-rcsb',
+    display: {
+        group: 'Superposition',
+        name: 'Alignment',
+        description: 'Show representations based on the structural alignment data.'
+    },
+    params: () => ({
+        ...StructureRepresentationPresetProvider.CommonParams,
+        selectionExpressions: PD.Value<SelectionExpression[]>([])
+    }),
+    async apply(ref, params, plugin) {
+        const structureCell = StateObjectRef.resolveAndCheck(plugin.state.data, ref);
+        if (!structureCell) return {};
+
+        const structure = structureCell.obj!.data;
+        const cartoonProps = { sizeFactor: structure.isCoarseGrained ? 0.8 : 0.2 };
+
+        let components = Object.create(null);
+        let representations = Object.create(null);
+
+        for (const expr of params.selectionExpressions) {
+            const comp = await plugin.builders.structure.tryCreateComponentFromExpression(structureCell, expr.expression, expr.label, { label: expr.label });
+            Object.assign(components, { [expr.label]: comp });
+
+            const { update, builder, typeParams, color } = reprBuilder(plugin, params);
+
+            let typeProps = { ...typeParams };
+            if (expr.type === 'cartoon') {
+                Object.assign(typeProps, { ...cartoonProps });
+            }
+
+            Object.assign(representations, {
+                [expr.label]: builder.buildRepresentation(update, comp, {
+                    type: expr.type,
+                    typeParams: typeProps,
+                    color: color as any
+                }, {
+                    tag: expr.tag,
+                    // this only hides the visuals but the state UI will still indicate them as visible
+                    initialState: { isHidden: expr.isHidden || false }
+                })
+            });
+            // make sure UI state is consistent
+            if (comp?.cell?.state && expr.isHidden) {
+                StateTransform.assignState(comp?.cell?.state, { isHidden: true });
+            }
+
+            await update.commit({ revertOnError: false });
+        }
+
+        // needed to apply same coloring scheme to focus representation
+        await updateFocusRepr(plugin, structure, params.theme?.focus?.name, params.theme?.focus?.params);
+
+        return representations;
+    }
+});

+ 0 - 62
src/viewer/helpers/superpose/substructure.ts

@@ -1,62 +0,0 @@
-import { PluginStateTransform, PluginStateObject as SO } from 'molstar/lib/mol-plugin-state/objects';
-import { RootStructureDefinition } from 'molstar/lib/mol-plugin-state/helpers/root-structure';
-import { PluginContext } from 'molstar/lib/mol-plugin/context';
-import { Task } from 'molstar/lib/mol-task';
-import { ParamDefinition as PD } from 'molstar/lib/mol-util/param-definition';
-import { MotifProps, selectionTest } from '../preset';
-import { StructureQueryHelper } from 'molstar/lib/mol-plugin-state/helpers/structure-query';
-import { MolScriptBuilder as MS } from 'molstar/lib/mol-script/language/builder';
-import { StructureSelection, Structure } from 'molstar/lib/mol-model/structure';
-
-export const SubstructureFromModel = PluginStateTransform.BuiltIn({
-    name: 'substructure-from-model',
-    display: { name: 'Substructure', description: 'Create a molecular substructure.' },
-    from: SO.Molecule.Model,
-    to: SO.Molecule.Structure,
-    isDecorator: true,
-    params(a) {
-        return {
-            targets: PD.Value<MotifProps['targets']>([])
-        };
-    }
-})({
-    apply({ a, params }, plugin: PluginContext) {
-        return Task.create('Build Substructure', async ctx => {
-            const base = await RootStructureDefinition.create(plugin, ctx, a.data);
-            const { targets } = params;
-            if (!targets?.length) return base;
-
-            const selectChains: string[] = [];
-            const selectBlocks: Structure[][] = [];
-            for (const target of targets) {
-                const asymId = target.label_asym_id!;
-                if (!selectChains.includes(asymId)) {
-                    selectChains.push(asymId);
-                    selectBlocks.push([]);
-                }
-                const test = selectionTest(asymId, [target.label_seq_id!]);
-                const expression = MS.struct.generator.atomGroups(test);
-                const { selection: sele } = StructureQueryHelper.createAndRun(base.data, expression);
-                const s = StructureSelection.unionStructure(sele);
-                selectBlocks[selectChains.indexOf(asymId)].push(s);
-            }
-
-            const builder = Structure.Builder({ label: base.data.label });
-            for (const blocks of selectBlocks) {
-                builder.beginChainGroup();
-                for (const b of blocks) {
-                    const u = b.units[0];
-                    builder.addUnit(u.kind, u.model, u.conformation.operator, u.elements, u.traits, u.invariantId);
-                }
-                builder.endChainGroup();
-            }
-
-            const blockStructure = builder.getStructure();
-            console.log(base.data.label);
-            return new SO.Molecule.Structure(blockStructure, { label: base.data.label });
-        });
-    },
-    dispose({ b }) {
-        b?.data.customPropertyDescriptors.dispose();
-    }
-});

+ 86 - 11
src/viewer/index.html

@@ -88,12 +88,12 @@
 
             &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
 
-            Motifs
+            Superpose Motifs
             <button style="padding: 3px;" onclick="motifs()">4CHA | 6YIW</button>
             &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
 
-            Ligand Focus on wrong assembly
-            <button style="padding: 3px" onclick="ligandAssembly()">5RLA</button>
+            Superpose Propset
+            <button style="padding: 3px" onclick="propset()">4HHB | 1OJ6</button>
         </div>
         <script>
 
@@ -323,6 +323,18 @@
                             auth_seq_id: 502
                         }
                     }
+                },
+                {
+                    id: '5RL9',
+                    info: 'ligand focus on wrong assembly: Crystal Structure of SARS-CoV-2 helicase in complex with Z1703168683',
+                    props: {
+                        kind: 'feature',
+                        assemblyId: '1',
+                        // UR7 is not present in assembly 1
+                        target: {
+                            label_comp_id: 'UR7'
+                        }
+                    }
                 }
             ];
 
@@ -358,7 +370,8 @@
                 viewer.clear()
                     .then(function() {
                         return viewer.loadPdbIds([{
-                            pdbId: '4cha', props: {
+                            pdbId: '4cha',
+                            props: {
                                 label: '4CHA',
                                 kind: 'motif',
                                 assemblyId: '1',
@@ -369,8 +382,9 @@
                                 ]
                             }
                         }, {
-                            pdbId: '6yiw', props: {
-                                label: '6YIW: 0.26 RMSD',
+                            pdbId: '6yiw',
+                            props: {
+                                label: '6YIW #1',
                                 kind: 'motif',
                                 assemblyId: '1',
                                 targets: [
@@ -378,7 +392,8 @@
                                     { label_asym_id: 'A', label_seq_id: 84 },
                                     { label_asym_id: 'A', label_seq_id: 177 }
                                 ]
-                            }, matrix: [
+                            },
+                            matrix: [
                                 0.1651637134205112, 0.7020365618749254, 0.6927233311791812, 0,
                                 0.39076998819946046, 0.5983062863806071, -0.6995201240851049, 0,
                                 -0.9055494266420474, 0.3862308292566522, -0.17551633097799743, 0,
@@ -391,11 +406,71 @@
                     });
             }
 
-            function ligandAssembly() {
+            function propset() {
                 viewer.clear()
-                    .then(function() {
-                        // UR7 is not present in assembly 1
-                        return viewer.loadPdbId('5RL9', { kind: 'feature', assemblyId: '1', target: { label_comp_id: 'UR7' } });
+                    .then(function () {
+                        return viewer.loadPdbIds([{
+                            pdbId: '4HHB',
+                            props: {
+                                kind: 'prop-set',
+                                representation: [{
+                                    name: 'color',
+                                    positions: [{
+                                        label_asym_id: 'A',
+                                        label_seq_id: { beg: 4, end: 141 }
+                                    }],
+                                    value: 13203230
+                                }],
+                                selection: [{
+                                    label_asym_id: 'A',
+                                    label_seq_id: { beg: 4, end: 141 }
+                                }]
+                            }
+                        }, {
+                            pdbId: '1OJ6',
+                            props: {
+                                kind: 'prop-set',
+                                representation: [{
+                                    name: 'color',
+                                    positions: [{
+                                        label_asym_id: 'A',
+                                        label_seq_id: { beg: 3, end: 44 }
+                                    }, {
+                                        label_asym_id: 'A',
+                                        label_seq_id: { beg: 48, end: 49 }
+                                    }, {
+                                        label_asym_id: 'A',
+                                        label_seq_id: { beg: 54, end: 95 }
+                                    }, {
+                                        label_asym_id: 'A',
+                                        label_seq_id: { beg: 98, end: 149 }
+                                    }],
+                                    value: 4947916
+                                }],
+                                selection: [{
+                                    label_asym_id: 'A',
+                                    label_seq_id: { beg: 3, end: 44 }
+                                }, {
+                                    label_asym_id: 'A',
+                                    label_seq_id: { beg: 48, end: 49 }
+                                }, {
+                                    label_asym_id: 'A',
+                                    label_seq_id: { beg: 54, end: 95 }
+                                }, {
+                                    label_asym_id: 'A',
+                                    label_seq_id: { beg: 98, end: 149 }
+                                }]
+                            },
+                            matrix: [
+                                -0.6263111483773867, -0.38259812283613237, -0.6792297268380318, 0.0,
+                                0.3014888059527303, 0.6846150421427817, -0.6636314820418288, 0.0,
+                                0.7189150473480135, -0.6204199549290021, -0.313432982027257, 0.0,
+                                -30.829713890311414, 24.04442469172666, 55.437150645037654, 1.0
+                            ]
+                        }]);
+                    })
+                    .then(function () {
+                        viewer.resetCamera(0)
                     });
             }
         </script>