瀏覽代碼

Merge branch 'master' into repr-defaults-tweaks

Alexander Rose 3 年之前
父節點
當前提交
49541558d1

+ 4 - 0
CHANGELOG.md

@@ -11,6 +11,10 @@ Note that since we don't clearly distinguish between a public and private interf
 - Tweaks for cleaner default representation style
     - Cartoon: use ``nucleotide-ring`` instead of ``nucleotide-block``
     - Focus: use ``xrayShaded`` instead of opacity; adjust target size; don't show non-covalent interactions twice
+- Fix representation preset side effects (changing post-processing parameters, see #363)
+- Add Quick Styles panel (default, illustrative, stylized)
+- Fix exported structure missing secondary-structure categories (#364)
+- Fix volume streaming error message: distinguish between missing data and server error (#364)
 
 ## [v3.0.2] - 2022-01-30
 

+ 7 - 7
src/mol-model/structure/export/categories/secondary-structure.ts

@@ -78,12 +78,12 @@ function findElements<T extends SecondaryStructure.Element>(ctx: CifExportContex
     const ssElements: SSElement<any>[] = [];
 
     const structure = ctx.structures[0];
-    for (const unit of structure.units) {
-        // currently can only support this for "identity" operators.
-        if (!Unit.isAtomic(unit) || !unit.conformation.operator.isIdentity) continue;
+    for (const { units } of structure.unitSymmetryGroups) {
+        const u = units[0];
+        if (!Unit.isAtomic(u)) continue;
 
-        const segs = unit.model.atomicHierarchy.residueAtomSegments;
-        const residues = Segmentation.transientSegments(segs, unit.elements);
+        const segs = u.model.atomicHierarchy.residueAtomSegments;
+        const residues = Segmentation.transientSegments(segs, u.elements);
 
         let current: Segmentation.Segment, move = true;
         while (residues.hasNext) {
@@ -104,8 +104,8 @@ function findElements<T extends SecondaryStructure.Element>(ctx: CifExportContex
                 if (startIdx !== key[current.index]) {
                     move = false;
                     ssElements[ssElements.length] = {
-                        start: StructureElement.Location.create(structure, unit, segs.offsets[start]),
-                        end: StructureElement.Location.create(structure, unit, segs.offsets[prev]),
+                        start: StructureElement.Location.create(structure, u, segs.offsets[start]),
+                        end: StructureElement.Location.create(structure, u, segs.offsets[prev]),
                         length: prev - start + 1,
                         element
                     };

+ 1 - 38
src/mol-plugin-state/builder/structure/representation-preset.ts

@@ -24,8 +24,6 @@ import { IndexPairBonds } from '../../../mol-model-formats/structure/property/bo
 import { StructConn } from '../../../mol-model-formats/structure/property/bonds/struct_conn';
 import { StructureRepresentationRegistry } from '../../../mol-repr/structure/registry';
 import { assertUnreachable } from '../../../mol-util/type-helpers';
-import { Color } from '../../../mol-util/color';
-import { PostprocessingParams } from '../../../mol-canvas3d/passes/postprocessing';
 
 export interface StructureRepresentationPresetProvider<P = any, S extends _Result = _Result> extends PresetProvider<PluginStateObject.Molecule.Structure, P, S> { }
 export function StructureRepresentationPresetProvider<P, S extends _Result>(repr: StructureRepresentationPresetProvider<P, S>) { return repr; }
@@ -103,15 +101,6 @@ type CommonParams = StructureRepresentationPresetProvider.CommonParams
 const reprBuilder = StructureRepresentationPresetProvider.reprBuilder;
 const updateFocusRepr = StructureRepresentationPresetProvider.updateFocusRepr;
 
-function resetPostprocessingProps(plugin: PluginContext) {
-    if (plugin.canvas3d) {
-        const p = PD.getDefaultValues(PostprocessingParams);
-        plugin.canvas3d.setProps({
-            postprocessing: { outline: p.outline, occlusion: p.occlusion }
-        });
-    }
-}
-
 const auto = StructureRepresentationPresetProvider({
     id: 'preset-structure-representation-auto',
     display: {
@@ -151,7 +140,6 @@ const empty = StructureRepresentationPresetProvider({
     id: 'preset-structure-representation-empty',
     display: { name: 'Empty', description: 'Removes all existing representations.' },
     async apply(ref, params, plugin) {
-        resetPostprocessingProps(plugin);
         return { };
     }
 });
@@ -206,8 +194,6 @@ const polymerAndLigand = StructureRepresentationPresetProvider({
         await update.commit({ revertOnError: false });
         await updateFocusRepr(plugin, structure, params.theme?.focus?.name, params.theme?.focus?.params);
 
-        resetPostprocessingProps(plugin);
-
         return { components, representations };
     }
 });
@@ -247,8 +233,6 @@ const proteinAndNucleic = StructureRepresentationPresetProvider({
         await update.commit({ revertOnError: true });
         await updateFocusRepr(plugin, structure, params.theme?.focus?.name, params.theme?.focus?.params);
 
-        resetPostprocessingProps(plugin);
-
         return { components, representations };
     }
 });
@@ -301,8 +285,6 @@ const coarseSurface = StructureRepresentationPresetProvider({
         await update.commit({ revertOnError: true });
         await updateFocusRepr(plugin, structure, params.theme?.focus?.name, params.theme?.focus?.params);
 
-        resetPostprocessingProps(plugin);
-
         return { components, representations };
     }
 });
@@ -336,8 +318,6 @@ const polymerCartoon = StructureRepresentationPresetProvider({
         await update.commit({ revertOnError: true });
         await updateFocusRepr(plugin, structure, params.theme?.focus?.name, params.theme?.focus?.params);
 
-        resetPostprocessingProps(plugin);
-
         return { components, representations };
     }
 });
@@ -404,8 +384,6 @@ const atomicDetail = StructureRepresentationPresetProvider({
         await update.commit({ revertOnError: true });
         await updateFocusRepr(plugin, structure, params.theme?.focus?.name ?? color, params.theme?.focus?.params ?? colorParams);
 
-        resetPostprocessingProps(plugin);
-
         return { components, representations };
     }
 });
@@ -439,21 +417,6 @@ const illustrative = StructureRepresentationPresetProvider({
         await update.commit({ revertOnError: true });
         await updateFocusRepr(plugin, structure, params.theme?.focus?.name ?? color, params.theme?.focus?.params);
 
-        if (plugin.canvas3d) {
-            plugin.canvas3d.setProps({
-                postprocessing: {
-                    outline: {
-                        name: 'on',
-                        params: { scale: 1, color: Color(0x000000), threshold: 0.25 }
-                    },
-                    occlusion: {
-                        name: 'on',
-                        params: { bias: 0.9, blurKernelSize: 15, radius: 5, samples: 32 }
-                    },
-                }
-            });
-        }
-
         return { components, representations };
     }
 });
@@ -474,6 +437,6 @@ export const PresetStructureRepresentations = {
     'polymer-and-ligand': polymerAndLigand,
     'protein-and-nucleic': proteinAndNucleic,
     'coarse-surface': coarseSurface,
-    'illustrative': illustrative,
+    illustrative,
 };
 export type PresetStructureRepresentations = typeof PresetStructureRepresentations;

+ 1 - 1
src/mol-plugin-state/builder/structure/representation.ts

@@ -29,7 +29,7 @@ export class StructureRepresentationBuilder {
 
     readonly defaultProvider = PresetStructureRepresentations.auto;
 
-    private resolveProvider(ref: StructureRepresentationPresetProviderRef) {
+    resolveProvider(ref: StructureRepresentationPresetProviderRef) {
         return typeof ref === 'string'
             ? PresetStructureRepresentations[ref as keyof PresetStructureRepresentations] ?? arrayFind(this._providers, p => p.id === ref)
             : ref;

+ 3 - 1
src/mol-plugin-ui/controls.tsx

@@ -1,5 +1,5 @@
 /**
- * Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2018-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
  * @author David Sehnal <david.sehnal@gmail.com>
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
@@ -24,6 +24,7 @@ import { StructureSourceControls } from './structure/source';
 import { VolumeStreamingControls, VolumeSourceControls } from './structure/volume';
 import { PluginConfig } from '../mol-plugin/config';
 import { StructureSuperpositionControls } from './structure/superposition';
+import { StructureQuickStylesControls } from './structure/quick-styles';
 
 export class TrajectoryViewportControls extends PluginUIComponent<{}, { show: boolean, label: string }> {
     state = { show: false, label: '' };
@@ -292,6 +293,7 @@ export class DefaultStructureTools extends PluginUIComponent {
             <StructureSourceControls />
             <StructureMeasurementsControls />
             <StructureSuperpositionControls />
+            <StructureQuickStylesControls />
             <StructureComponentControls />
             {this.plugin.config.get(PluginConfig.VolumeStreaming.Enabled) && <VolumeStreamingControls />}
             <VolumeSourceControls />

+ 7 - 4
src/mol-plugin-ui/controls/icons.tsx

@@ -1,5 +1,5 @@
 /**
- * Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2020-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
  * @author David Sehnal <david.sehnal@gmail.com>
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
@@ -38,10 +38,10 @@ export function MoleculeSvg() { return _Molecule; }
 // The following icons are adapted from https://materialdesignicons.com/ and
 // licensed with https://github.com/Templarian/MaterialDesign/blob/master/LICENSE
 
-const _CubeOutline = <svg width='24px' height='24px' viewBox='0 0 24 24' strokeWidth='0.1px'><path d="M21,16.5C21,16.88 20.79,17.21 20.47,17.38L12.57,21.82C12.41,21.94 12.21,22 12,22C11.79,22 11.59,21.94 11.43,21.82L3.53,17.38C3.21,17.21 3,16.88 3,16.5V7.5C3,7.12 3.21,6.79 3.53,6.62L11.43,2.18C11.59,2.06 11.79,2 12,2C12.21,2 12.41,2.06 12.57,2.18L20.47,6.62C20.79,6.79 21,7.12 21,7.5V16.5M12,4.15L6.04,7.5L12,10.85L17.96,7.5L12,4.15M5,15.91L11,19.29V12.58L5,9.21V15.91M19,15.91V9.21L13,12.58V19.29L19,15.91Z" /></svg>;
+const _CubeOutline = <svg width='24px' height='24px' viewBox='0 0 24 24' strokeWidth='0.1px'><path d='M21,16.5C21,16.88 20.79,17.21 20.47,17.38L12.57,21.82C12.41,21.94 12.21,22 12,22C11.79,22 11.59,21.94 11.43,21.82L3.53,17.38C3.21,17.21 3,16.88 3,16.5V7.5C3,7.12 3.21,6.79 3.53,6.62L11.43,2.18C11.59,2.06 11.79,2 12,2C12.21,2 12.41,2.06 12.57,2.18L20.47,6.62C20.79,6.79 21,7.12 21,7.5V16.5M12,4.15L6.04,7.5L12,10.85L17.96,7.5L12,4.15M5,15.91L11,19.29V12.58L5,9.21V15.91M19,15.91V9.21L13,12.58V19.29L19,15.91Z' /></svg>;
 export function CubeOutlineSvg() { return _CubeOutline; }
 
-const _CubeScan = <svg width='24px' height='24px' viewBox='0 0 24 24' strokeWidth='0.1px'><path d="M17,22V20H20V17H22V20.5C22,20.89 21.84,21.24 21.54,21.54C21.24,21.84 20.89,22 20.5,22H17M7,22H3.5C3.11,22 2.76,21.84 2.46,21.54C2.16,21.24 2,20.89 2,20.5V17H4V20H7V22M17,2H20.5C20.89,2 21.24,2.16 21.54,2.46C21.84,2.76 22,3.11 22,3.5V7H20V4H17V2M7,2V4H4V7H2V3.5C2,3.11 2.16,2.76 2.46,2.46C2.76,2.16 3.11,2 3.5,2H7M13,17.25L17,14.95V10.36L13,12.66V17.25M12,10.92L16,8.63L12,6.28L8,8.63L12,10.92M7,14.95L11,17.25V12.66L7,10.36V14.95M18.23,7.59C18.73,7.91 19,8.34 19,8.91V15.23C19,15.8 18.73,16.23 18.23,16.55L12.75,19.73C12.25,20.05 11.75,20.05 11.25,19.73L5.77,16.55C5.27,16.23 5,15.8 5,15.23V8.91C5,8.34 5.27,7.91 5.77,7.59L11.25,4.41C11.5,4.28 11.75,4.22 12,4.22C12.25,4.22 12.5,4.28 12.75,4.41L18.23,7.59Z" /></svg>;
+const _CubeScan = <svg width='24px' height='24px' viewBox='0 0 24 24' strokeWidth='0.1px'><path d='M17,22V20H20V17H22V20.5C22,20.89 21.84,21.24 21.54,21.54C21.24,21.84 20.89,22 20.5,22H17M7,22H3.5C3.11,22 2.76,21.84 2.46,21.54C2.16,21.24 2,20.89 2,20.5V17H4V20H7V22M17,2H20.5C20.89,2 21.24,2.16 21.54,2.46C21.84,2.76 22,3.11 22,3.5V7H20V4H17V2M7,2V4H4V7H2V3.5C2,3.11 2.16,2.76 2.46,2.46C2.76,2.16 3.11,2 3.5,2H7M13,17.25L17,14.95V10.36L13,12.66V17.25M12,10.92L16,8.63L12,6.28L8,8.63L12,10.92M7,14.95L11,17.25V12.66L7,10.36V14.95M18.23,7.59C18.73,7.91 19,8.34 19,8.91V15.23C19,15.8 18.73,16.23 18.23,16.55L12.75,19.73C12.25,20.05 11.75,20.05 11.25,19.73L5.77,16.55C5.27,16.23 5,15.8 5,15.23V8.91C5,8.34 5.27,7.91 5.77,7.59L11.25,4.41C11.5,4.28 11.75,4.22 12,4.22C12.25,4.22 12.5,4.28 12.75,4.41L18.23,7.59Z' /></svg>;
 export function CubeScanSvg() { return _CubeScan; }
 
 const _CubeSend = <svg width='24px' height='24px' viewBox='0 0 24 24' strokeWidth='0.1px'><path d="M16,4L9,8.04V15.96L16,20L23,15.96V8.04M16,6.31L19.8,8.5L16,10.69L12.21,8.5M0,7V9H7V7M11,10.11L15,12.42V17.11L11,14.81M21,10.11V14.81L17,17.11V12.42M2,11V13H7V11M4,15V17H7V15" /></svg>;
@@ -53,9 +53,12 @@ export function CursorDefaultOutlineSvg() { return _CursorDefaultOutline; }
 const _FileOutline = <svg width='24px' height='24px' viewBox='0 0 24 24' strokeWidth='0.1px'><path fill='currentColor' d='M14,2H6A2,2 0 0,0 4,4V20A2,2 0 0,0 6,22H18A2,2 0 0,0 20,20V8L14,2M18,20H6V4H13V9H18V20Z' /></svg>;
 export function FileOutlineSvg() { return _FileOutline; }
 
-const _PencilRuler = <svg width='24px' height='24px' viewBox='0 0 24 24' strokeWidth='0.1px'><path d="M3 17.25V21H6.75L17.81 9.93L14.06 6.18L3 17.25M22.61 18.36L18.36 22.61L13.16 17.41L14.93 15.64L15.93 16.64L18.4 14.16L19.82 15.58L18.36 17L19.42 18L20.84 16.6L22.61 18.36M6.61 10.83L1.39 5.64L5.64 1.39L7.4 3.16L4.93 5.64L6 6.7L8.46 4.22L9.88 5.64L8.46 7.05L9.46 8.05L6.61 10.83M20.71 7C21.1 6.61 21.1 6 20.71 5.59L18.37 3.29C18 2.9 17.35 2.9 16.96 3.29L15.12 5.12L18.87 8.87L20.71 7Z" /></svg>;
+const _PencilRuler = <svg width='24px' height='24px' viewBox='0 0 24 24' strokeWidth='0.1px'><path d='M3 17.25V21H6.75L17.81 9.93L14.06 6.18L3 17.25M22.61 18.36L18.36 22.61L13.16 17.41L14.93 15.64L15.93 16.64L18.4 14.16L19.82 15.58L18.36 17L19.42 18L20.84 16.6L22.61 18.36M6.61 10.83L1.39 5.64L5.64 1.39L7.4 3.16L4.93 5.64L6 6.7L8.46 4.22L9.88 5.64L8.46 7.05L9.46 8.05L6.61 10.83M20.71 7C21.1 6.61 21.1 6 20.71 5.59L18.37 3.29C18 2.9 17.35 2.9 16.96 3.29L15.12 5.12L18.87 8.87L20.71 7Z' /></svg>;
 export function PencilRulerSvg() { return _PencilRuler; }
 
+const _MagicWand = <svg width='24px' height='24px' viewBox='0 0 24 24'><path fill='currentColor' d='M7.5,5.6L5,7L6.4,4.5L5,2L7.5,3.4L10,2L8.6,4.5L10,7L7.5,5.6M19.5,15.4L22,14L20.6,16.5L22,19L19.5,17.6L17,19L18.4,16.5L17,14L19.5,15.4M22,2L20.6,4.5L22,7L19.5,5.6L17,7L18.4,4.5L17,2L19.5,3.4L22,2M13.34,12.78L15.78,10.34L13.66,8.22L11.22,10.66L13.34,12.78M14.37,7.29L16.71,9.63C17.1,10 17.1,10.65 16.71,11.04L5.04,22.71C4.65,23.1 4,23.1 3.63,22.71L1.29,20.37C0.9,20 0.9,19.35 1.29,18.96L12.96,7.29C13.35,6.9 14,6.9 14.37,7.29Z' /></svg>;
+export function MagicWandSvg() { return _MagicWand; }
+
 // The following icons are adapted from https://material-ui.com/components/material-icons/ and
 // licensed with https://github.com/mui-org/material-ui/blob/master/LICENSE
 

+ 107 - 0
src/mol-plugin-ui/structure/quick-styles.tsx

@@ -0,0 +1,107 @@
+/**
+ * Copyright (c) 2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
+ */
+
+import { PresetStructureRepresentations } from '../../mol-plugin-state/builder/structure/representation-preset';
+import { Color } from '../../mol-util/color';
+import { CollapsableControls, PurePluginUIComponent } from '../base';
+import { Button } from '../controls/common';
+import { MagicWandSvg } from '../controls/icons';
+import { ParamDefinition as PD } from '../../mol-util/param-definition';
+import { PostprocessingParams } from '../../mol-canvas3d/passes/postprocessing';
+import { PluginConfig } from '../../mol-plugin/config';
+import { StructureComponentManager } from '../../mol-plugin-state/manager/structure/component';
+
+export class StructureQuickStylesControls extends CollapsableControls {
+    defaultState() {
+        return {
+            isCollapsed: false,
+            header: 'Quick Styles',
+            brand: { accent: 'gray' as const, svg: MagicWandSvg }
+        };
+    }
+
+    renderControls() {
+        return <>
+            <QuickStyles />
+        </>;
+    }
+}
+
+export class QuickStyles extends PurePluginUIComponent {
+    async default() {
+        const { structures } = this.plugin.managers.structure.hierarchy.selection;
+        const preset = this.plugin.config.get(PluginConfig.Structure.DefaultRepresentationPreset) || PresetStructureRepresentations.auto.id;
+        const provider = this.plugin.builders.structure.representation.resolveProvider(preset);
+        await this.plugin.managers.structure.component.applyPreset(structures, provider);
+
+        this.plugin.managers.structure.component.setOptions(PD.getDefaultValues(StructureComponentManager.OptionsParams));
+
+        if (this.plugin.canvas3d) {
+            const p = PD.getDefaultValues(PostprocessingParams);
+            this.plugin.canvas3d.setProps({
+                postprocessing: { outline: p.outline, occlusion: p.occlusion }
+            });
+        }
+    }
+
+    async illustrative() {
+        const { structures } = this.plugin.managers.structure.hierarchy.selection;
+        await this.plugin.managers.structure.component.applyPreset(structures, PresetStructureRepresentations.illustrative);
+
+        if (this.plugin.canvas3d) {
+            this.plugin.canvas3d.setProps({
+                postprocessing: {
+                    outline: {
+                        name: 'on',
+                        params: { scale: 1, color: Color(0x000000), threshold: 0.25 }
+                    },
+                    occlusion: {
+                        name: 'on',
+                        params: { bias: 0.9, blurKernelSize: 15, radius: 5, samples: 32 }
+                    },
+                }
+            });
+        }
+    }
+
+    async stylized() {
+        this.plugin.managers.structure.component.setOptions({ ...this.plugin.managers.structure.component.state.options, ignoreLight: true });
+
+        if (this.plugin.canvas3d) {
+            const pp = this.plugin.canvas3d.props.postprocessing;
+            this.plugin.canvas3d.setProps({
+                postprocessing: {
+                    outline: {
+                        name: 'on',
+                        params: pp.outline.name === 'on'
+                            ? pp.outline.params
+                            : { scale: 1, color: Color(0x000000), threshold: 0.33 }
+                    },
+                    occlusion: {
+                        name: 'on',
+                        params: pp.occlusion.name === 'on'
+                            ? pp.occlusion.params
+                            : { bias: 0.9, blurKernelSize: 15, radius: 5, samples: 32 }
+                    },
+                }
+            });
+        }
+    }
+
+    render() {
+        return <div className='msp-flex-row'>
+            <Button noOverflow title='Applies default representation preset. Set outline and occlusion effects to defaults.' onClick={() => this.default()} style={{ width: 'auto' }}>
+                Default
+            </Button>
+            <Button noOverflow title='Applies illustrative representation preset. Enables outline and occlusion effects. Enables ignore-light parameter.' onClick={() => this.illustrative()} style={{ width: 'auto' }}>
+                Illustrative
+            </Button>
+            <Button noOverflow title='Applies no representation preset. Enables outline and occlusion effects. Enables ignore-light representation parameter.' onClick={() => this.stylized()} style={{ width: 'auto' }}>
+                Stylized
+            </Button>
+        </div>;
+    }
+}