فهرست منبع

Merge branch 'master' into fix-pdb-insCode-labelSeq

Alexander Rose 1 سال پیش
والد
کامیت
65b52c8ecd

+ 6 - 1
CHANGELOG.md

@@ -6,9 +6,14 @@ Note that since we don't clearly distinguish between a public and private interf
 
 ## [Unreleased]
 
-- Add `PluginContext.initialized` promise & support for it in the `Plugin` UI component.
 - Fix handling of PDB files with insertion codes (#945)
 
+## [v3.41.0] - 2023-10-15
+
+- Add `PluginContext.initialized` promise & support for it in the `Plugin` UI component.
+- Fix undesired interaction between settings panel and the panel on the right.
+- Add ability to customize server parameters for `RCSBAssemblySymmetry`.
+
 ## [v3.40.1] - 2023-09-30
 
 - Do not call `updateFocusRepr` if default `StructureFocusRepresentation` isn't present.

+ 2 - 2
package-lock.json

@@ -1,12 +1,12 @@
 {
   "name": "molstar",
-  "version": "3.40.1",
+  "version": "3.41.0",
   "lockfileVersion": 2,
   "requires": true,
   "packages": {
     "": {
       "name": "molstar",
-      "version": "3.40.1",
+      "version": "3.41.0",
       "license": "MIT",
       "dependencies": {
         "@types/argparse": "^2.0.11",

+ 1 - 1
package.json

@@ -1,6 +1,6 @@
 {
   "name": "molstar",
-  "version": "3.40.1",
+  "version": "3.41.0",
   "description": "A comprehensive macromolecular library.",
   "homepage": "https://github.com/molstar/molstar#readme",
   "repository": {

+ 7 - 0
src/apps/viewer/app.ts

@@ -51,6 +51,7 @@ import { Backgrounds } from '../../extensions/backgrounds';
 import { SbNcbrPartialCharges, SbNcbrPartialChargesPreset, SbNcbrPartialChargesPropertyProvider } from '../../extensions/sb-ncbr';
 import { wwPDBStructConnExtensionFunctions } from '../../extensions/wwpdb/struct-conn';
 import { wwPDBChemicalComponentDictionary } from '../../extensions/wwpdb/ccd/behavior';
+import { RCSBAssemblySymmetryConfig } from '../../extensions/rcsb/assembly-symmetry/behavior';
 
 export { PLUGIN_VERSION as version } from '../../mol-plugin/version';
 export { setDebugMode, setProductionMode, setTimingMode, consoleStats } from '../../mol-util/debug';
@@ -114,6 +115,9 @@ const DefaultViewerOptions = {
     emdbProvider: PluginConfig.Download.DefaultEmdbProvider.defaultValue,
     saccharideCompIdMapType: 'default' as SaccharideCompIdMapType,
     volumesAndSegmentationsDefaultServer: VolsegVolumeServerConfig.DefaultServer.defaultValue,
+    rcsbAssemblySymmetryDefaultServerType: RCSBAssemblySymmetryConfig.DefaultServerType.defaultValue,
+    rcsbAssemblySymmetryDefaultServerUrl: RCSBAssemblySymmetryConfig.DefaultServerUrl.defaultValue,
+    rcsbAssemblySymmetryApplyColors: RCSBAssemblySymmetryConfig.ApplyColors.defaultValue,
 };
 type ViewerOptions = typeof DefaultViewerOptions;
 
@@ -191,6 +195,9 @@ export class Viewer {
                 [PluginConfig.Structure.DefaultRepresentationPreset, ViewerAutoPreset.id],
                 [PluginConfig.Structure.SaccharideCompIdMapType, o.saccharideCompIdMapType],
                 [VolsegVolumeServerConfig.DefaultServer, o.volumesAndSegmentationsDefaultServer],
+                [RCSBAssemblySymmetryConfig.DefaultServerType, o.rcsbAssemblySymmetryDefaultServerType],
+                [RCSBAssemblySymmetryConfig.DefaultServerUrl, o.rcsbAssemblySymmetryDefaultServerUrl],
+                [RCSBAssemblySymmetryConfig.ApplyColors, o.rcsbAssemblySymmetryApplyColors],
             ]
         };
 

+ 41 - 14
src/extensions/rcsb/assembly-symmetry/behavior.ts

@@ -5,12 +5,13 @@
  */
 
 import { ParamDefinition as PD } from '../../../mol-util/param-definition';
-import { AssemblySymmetryProvider, AssemblySymmetry, AssemblySymmetryDataProvider } from './prop';
+import { AssemblySymmetryProvider, AssemblySymmetry, AssemblySymmetryDataProvider, AssemblySymmetryDataParams } from './prop';
 import { PluginBehavior } from '../../../mol-plugin/behavior/behavior';
 import { AssemblySymmetryParams, AssemblySymmetryRepresentation } from './representation';
 import { AssemblySymmetryClusterColorThemeProvider } from './color';
 import { PluginStateTransform, PluginStateObject } from '../../../mol-plugin-state/objects';
 import { Task } from '../../../mol-task';
+import { PluginConfigItem } from '../../../mol-plugin/config';
 import { PluginContext } from '../../../mol-plugin/context';
 import { StateTransformer, StateAction, StateObject, StateTransform, StateObjectRef } from '../../../mol-state';
 import { GenericRepresentationRef } from '../../../mol-plugin-state/manager/structure/hierarchy-state';
@@ -77,14 +78,15 @@ export const InitAssemblySymmetry3D = StateAction.build({
         description: 'Initialize Assembly Symmetry axes and cage. Data calculated with BioJava, obtained via RCSB PDB.'
     },
     from: PluginStateObject.Molecule.Structure,
-    isApplicable: (a) => AssemblySymmetry.isApplicable(a.data)
-})(({ a, ref, state }, plugin: PluginContext) => Task.create('Init Assembly Symmetry', async ctx => {
+    isApplicable: (a) => AssemblySymmetry.isApplicable(a.data),
+    params: (a, plugin: PluginContext) => getConfiguredDefaultParams(plugin)
+})(({ a, ref, state, params }, plugin: PluginContext) => Task.create('Init Assembly Symmetry', async ctx => {
     try {
         const propCtx = { runtime: ctx, assetManager: plugin.managers.asset };
-        await AssemblySymmetryDataProvider.attach(propCtx, a.data);
+        await AssemblySymmetryDataProvider.attach(propCtx, a.data, params);
         const assemblySymmetryData = AssemblySymmetryDataProvider.get(a.data).value;
         const symmetryIndex = assemblySymmetryData ? AssemblySymmetry.firstNonC1(assemblySymmetryData) : -1;
-        await AssemblySymmetryProvider.attach(propCtx, a.data, { symmetryIndex });
+        await AssemblySymmetryProvider.attach(propCtx, a.data, { ...params, symmetryIndex });
     } catch (e) {
         plugin.log.error(`Assembly Symmetry: ${e}`);
         return;
@@ -152,10 +154,6 @@ const AssemblySymmetry3D = PluginStateTransform.BuiltIn({
 
 //
 
-export const AssemblySymmetryPresetParams = {
-    ...StructureRepresentationPresetProvider.CommonParams,
-};
-
 export const AssemblySymmetryPreset = StructureRepresentationPresetProvider({
     id: 'preset-structure-representation-rcsb-assembly-symmetry',
     display: {
@@ -165,7 +163,12 @@ export const AssemblySymmetryPreset = StructureRepresentationPresetProvider({
     isApplicable(a) {
         return AssemblySymmetry.isApplicable(a.data);
     },
-    params: () => AssemblySymmetryPresetParams,
+    params: (a, plugin) => {
+        return {
+            ...StructureRepresentationPresetProvider.CommonParams,
+            ...getConfiguredDefaultParams(plugin)
+        };
+    },
     async apply(ref, params, plugin) {
         const structureCell = StateObjectRef.resolveAndCheck(plugin.state.data, ref);
         const structure = structureCell?.obj?.data;
@@ -174,15 +177,16 @@ export const AssemblySymmetryPreset = StructureRepresentationPresetProvider({
         if (!AssemblySymmetryDataProvider.get(structure).value) {
             await plugin.runTask(Task.create('Assembly Symmetry', async runtime => {
                 const propCtx = { runtime, assetManager: plugin.managers.asset };
-                await AssemblySymmetryDataProvider.attach(propCtx, structure);
+                const propProps = { serverType: params.serverType, serverUrl: params.serverUrl };
+                await AssemblySymmetryDataProvider.attach(propCtx, structure, propProps);
                 const assemblySymmetryData = AssemblySymmetryDataProvider.get(structure).value;
                 const symmetryIndex = assemblySymmetryData ? AssemblySymmetry.firstNonC1(assemblySymmetryData) : -1;
-                await AssemblySymmetryProvider.attach(propCtx, structure, { symmetryIndex });
+                await AssemblySymmetryProvider.attach(propCtx, structure, { ...propProps, symmetryIndex });
             }));
         }
 
         const assemblySymmetry = await tryCreateAssemblySymmetry(plugin, structureCell);
-        const colorTheme = assemblySymmetry.isOk ? Tag.Cluster as any : undefined;
+        const colorTheme = getRCSBAssemblySymmetryConfig(plugin).ApplyColors && assemblySymmetry.isOk ? Tag.Cluster as any : undefined;
         const preset = await PresetStructureRepresentations.auto.apply(ref, { ...params, theme: { globalName: colorTheme, focus: { name: colorTheme } } }, plugin);
 
         return { components: preset.components, representations: { ...preset.representations, assemblySymmetry } };
@@ -194,4 +198,27 @@ export function tryCreateAssemblySymmetry(plugin: PluginContext, structure: Stat
     const assemblySymmetry = state.build().to(structure)
         .applyOrUpdateTagged(AssemblySymmetry.Tag.Representation, AssemblySymmetry3D, params, { state: initialState });
     return assemblySymmetry.commit({ revertOnError: true });
-}
+}
+
+//
+
+export const RCSBAssemblySymmetryConfig = {
+    DefaultServerType: new PluginConfigItem('rcsb-assembly-symmetry.server-type', AssemblySymmetryDataParams.serverType.defaultValue),
+    DefaultServerUrl: new PluginConfigItem('rcsb-assembly-symmetry.server-url', AssemblySymmetryDataParams.serverUrl.defaultValue),
+    ApplyColors: new PluginConfigItem('rcsb-assembly-symmetry.apply-colors', true),
+};
+
+export function getRCSBAssemblySymmetryConfig(plugin: PluginContext): { [key in keyof typeof RCSBAssemblySymmetryConfig]: NonNullable<typeof RCSBAssemblySymmetryConfig[key]['defaultValue']> } {
+    return {
+        ApplyColors: plugin.config.get(RCSBAssemblySymmetryConfig.ApplyColors) ?? RCSBAssemblySymmetryConfig.ApplyColors.defaultValue ?? true,
+        DefaultServerType: plugin.config.get(RCSBAssemblySymmetryConfig.DefaultServerType) ?? RCSBAssemblySymmetryConfig.DefaultServerType.defaultValue ?? AssemblySymmetryDataParams.serverType.defaultValue,
+        DefaultServerUrl: plugin.config.get(RCSBAssemblySymmetryConfig.DefaultServerUrl) ?? RCSBAssemblySymmetryConfig.DefaultServerUrl.defaultValue ?? AssemblySymmetryDataParams.serverUrl.defaultValue,
+    };
+}
+
+function getConfiguredDefaultParams(plugin: PluginContext) {
+    const config = getRCSBAssemblySymmetryConfig(plugin);
+    const params = PD.clone(AssemblySymmetryDataParams);
+    PD.setDefaultValues(params, { serverType: config.DefaultServerType, serverUrl: config.DefaultServerUrl });
+    return params;
+}

+ 38 - 3
src/extensions/rcsb/assembly-symmetry/prop.ts

@@ -20,6 +20,7 @@ import { SetUtils } from '../../../mol-util/set';
 import { MolScriptBuilder as MS } from '../../../mol-script/language/builder';
 import { compile } from '../../../mol-script/runtime/query/compiler';
 import { CustomPropertyDescriptor } from '../../../mol-model/custom-property';
+import { Asset } from '../../../mol-util/assets';
 
 const BiologicalAssemblyNames = new Set([
     'author_and_software_defined_assembly',
@@ -48,7 +49,7 @@ export namespace AssemblySymmetry {
         Representation = 'rcsb-assembly-symmetry-3d'
     }
 
-    export const DefaultServerUrl = 'https://data.rcsb.org/graphql';
+    export const DefaultServerUrl = 'https://data.rcsb.org/graphql'; // Alternative: 'https://www.ebi.ac.uk/pdbe/aggregated-api/pdb/symmetry' (if serverType is 'pdbe')
 
     export function isApplicable(structure?: Structure): boolean {
         return (
@@ -61,6 +62,8 @@ export namespace AssemblySymmetry {
     export async function fetch(ctx: CustomProperty.Context, structure: Structure, props: AssemblySymmetryDataProps): Promise<CustomProperty.Data<AssemblySymmetryDataValue>> {
         if (!isApplicable(structure)) return { value: [] };
 
+        if (props.serverType === 'pdbe') return fetch_PDBe(ctx, structure, props);
+
         const client = new GraphQLClient(props.serverUrl, ctx.assetManager);
         const variables: AssemblySymmetryQueryVariables = {
             assembly_id: structure.units[0].conformation.operator.assembly?.id || '',
@@ -77,6 +80,37 @@ export namespace AssemblySymmetry {
         return { value, assets: [result] };
     }
 
+    async function fetch_PDBe(ctx: CustomProperty.Context, structure: Structure, props: AssemblySymmetryDataProps): Promise<CustomProperty.Data<AssemblySymmetryDataValue>> {
+        const assembly_id = structure.units[0].conformation.operator.assembly?.id || '-1'; // should use '' instead of '-1' but the API does not support non-number assembly_id
+        const entry_id = structure.units[0].model.entryId.toLowerCase();
+        const url = `${props.serverUrl}/${entry_id}?assembly_id=${assembly_id}`;
+        const asset = Asset.getUrlAsset(ctx.assetManager, url);
+        let dataWrapper: Asset.Wrapper<'json'>;
+        try {
+            dataWrapper = await ctx.assetManager.resolve(asset, 'json').runInContext(ctx.runtime);
+        } catch (err) {
+            // PDBe API returns 404 when there are no symmetries -> treat as success with empty json in body
+            if (`${err}`.includes('404')) { // dirrrty
+                dataWrapper = Asset.Wrapper({}, asset, ctx.assetManager);
+            } else {
+                throw err;
+            }
+        }
+        const data = dataWrapper.data;
+
+        const value: AssemblySymmetryDataValue = (data[entry_id] ?? []).map((v: any) => ({
+            kind: 'Global Symmetry',
+            oligomeric_state: v.oligomeric_state,
+            stoichiometry: [v.stoichiometry],
+            symbol: v.symbol,
+            type: v.type,
+            clusters: [],
+            rotation_axes: v.rotation_axes,
+        }));
+
+        return { value, assets: [dataWrapper] };
+    }
+
     /** Returns the index of the first non C1 symmetry or -1 */
     export function firstNonC1(assemblySymmetryData: AssemblySymmetryDataValue) {
         for (let i = 0, il = assemblySymmetryData.length; i < il; ++i) {
@@ -147,7 +181,8 @@ export function getSymmetrySelectParam(structure?: Structure) {
 //
 
 export const AssemblySymmetryDataParams = {
-    serverUrl: PD.Text(AssemblySymmetry.DefaultServerUrl, { description: 'GraphQL endpoint URL' })
+    serverType: PD.Select('rcsb', [['rcsb', 'RCSB'], ['pdbe', 'PDBe']] as const),
+    serverUrl: PD.Text(AssemblySymmetry.DefaultServerUrl, { description: 'GraphQL endpoint URL (if server type is RCSB) or PDBe API endpoint URL (if server type is PDBe)' })
 };
 export type AssemblySymmetryDataParams = typeof AssemblySymmetryDataParams
 export type AssemblySymmetryDataProps = PD.Values<AssemblySymmetryDataParams>
@@ -174,7 +209,7 @@ export const AssemblySymmetryDataProvider: CustomStructureProperty.Provider<Asse
 
 function getAssemblySymmetryParams(data?: Structure) {
     return {
-        ... AssemblySymmetryDataParams,
+        ...AssemblySymmetryDataParams,
         symmetryIndex: getSymmetrySelectParam(data)
     };
 }

+ 8 - 3
src/extensions/rcsb/assembly-symmetry/ui.tsx

@@ -6,7 +6,7 @@
 
 import { CollapsableState, CollapsableControls } from '../../../mol-plugin-ui/base';
 import { ApplyActionControl } from '../../../mol-plugin-ui/state/apply-action';
-import { InitAssemblySymmetry3D, AssemblySymmetry3D, AssemblySymmetryPreset, tryCreateAssemblySymmetry } from './behavior';
+import { InitAssemblySymmetry3D, AssemblySymmetry3D, AssemblySymmetryPreset, tryCreateAssemblySymmetry, getRCSBAssemblySymmetryConfig } from './behavior';
 import { AssemblySymmetryProvider, AssemblySymmetryProps, AssemblySymmetryDataProvider, AssemblySymmetry } from './prop';
 import { ParameterControls } from '../../../mol-plugin-ui/controls/parameters';
 import { ParamDefinition as PD } from '../../../mol-util/param-definition';
@@ -72,6 +72,7 @@ export class AssemblySymmetryControls extends CollapsableControls<{}, AssemblySy
     get params() {
         const structure = this.pivot.cell.obj?.data;
         const params = PD.clone(structure ? AssemblySymmetryProvider.getParams(structure) : AssemblySymmetryProvider.defaultParams);
+        params.serverType.isHidden = true;
         params.serverUrl.isHidden = true;
         return params;
     }
@@ -111,7 +112,9 @@ export class AssemblySymmetryControls extends CollapsableControls<{}, AssemblySy
                 }
             } else {
                 tryCreateAssemblySymmetry(this.plugin, s.cell);
-                await this.plugin.managers.structure.component.updateRepresentationsTheme(components, { color: AssemblySymmetry.Tag.Cluster as any });
+                if (getRCSBAssemblySymmetryConfig(this.plugin).ApplyColors) {
+                    await this.plugin.managers.structure.component.updateRepresentationsTheme(components, { color: AssemblySymmetry.Tag.Cluster as any });
+                }
             }
         }
     }
@@ -151,5 +154,7 @@ export class AssemblySymmetryControls extends CollapsableControls<{}, AssemblySy
 const EnableAssemblySymmetry3D = StateAction.build({
     from: PluginStateObject.Molecule.Structure,
 })(({ a, ref, state }, plugin: PluginContext) => Task.create('Enable Assembly Symmetry', async ctx => {
-    await AssemblySymmetryPreset.apply(ref, Object.create(null), plugin);
+    const presetParams = AssemblySymmetryPreset.params?.(a, plugin) as PD.Params | undefined;
+    const presetProps = presetParams ? PD.getDefaultValues(presetParams) : Object.create(null);
+    await AssemblySymmetryPreset.apply(ref, presetProps, plugin);
 }));

+ 2 - 2
src/mol-plugin-state/actions/structure.ts

@@ -65,11 +65,11 @@ const DownloadStructure = StateAction.build({
                 }, { isFlat: true, label: 'PDB' }),
                 'pdb-dev': PD.Group({
                     provider: PD.Group({
-                        id: PD.Text('PDBDEV_00000001', { label: 'PDBDev Id(s)', description: 'One or more comma/space separated ids.' }),
+                        id: PD.Text('PDBDEV_00000001', { label: 'PDB-Dev Id(s)', description: 'One or more comma/space separated ids.' }),
                         encoding: PD.Select('bcif', PD.arrayToOptions(['cif', 'bcif'] as const)),
                     }, { pivot: 'id' }),
                     options
-                }, { isFlat: true, label: 'PDBDEV' }),
+                }, { isFlat: true, label: 'PDB-Dev' }),
                 'swissmodel': PD.Group({
                     id: PD.Text('Q9Y2I8', { label: 'UniProtKB AC(s)', description: 'One or more comma/space separated ACs.' }),
                     options

+ 1 - 1
src/mol-plugin-ui/viewport/simple-settings.tsx

@@ -100,7 +100,7 @@ const SimpleSettingsMapping = ParamMapping({
         if (r.top !== 'hidden' && (!c || c.top !== 'none')) layout.push('sequence');
         if (r.bottom !== 'hidden' && (!c || c.bottom !== 'none')) layout.push('log');
         if (r.left !== 'hidden' && (!c || c.left !== 'none')) layout.push('left');
-        if (r.right !== 'hidden' && (!c || c.left !== 'none')) layout.push('right');
+        if (r.right !== 'hidden' && (!c || c.right !== 'none')) layout.push('right');
         return { canvas: ctx.canvas3d?.props!, layout };
     }
 })({

+ 2 - 2
src/mol-repr/structure/visual/util/polymer/trace-iterator.ts

@@ -233,8 +233,8 @@ export class AtomicPolymerTraceIterator implements Iterator<PolymerTraceElement>
             const residueIndexNext2 = this.getResidueIndex(residueIndex + 2);
             const residueIndexNext3 = this.getResidueIndex(residueIndex + 3);
 
-            this.prevSecStrucType = this.currSecStrucType;
-            this.currSecStrucType = this.nextSecStrucType;
+            this.prevSecStrucType = this.getSecStruc(residueIndexPrev1);
+            this.currSecStrucType = this.getSecStruc(residueIndex);
             this.nextSecStrucType = residueIndex === residueIndexNext1 ? SecStrucTypeNA : this.getSecStruc(residueIndexNext1);
 
             this.prevCoarseBackbone = this.currCoarseBackbone;

+ 1 - 1
src/mol-util/color/color.ts

@@ -192,7 +192,7 @@ export interface ColorList {
     type: 'sequential' | 'diverging' | 'qualitative'
 }
 export function ColorList(label: string, type: 'sequential' | 'diverging' | 'qualitative', description: string, list: (number | [number, number])[]): ColorList {
-    return { label, description, list: list as Color[], type };
+    return { label, description, list: list as ColorListEntry[], type };
 }
 
 export type ColorTable<T extends { [k: string]: number[] }> = { [k in keyof T]: Color[] }

+ 2 - 1
src/mol-util/color/distinct.ts

@@ -15,6 +15,7 @@ import { deepEqual } from '../../mol-util';
 import { arraySum } from '../../mol-util/array';
 import { ParamDefinition as PD } from '../../mol-util/param-definition';
 import { ColorNames } from './names';
+import { Color } from './color';
 
 export const DistinctColorsParams = {
     hue: PD.Interval([1, 360], { min: 0, max: 360, step: 1 }),
@@ -100,7 +101,7 @@ function getSamples(count: number, p: DistinctColorsProps) {
 /**
  * Create a list of visually distinct colors
  */
-export function distinctColors(count: number, props: Partial<DistinctColorsProps> = {}) {
+export function distinctColors(count: number, props: Partial<DistinctColorsProps> = {}): Color[] {
     const p = { ...PD.getDefaultValues(DistinctColorsParams), ...props };
 
     if (count <= 0) return [];