Explorar o código

Merge remote-tracking branch 'upstream/master' into chem-comp

JonStargaryen %!s(int64=4) %!d(string=hai) anos
pai
achega
fad5a40ec4

A diferenza do arquivo foi suprimida porque é demasiado grande
+ 353 - 388
package-lock.json


+ 25 - 25
package.json

@@ -1,6 +1,6 @@
 {
   "name": "molstar",
-  "version": "1.1.3",
+  "version": "1.1.5",
   "description": "A comprehensive macromolecular library.",
   "homepage": "https://github.com/molstar/molstar#readme",
   "repository": {
@@ -86,48 +86,48 @@
   ],
   "license": "MIT",
   "devDependencies": {
-    "@graphql-codegen/add": "^1.14.0",
-    "@graphql-codegen/cli": "^1.14.0",
-    "@graphql-codegen/time": "^1.14.0",
-    "@graphql-codegen/typescript": "^1.14.0",
-    "@graphql-codegen/typescript-graphql-files-modules": "^1.14.0",
-    "@graphql-codegen/typescript-graphql-request": "^1.14.0",
-    "@graphql-codegen/typescript-operations": "^1.14.0",
+    "@graphql-codegen/add": "^1.17.3",
+    "@graphql-codegen/cli": "^1.17.3",
+    "@graphql-codegen/time": "^1.17.3",
+    "@graphql-codegen/typescript": "^1.17.3",
+    "@graphql-codegen/typescript-graphql-files-modules": "^1.17.3",
+    "@graphql-codegen/typescript-graphql-request": "^1.17.3",
+    "@graphql-codegen/typescript-operations": "^1.17.3",
     "@types/cors": "^2.8.6",
-    "@typescript-eslint/eslint-plugin": "^3.0.0",
-    "@typescript-eslint/parser": "^3.0.0",
+    "@typescript-eslint/eslint-plugin": "^3.7.0",
+    "@typescript-eslint/parser": "^3.7.0",
     "benchmark": "^2.1.4",
     "concurrently": "^5.2.0",
     "cpx2": "^2.0.0",
-    "css-loader": "^3.5.3",
-    "eslint": "^7.0.0",
+    "css-loader": "^3.6.0",
+    "eslint": "^7.5.0",
     "extra-watch-webpack-plugin": "^1.0.3",
     "file-loader": "^6.0.0",
-    "fs-extra": "^9.0.0",
-    "graphql": "^15.0.0",
+    "fs-extra": "^9.0.1",
+    "graphql": "^15.3.0",
     "http-server": "^0.12.3",
-    "jest": "^26.0.1",
+    "jest": "^26.1.0",
     "mini-css-extract-plugin": "^0.9.0",
     "node-sass": "^4.14.1",
     "raw-loader": "^4.0.1",
     "sass-loader": "^8.0.2",
-    "simple-git": "^2.5.0",
+    "simple-git": "^2.14.0",
     "style-loader": "^1.2.1",
-    "ts-jest": "^26.0.0",
-    "typescript": "^3.9.3",
+    "ts-jest": "^26.1.3",
+    "typescript": "^3.9.7",
     "webpack": "^4.43.0",
-    "webpack-cli": "^3.3.11",
+    "webpack-cli": "^3.3.12",
     "webpack-version-file-plugin": "^0.4.0"
   },
   "dependencies": {
     "@types/argparse": "^1.0.38",
     "@types/benchmark": "^1.0.33",
     "@types/compression": "1.7.0",
-    "@types/express": "^4.17.6",
+    "@types/express": "^4.17.7",
     "@types/jest": "^25.2.3",
-    "@types/node": "^14.0.5",
+    "@types/node": "^14.0.24",
     "@types/node-fetch": "^2.5.7",
-    "@types/react": "^16.9.35",
+    "@types/react": "^16.9.43",
     "@types/react-dom": "^16.9.8",
     "@types/swagger-ui-dist": "3.0.5",
     "argparse": "^1.0.10",
@@ -135,13 +135,13 @@
     "compression": "^1.7.4",
     "cors": "^2.8.5",
     "express": "^4.17.1",
-    "immer": "^6.0.6",
+    "immer": "^7.0.5",
     "immutable": "^3.8.2",
     "node-fetch": "^2.6.0",
     "react": "^16.13.1",
     "react-dom": "^16.13.1",
-    "rxjs": "^6.5.5",
-    "swagger-ui-dist": "^3.25.4",
+    "rxjs": "^6.6.0",
+    "swagger-ui-dist": "^3.30.1",
     "tslib": "^2.0.0",
     "util.promisify": "^1.0.1",
     "xhr2": "^0.2.0"

+ 2 - 15
src/examples/docking-viewer/index.html → src/apps/docking-viewer/index.html

@@ -17,22 +17,9 @@
     </head>
     <body>
         <div id="app"></div>
-        <script type="text/javascript" src="./index.js"></script>
+        <script type="text/javascript" src="./molstar.js"></script>
         <script type="text/javascript">
-            var viewer = new DockingViewer('app', {
-                layoutIsExpanded: false,
-                layoutShowControls: false,
-                layoutShowRemoteState: false,
-                layoutShowSequence: true,
-                layoutShowLog: false,
-                layoutShowLeftPanel: true,
-
-                viewportShowExpand: true,
-                viewportShowControls: false,
-                viewportShowSettings: false,
-                viewportShowSelectionMode: false,
-                viewportShowAnimation: false,
-            });
+            var viewer = new DockingViewer('app', [0x33DD22, 0x1133EE], true);
 
             function getParam(name, regex) {
                 var r = new RegExp(name + '=' + '(' + regex + ')[&]?', 'i');

+ 48 - 100
src/examples/docking-viewer/index.ts → src/apps/docking-viewer/index.ts

@@ -11,12 +11,8 @@ import './index.html';
 import { PluginContext } from '../../mol-plugin/context';
 import { PluginCommands } from '../../mol-plugin/commands';
 import { PluginSpec } from '../../mol-plugin/spec';
-import { DownloadStructure, PdbDownloadProvider } from '../../mol-plugin-state/actions/structure';
 import { PluginConfig } from '../../mol-plugin/config';
-import { Asset } from '../../mol-util/assets';
 import { ObjectKeys } from '../../mol-util/type-helpers';
-import { PluginState } from '../../mol-plugin/state';
-import { DownloadDensity } from '../../mol-plugin-state/actions/volume';
 import { PluginLayoutControlsDisplay } from '../../mol-plugin/layout';
 import { BuiltInTrajectoryFormat } from '../../mol-plugin-state/formats/trajectory';
 import { Structure } from '../../mol-model/structure';
@@ -24,9 +20,10 @@ import { PluginStateTransform, PluginStateObject as PSO } from '../../mol-plugin
 import { ParamDefinition as PD } from '../../mol-util/param-definition';
 import { Task } from '../../mol-task';
 import { StateObject } from '../../mol-state';
-import { ViewportComponent, StructurePreset } from './viewport';
+import { ViewportComponent, StructurePreset, ShowButtons } from './viewport';
 import { PluginBehaviors } from '../../mol-plugin/behavior';
 import { ColorNames } from '../../mol-util/color/names';
+import { Color } from '../../mol-util/color';
 
 require('mol-plugin-ui/skin/light.scss');
 
@@ -53,13 +50,25 @@ const DefaultViewerOptions = {
     pdbProvider: PluginConfig.Download.DefaultPdbProvider.defaultValue,
     emdbProvider: PluginConfig.Download.DefaultEmdbProvider.defaultValue,
 };
-type ViewerOptions = typeof DefaultViewerOptions;
 
 class Viewer {
     plugin: PluginContext
 
-    constructor(elementOrId: string | HTMLElement, options: Partial<ViewerOptions> = {}) {
-        const o = { ...DefaultViewerOptions, ...options };
+    constructor(elementOrId: string | HTMLElement, colors = [Color(0x992211), Color(0xDDDDDD)], showButtons = true) {
+        const o = { ...DefaultViewerOptions, ...{
+            layoutIsExpanded: false,
+            layoutShowControls: false,
+            layoutShowRemoteState: false,
+            layoutShowSequence: true,
+            layoutShowLog: false,
+            layoutShowLeftPanel: true,
+
+            viewportShowExpand: true,
+            viewportShowControls: false,
+            viewportShowSettings: false,
+            viewportShowSelectionMode: false,
+            viewportShowAnimation: false,
+        } };
 
         const spec: PluginSpec = {
             actions: [...DefaultPluginSpec.actions],
@@ -104,7 +113,8 @@ class Viewer {
                 [PluginConfig.State.CurrentServer, o.pluginStateServer],
                 [PluginConfig.VolumeStreaming.DefaultServer, o.volumeStreamingServer],
                 [PluginConfig.Download.DefaultPdbProvider, o.pdbProvider],
-                [PluginConfig.Download.DefaultEmdbProvider, o.emdbProvider]
+                [PluginConfig.Download.DefaultEmdbProvider, o.emdbProvider],
+                [ShowButtons, showButtons]
             ]
         };
 
@@ -114,40 +124,27 @@ class Viewer {
         if (!element) throw new Error(`Could not get element with id '${elementOrId}'`);
         this.plugin = createPlugin(element, spec);
 
-        PluginCommands.Canvas3D.SetSettings(this.plugin, { settings: {
-            renderer: {
-                ...this.plugin.canvas3d!.props.renderer,
-                backgroundColor: ColorNames.white,
-            },
-            camera: {
-                ...this.plugin.canvas3d!.props.camera,
-                helper: { axes: { name: 'off', params: {} } }
+        (this.plugin.customState as any) = {
+            colorPalette: {
+                name: 'colors',
+                params: { list: { colors } }
             }
-        } });
-    }
-
-    setRemoteSnapshot(id: string) {
-        const url = `${this.plugin.config.get(PluginConfig.State.CurrentServer)}/get/${id}`;
-        return PluginCommands.State.Snapshots.Fetch(this.plugin, { url });
-    }
-
-    loadSnapshotFromUrl(url: string, type: PluginState.SnapshotType) {
-        return PluginCommands.State.Snapshots.OpenUrl(this.plugin, { url, type });
-    }
+        };
 
-    loadStructureFromUrl(url: string, format: BuiltInTrajectoryFormat = 'mmcif', isBinary = false) {
-        const params = DownloadStructure.createDefaultParams(this.plugin.state.data.root.obj!, this.plugin);
-        return this.plugin.runTask(this.plugin.state.data.applyAction(DownloadStructure, {
-            source: {
-                name: 'url',
-                params: {
-                    url: Asset.Url(url),
-                    format: format as any,
-                    isBinary,
-                    options: params.source.params.options,
-                }
+        this.plugin.behaviors.canvas3d.initialized.subscribe(v => {
+            if (v) {
+                PluginCommands.Canvas3D.SetSettings(this.plugin, { settings: {
+                    renderer: {
+                        ...this.plugin.canvas3d!.props.renderer,
+                        backgroundColor: ColorNames.white,
+                    },
+                    camera: {
+                        ...this.plugin.canvas3d!.props.camera,
+                        helper: { axes: { name: 'off', params: {} } }
+                    }
+                } });
             }
-        }));
+        });
     }
 
     async loadStructuresFromUrlsAndMerge(sources: { url: string, format: BuiltInTrajectoryFormat, isBinary?: boolean }[]) {
@@ -162,69 +159,19 @@ class Viewer {
 
             structures.push({ ref: structureProperties?.ref || structure.ref });
         }
+
+        // remove current structuresfrom hierarchy as they will be merged
+        // TODO only works with using loadStructuresFromUrlsAndMerge once
+        //      need some more API metho to work with the hierarchy
+        this.plugin.managers.structure.hierarchy.updateCurrent(this.plugin.managers.structure.hierarchy.current.structures, 'remove');
+
         const dependsOn = structures.map(({ ref }) => ref);
         const data = this.plugin.state.data.build().toRoot().apply(MergeStructures, { structures }, { dependsOn });
         const structure = await data.commit();
         const structureProperties = await this.plugin.builders.structure.insertStructureProperties(structure);
-        await this.plugin.builders.structure.representation.applyPreset(structureProperties || structure, StructurePreset);
-    }
-
-    async loadStructureFromData(data: string | number[], format: BuiltInTrajectoryFormat, options?: { dataLabel?: string }) {
-        const _data = await this.plugin.builders.data.rawData({ data, label: options?.dataLabel });
-        const trajectory = await this.plugin.builders.structure.parseTrajectory(_data, format);
-        await this.plugin.builders.structure.hierarchy.applyPreset(trajectory, 'default');
-    }
-
-    loadPdb(pdb: string) {
-        const params = DownloadStructure.createDefaultParams(this.plugin.state.data.root.obj!, this.plugin);
-        const provider = this.plugin.config.get(PluginConfig.Download.DefaultPdbProvider)!;
-        return this.plugin.runTask(this.plugin.state.data.applyAction(DownloadStructure, {
-            source: {
-                name: 'pdb' as const,
-                params: {
-                    provider: {
-                        id: pdb,
-                        server: {
-                            name: provider,
-                            params: PdbDownloadProvider[provider].defaultValue as any
-                        }
-                    },
-                    options: params.source.params.options,
-                }
-            }
-        }));
-    }
-
-    loadPdbDev(pdbDev: string) {
-        const params = DownloadStructure.createDefaultParams(this.plugin.state.data.root.obj!, this.plugin);
-        return this.plugin.runTask(this.plugin.state.data.applyAction(DownloadStructure, {
-            source: {
-                name: 'pdb-dev' as const,
-                params: {
-                    provider: {
-                        id: pdbDev,
-                        encoding: 'bcif',
-                    },
-                    options: params.source.params.options,
-                }
-            }
-        }));
-    }
-
-    loadEmdb(emdb: string) {
-        const provider = this.plugin.config.get(PluginConfig.Download.DefaultEmdbProvider)!;
-        return this.plugin.runTask(this.plugin.state.data.applyAction(DownloadDensity, {
-            source: {
-                name: 'pdb-emd-ds' as const,
-                params: {
-                    provider: {
-                        id: emdb,
-                        server: provider,
-                    },
-                    detail: 3,
-                }
-            }
-        }));
+        this.plugin.behaviors.canvas3d.initialized.subscribe(async v => {
+            await this.plugin.builders.structure.representation.applyPreset(structureProperties || structure, StructurePreset);
+        });
     }
 }
 
@@ -260,4 +207,5 @@ const MergeStructures = PluginStateTransform.BuiltIn({
     }
 });
 
-(window as any).DockingViewer = Viewer;
+(window as any).DockingViewer = Viewer;
+export { Viewer as DockingViewer };

+ 67 - 46
src/examples/docking-viewer/viewport.tsx → src/apps/docking-viewer/viewport.tsx

@@ -17,10 +17,11 @@ import { StructureSelectionQueries, StructureSelectionQuery } from '../../mol-pl
 import { MolScriptBuilder as MS } from '../../mol-script/language/builder';
 import { InteractionsRepresentationProvider } from '../../mol-model-props/computed/representations/interactions';
 import { InteractionTypeColorThemeProvider } from '../../mol-model-props/computed/themes/interaction-type';
-import { compile } from '../../mol-script/runtime/query/compiler';
-import { StructureSelection, QueryContext, Structure } from '../../mol-model/structure';
 import { PluginCommands } from '../../mol-plugin/commands';
 import { PluginContext } from '../../mol-plugin/context';
+import { StructureRef } from '../../mol-plugin-state/manager/structure/hierarchy-state';
+import { Color } from '../../mol-util/color';
+import { PluginConfig } from '../../mol-plugin/config';
 
 function shinyStyle(plugin: PluginContext) {
     return PluginCommands.Canvas3D.SetSettings(plugin, { settings: {
@@ -76,6 +77,8 @@ const PresetParams = {
     ...StructureRepresentationPresetProvider.CommonParams,
 };
 
+
+
 export const StructurePreset = StructureRepresentationPresetProvider({
     id: 'preset-structure',
     display: { name: 'Structure' },
@@ -89,10 +92,10 @@ export const StructurePreset = StructureRepresentationPresetProvider({
             polymer: await presetStaticComponent(plugin, structureCell, 'polymer'),
         };
 
-        const { update, builder, typeParams, color } = StructureRepresentationPresetProvider.reprBuilder(plugin, params);
+        const { update, builder, typeParams } = StructureRepresentationPresetProvider.reprBuilder(plugin, params);
         const representations = {
-            ligand: builder.buildRepresentation(update, components.ligand, { type: 'ball-and-stick', typeParams: { ...typeParams, sizeFactor: 0.26 }, color }, { tag: 'ligand' }),
-            polymer: builder.buildRepresentation(update, components.polymer, { type: 'cartoon', typeParams: { ...typeParams }, color }, { tag: 'polymer' }),
+            ligand: builder.buildRepresentation(update, components.ligand, { type: 'ball-and-stick', typeParams: { ...typeParams, sizeFactor: 0.35 }, color: 'element-symbol', colorParams: { carbonColor: { name: 'element-symbol', params: {} } } }, { tag: 'ligand' }),
+            polymer: builder.buildRepresentation(update, components.polymer, { type: 'cartoon', typeParams: { ...typeParams }, color: 'chain-id', colorParams: { palette: (plugin.customState as any).colorPalette } }, { tag: 'polymer' }),
         };
 
         await update.commit({ revertOnError: true });
@@ -112,12 +115,14 @@ export const IllustrativePreset = StructureRepresentationPresetProvider({
         if (!structureCell) return {};
 
         const components = {
-            all: await presetStaticComponent(plugin, structureCell, 'all')
+            ligand: await presetStaticComponent(plugin, structureCell, 'ligand'),
+            polymer: await presetStaticComponent(plugin, structureCell, 'polymer'),
         };
 
         const { update, builder, typeParams } = StructureRepresentationPresetProvider.reprBuilder(plugin, params);
         const representations = {
-            all: builder.buildRepresentation(update, components.all, { type: 'spacefill', typeParams: { ...typeParams }, color: 'illustrative' }, { tag: 'all' }),
+            ligand: builder.buildRepresentation(update, components.ligand, { type: 'spacefill', typeParams: { ...typeParams }, color: 'element-symbol', colorParams: { carbonColor: { name: 'element-symbol', params: {} } } }, { tag: 'ligand' }),
+            polymer: builder.buildRepresentation(update, components.polymer, { type: 'spacefill', typeParams: { ...typeParams }, color: 'illustrative', colorParams: { palette: (plugin.customState as any).colorPalette } }, { tag: 'polymer' }),
         };
 
         await update.commit({ revertOnError: true });
@@ -128,6 +133,34 @@ export const IllustrativePreset = StructureRepresentationPresetProvider({
     }
 });
 
+const SurfacePreset = StructureRepresentationPresetProvider({
+    id: 'preset-surface',
+    display: { name: 'Surface' },
+    params: () => PresetParams,
+    async apply(ref, params, plugin) {
+        const structureCell = StateObjectRef.resolveAndCheck(plugin.state.data, ref);
+        const structure = structureCell?.obj?.data;
+        if (!structureCell || !structure) return {};
+
+        const components = {
+            ligand: await presetStaticComponent(plugin, structureCell, 'ligand'),
+            polymer: await presetStaticComponent(plugin, structureCell, 'polymer'),
+        };
+
+        const { update, builder, typeParams } = StructureRepresentationPresetProvider.reprBuilder(plugin, params);
+        const representations = {
+            ligand: builder.buildRepresentation(update, components.ligand, { type: 'ball-and-stick', typeParams: { ...typeParams, sizeFactor: 0.26 }, color: 'element-symbol', colorParams: { carbonColor: { name: 'element-symbol', params: {} } } }, { tag: 'ligand' }),
+            polymer: builder.buildRepresentation(update, components.polymer, { type: 'molecular-surface', typeParams: { ...typeParams, quality: 'custom', resolution: 0.5, doubleSided: true }, color: 'partial-charge' }, { tag: 'polymer' }),
+        };
+
+        await update.commit({ revertOnError: true });
+        await shinyStyle(plugin);
+        plugin.managers.interactivity.setProps({ granularity: 'residue' });
+
+        return { components, representations };
+    }
+});
+
 const PocketPreset = StructureRepresentationPresetProvider({
     id: 'preset-pocket',
     display: { name: 'Pocket' },
@@ -144,7 +177,7 @@ const PocketPreset = StructureRepresentationPresetProvider({
 
         const { update, builder, typeParams } = StructureRepresentationPresetProvider.reprBuilder(plugin, params);
         const representations = {
-            ligand: builder.buildRepresentation(update, components.ligand, { type: 'ball-and-stick', typeParams: { ...typeParams, sizeFactor: 0.26 }, color: 'partial-charge' }, { tag: 'ligand' }),
+            ligand: builder.buildRepresentation(update, components.ligand, { type: 'ball-and-stick', typeParams: { ...typeParams, sizeFactor: 0.26 }, color: 'element-symbol', colorParams: { carbonColor: { name: 'element-symbol', params: {} } } }, { tag: 'ligand' }),
             surroundings: builder.buildRepresentation(update, components.surroundings, { type: 'molecular-surface', typeParams: { ...typeParams, includeParent: true, quality: 'custom', resolution: 0.2, doubleSided: true }, color: 'partial-charge' }, { tag: 'surroundings' }),
         };
 
@@ -152,11 +185,6 @@ const PocketPreset = StructureRepresentationPresetProvider({
         await shinyStyle(plugin);
         plugin.managers.interactivity.setProps({ granularity: 'element' });
 
-        const compiled = compile<StructureSelection>(StructureSelectionQueries.ligand.expression);
-        const result = compiled(new QueryContext(structure));
-        const selection = StructureSelection.unionStructure(result);
-        plugin.managers.camera.focusLoci(Structure.toStructureElementLoci(selection));
-
         return { components, representations };
     }
 });
@@ -172,56 +200,46 @@ const InteractionsPreset = StructureRepresentationPresetProvider({
 
         const components = {
             ligand: await presetStaticComponent(plugin, structureCell, 'ligand'),
-            selection: await plugin.builders.structure.tryCreateComponentFromSelection(structureCell, ligandPlusSurroundings, `selection`)
+            surroundings: await plugin.builders.structure.tryCreateComponentFromSelection(structureCell, ligandSurroundings, `surroundings`),
+            interactions: await plugin.builders.structure.tryCreateComponentFromSelection(structureCell, ligandPlusSurroundings, `interactions`)
         };
 
         const { update, builder, typeParams } = StructureRepresentationPresetProvider.reprBuilder(plugin, params);
         const representations = {
-            ligand: builder.buildRepresentation(update, components.ligand, { type: 'ball-and-stick', typeParams: { ...typeParams, sizeFactor: 0.26 }, color: 'partial-charge' }, { tag: 'ligand' }),
-            ballAndStick: builder.buildRepresentation(update, components.selection, { type: 'ball-and-stick', typeParams: { ...typeParams, sizeFactor: 0.1, sizeAspectRatio: 1 }, color: 'partial-charge' }, { tag: 'ball-and-stick' }),
-            interactions: builder.buildRepresentation(update, components.selection, { type: InteractionsRepresentationProvider, typeParams: { ...typeParams }, color: InteractionTypeColorThemeProvider }, { tag: 'interactions' }),
+            ligand: builder.buildRepresentation(update, components.ligand, { type: 'ball-and-stick', typeParams: { ...typeParams, sizeFactor: 0.3 }, color: 'element-symbol', colorParams: { carbonColor: { name: 'element-symbol', params: {} } } }, { tag: 'ligand' }),
+            ballAndStick: builder.buildRepresentation(update, components.surroundings, { type: 'ball-and-stick', typeParams: { ...typeParams, sizeFactor: 0.1, sizeAspectRatio: 1 }, color: 'element-symbol', colorParams: { carbonColor: { name: 'element-symbol', params: {} } } }, { tag: 'ball-and-stick' }),
+            interactions: builder.buildRepresentation(update, components.interactions, { type: InteractionsRepresentationProvider, typeParams: { ...typeParams }, color: InteractionTypeColorThemeProvider }, { tag: 'interactions' }),
+            label: builder.buildRepresentation(update, components.surroundings, { type: 'label', typeParams: { ...typeParams, background: false, borderWidth: 0.1 }, color: 'uniform', colorParams: { value: Color(0x000000) } }, { tag: 'label' }),
         };
 
         await update.commit({ revertOnError: true });
         await shinyStyle(plugin);
         plugin.managers.interactivity.setProps({ granularity: 'element' });
 
-        const compiled = compile<StructureSelection>(StructureSelectionQueries.ligand.expression);
-        const result = compiled(new QueryContext(structure));
-        const selection = StructureSelection.unionStructure(result);
-        plugin.managers.camera.focusLoci(Structure.toStructureElementLoci(selection));
-
         return { components, representations };
     }
 });
 
+export const ShowButtons = PluginConfig.item('showButtons', true);
+
 export class ViewportComponent extends PluginUIComponent {
-    structurePreset = () => {
-        this.plugin.managers.structure.component.applyPreset(
-            this.plugin.managers.structure.hierarchy.selection.structures,
-            StructurePreset
-        );
+    async _set(structures: readonly StructureRef[], preset: StructureRepresentationPresetProvider) {
+        await this.plugin.managers.structure.component.clear(structures);
+        await this.plugin.managers.structure.component.applyPreset(structures, preset);
     }
 
-    illustrativePreset = () => {
-        this.plugin.managers.structure.component.applyPreset(
-            this.plugin.managers.structure.hierarchy.selection.structures,
-            IllustrativePreset
-        );
+    set = async (preset: StructureRepresentationPresetProvider) => {
+        await this._set(this.plugin.managers.structure.hierarchy.selection.structures, preset);
     }
 
-    pocketPreset = () => {
-        this.plugin.managers.structure.component.applyPreset(
-            this.plugin.managers.structure.hierarchy.selection.structures,
-            PocketPreset
-        );
-    }
+    structurePreset = () => this.set(StructurePreset);
+    illustrativePreset = () => this.set(IllustrativePreset);
+    surfacePreset = () => this.set(SurfacePreset);
+    pocketPreset = () => this.set(PocketPreset);
+    interactionsPreset = () => this.set(InteractionsPreset);
 
-    interactionsPreset = () => {
-        this.plugin.managers.structure.component.applyPreset(
-            this.plugin.managers.structure.hierarchy.selection.structures,
-            InteractionsPreset
-        );
+    get showButtons () {
+        return this.plugin.config.get(ShowButtons);
     }
 
     render() {
@@ -229,7 +247,7 @@ export class ViewportComponent extends PluginUIComponent {
 
         return <>
             <Viewport />
-            <div className='msp-viewport-top-left-controls'>
+            {this.showButtons && <div className='msp-viewport-top-left-controls'>
                 <div style={{ marginBottom: '4px' }}>
                     <Button onClick={this.structurePreset} >Structure</Button>
                 </div>
@@ -237,12 +255,15 @@ export class ViewportComponent extends PluginUIComponent {
                     <Button onClick={this.illustrativePreset}>Illustrative</Button>
                 </div>
                 <div style={{ marginBottom: '4px' }}>
-                    <Button onClick={this.pocketPreset}>Pocket</Button>
+                    <Button onClick={this.surfacePreset}>Surface</Button>
                 </div>
+                {/* <div style={{ marginBottom: '4px' }}>
+                    <Button onClick={this.pocketPreset}>Pocket</Button>
+                </div> */}
                 <div style={{ marginBottom: '4px' }}>
                     <Button onClick={this.interactionsPreset}>Interactions</Button>
                 </div>
-            </div>
+            </div>}
             <VPControls />
             <BackgroundTaskProgress />
             <div className='msp-highlight-toast-wrapper'>

+ 1 - 1
src/apps/viewer/index.ts

@@ -92,7 +92,7 @@ export class Viewer {
             },
             components: {
                 ...DefaultPluginSpec.components,
-                remoteState: o.layoutShowRemoteState ? 'default' : 'none',
+                remoteState: o.layoutShowRemoteState ? 'default' : 'none'
             },
             config: [
                 [PluginConfig.Viewport.ShowExpand, o.viewportShowExpand],

+ 4 - 3
src/mol-canvas3d/canvas3d.ts

@@ -66,6 +66,7 @@ export const Canvas3DParams = {
 };
 export const DefaultCanvas3DParams = PD.getDefaultValues(Canvas3DParams);
 export type Canvas3DProps = PD.Values<typeof Canvas3DParams>
+export type PartialCanvas3DProps = { [K in keyof Canvas3DProps]?: Partial<Canvas3DProps[K]> }
 
 export { Canvas3D };
 
@@ -97,7 +98,7 @@ interface Canvas3D {
     readonly camera: Camera
     readonly boundingSphere: Readonly<Sphere3D>
     getPixelData(variant: GraphicsRenderVariant): PixelData
-    setProps(props: Partial<Canvas3DProps> | ((old: Canvas3DProps) => Partial<Canvas3DProps> | void)): void
+    setProps(props: PartialCanvas3DProps | ((old: Canvas3DProps) => Partial<Canvas3DProps> | void)): void
     getImagePass(props: Partial<ImageProps>): ImagePass
 
     /** Returns a copy of the current Canvas3D instance props */
@@ -530,7 +531,7 @@ namespace Canvas3D {
             didDraw,
             reprCount,
             setProps: (properties) => {
-                const props: Partial<Canvas3DProps> = typeof properties === 'function'
+                const props: PartialCanvas3DProps = typeof properties === 'function'
                     ? produce(getProps(), properties)
                     : properties;
 
@@ -538,7 +539,7 @@ namespace Canvas3D {
                 if (props.camera && props.camera.mode !== undefined && props.camera.mode !== camera.state.mode) {
                     cameraState.mode = props.camera.mode;
                 }
-                if (props.cameraFog !== undefined) {
+                if (props.cameraFog !== undefined && props.cameraFog.params) {
                     const newFog = props.cameraFog.name === 'on' ? props.cameraFog.params.intensity : 0;
                     if (newFog !== camera.state.fog) cameraState.fog = newFog;
                 }

+ 4 - 0
src/mol-io/reader/cif/data-model.ts

@@ -265,6 +265,10 @@ export namespace CifField {
             toFloatArray: params => ColumnHelpers.createAndFillArray(rowCount, float, params)
         };
     }
+
+    export function ofUndefined(rowCount: number, schema: Column.Schema): CifField {
+        return ofColumn(Column.Undefined(rowCount, schema));
+    }
 }
 
 export function tensorFieldNameGetter(field: string, rank: number, zeroIndexed: boolean, namingVariant: 'brackets' | 'underscore') {

+ 16 - 2
src/mol-model-formats/structure/basic/atomic.ts

@@ -67,13 +67,27 @@ function createHierarchyData(atom_site: AtomSite, sourceIndex: Column<number>, o
     });
 
     const residues = Table.view(atom_site, ResiduesSchema, offsets.residues);
+    const chains = Table.view(atom_site, ChainsSchema, offsets.chains);
+
+    if (!residues.label_seq_id.isDefined) {
+        const seqIds = new Int32Array(residues.label_seq_id.rowCount);
+        const { residues: residueOffsets, chains: chainOffsets } = offsets;
+        let cI = 0;
+        let seqId = 0;
+        for (let i = 0, il = seqIds.length; i < il; ++i) {
+            if (chainOffsets[cI] > residueOffsets[i]) {
+                cI += 1;
+                seqId = 0;
+            }
+            seqIds[i] = ++seqId;
+        }
+        residues.label_seq_id = Column.ofIntArray(seqIds);
+    }
 
     // Optimize the numeric columns
     Table.columnToArray(residues, 'label_seq_id', Int32Array);
     Table.columnToArray(residues, 'auth_seq_id', Int32Array);
 
-    const chains = Table.view(atom_site, ChainsSchema, offsets.chains);
-
     // Fix possibly missing auth_/label_ columns
     substUndefinedColumn(atoms, 'label_atom_id', 'auth_atom_id');
     substUndefinedColumn(atoms, 'label_comp_id', 'auth_comp_id');

+ 3 - 3
src/mol-model-formats/structure/pdb/atom-site.ts

@@ -9,6 +9,7 @@ import { CifField } from '../../../mol-io/reader/cif';
 import { mmCIF_Schema } from '../../../mol-io/reader/cif/schema/mmcif';
 import { TokenBuilder, Tokenizer } from '../../../mol-io/reader/common/text/tokenizer';
 import { guessElementSymbolTokens } from '../util';
+import { Column } from '../../../mol-data/db';
 
 type AtomSiteTemplate = typeof getAtomSiteTemplate extends (...args: any) => infer T ? T : never
 export function getAtomSiteTemplate(data: string, count: number) {
@@ -42,13 +43,12 @@ export function getAtomSite(sites: AtomSiteTemplate): { [K in keyof mmCIF_Schema
     const auth_asym_id = CifField.ofTokens(sites.auth_asym_id);
     const auth_atom_id = CifField.ofTokens(sites.auth_atom_id);
     const auth_comp_id = CifField.ofTokens(sites.auth_comp_id);
-    const auth_seq_id = CifField.ofTokens(sites.auth_seq_id);
 
     return {
         auth_asym_id,
         auth_atom_id,
         auth_comp_id,
-        auth_seq_id,
+        auth_seq_id: CifField.ofTokens(sites.auth_seq_id),
         B_iso_or_equiv: CifField.ofTokens(sites.B_iso_or_equiv),
         Cartn_x: CifField.ofTokens(sites.Cartn_x),
         Cartn_y: CifField.ofTokens(sites.Cartn_y),
@@ -61,7 +61,7 @@ export function getAtomSite(sites: AtomSiteTemplate): { [K in keyof mmCIF_Schema
         label_asym_id: auth_asym_id,
         label_atom_id: auth_atom_id,
         label_comp_id: auth_comp_id,
-        label_seq_id: auth_seq_id,
+        label_seq_id: CifField.ofUndefined(sites.count, Column.Schema.int),
         label_entity_id: CifField.ofStrings(sites.label_entity_id),
 
         occupancy: CifField.ofTokens(sites.occupancy),

+ 7 - 8
src/mol-model-formats/structure/pdb/secondary-structure.ts

@@ -7,6 +7,7 @@
 import { CifCategory, CifField } from '../../../mol-io/reader/cif';
 import { mmCIF_Schema } from '../../../mol-io/reader/cif/schema/mmcif';
 import { Tokens } from '../../../mol-io/reader/common/text/tokenizer';
+import { Column } from '../../../mol-data/db';
 
 const HelixTypes: {[k: string]: mmCIF_Schema['struct_conf']['conf_type_id']['T']} = {
     // CLASS NUMBER
@@ -99,29 +100,27 @@ export function parseHelix(lines: Tokens, lineStart: number, lineEnd: number): C
 
     const beg_auth_asym_id = CifField.ofStrings(helices.map(h => h.initChainID));
     const beg_auth_comp_id = CifField.ofStrings(helices.map(h => h.initResName));
-    const beg_auth_seq_id = CifField.ofStrings(helices.map(h => h.initSeqNum));
 
     const end_auth_asym_id = CifField.ofStrings(helices.map(h => h.endChainID));
-    const end_auth_comp_id = CifField.ofStrings(helices.map(h => h.endResName));
-    const end_auth_seq_id = CifField.ofStrings(helices.map(h => h.endSeqNum));
+    const end_auth_comp_id = CifField.ofStrings(helices.map(h => h.endResName));;
 
     const struct_conf: CifCategory.Fields<mmCIF_Schema['struct_conf']> = {
         beg_label_asym_id: beg_auth_asym_id,
         beg_label_comp_id: beg_auth_comp_id,
-        beg_label_seq_id: beg_auth_seq_id,
+        beg_label_seq_id: CifField.ofUndefined(helices.length, Column.Schema.int),
         beg_auth_asym_id,
         beg_auth_comp_id,
-        beg_auth_seq_id,
+        beg_auth_seq_id: CifField.ofStrings(helices.map(h => h.initSeqNum)),
 
         conf_type_id: CifField.ofStrings(helices.map(h => getStructConfTypeId(h.helixClass))),
         details: CifField.ofStrings(helices.map(h => h.comment)),
 
         end_label_asym_id: end_auth_asym_id,
-        end_label_comp_id: end_auth_asym_id,
-        end_label_seq_id: end_auth_seq_id,
+        end_label_comp_id: end_auth_comp_id,
+        end_label_seq_id: CifField.ofUndefined(helices.length, Column.Schema.int),
         end_auth_asym_id,
         end_auth_comp_id,
-        end_auth_seq_id,
+        end_auth_seq_id: CifField.ofStrings(helices.map(h => h.endSeqNum)),
 
         id: CifField.ofStrings(helices.map(h => h.serNum)),
         pdbx_beg_PDB_ins_code: CifField.ofStrings(helices.map(h => h.initICode)),

+ 12 - 12
src/mol-model-formats/structure/property/secondary-structure.ts

@@ -62,8 +62,8 @@ type SecondaryStructureData = { type: SecondaryStructureType[], key: number[], e
 function addHelices(cat: StructConf, map: SecondaryStructureMap, elements: SecondaryStructure.Element[]) {
     if (!cat._rowCount) return;
 
-    const { beg_label_asym_id, beg_label_seq_id, pdbx_beg_PDB_ins_code } = cat;
-    const { end_label_seq_id, pdbx_end_PDB_ins_code } = cat;
+    const { beg_label_asym_id, beg_auth_seq_id, pdbx_beg_PDB_ins_code } = cat;
+    const { end_auth_seq_id, pdbx_end_PDB_ins_code } = cat;
     const { pdbx_PDB_helix_class, conf_type_id, details } = cat;
 
     for (let i = 0, _i = cat._rowCount; i < _i; i++) {
@@ -81,9 +81,9 @@ function addHelices(cat: StructConf, map: SecondaryStructureMap, elements: Secon
             details: details.valueKind(i) === Column.ValueKind.Present ? details.value(i) : void 0
         };
         const entry: SecondaryStructureEntry = {
-            startSeqNumber: beg_label_seq_id.value(i),
+            startSeqNumber: beg_auth_seq_id.value(i),
             startInsCode: pdbx_beg_PDB_ins_code.value(i),
-            endSeqNumber: end_label_seq_id.value(i),
+            endSeqNumber: end_auth_seq_id.value(i),
             endInsCode: pdbx_end_PDB_ins_code.value(i),
             type,
             key: elements.length
@@ -108,8 +108,8 @@ function addHelices(cat: StructConf, map: SecondaryStructureMap, elements: Secon
 function addSheets(cat: StructSheetRange, map: SecondaryStructureMap, sheetCount: number, elements: SecondaryStructure.Element[]) {
     if (!cat._rowCount) return;
 
-    const { beg_label_asym_id, beg_label_seq_id, pdbx_beg_PDB_ins_code } = cat;
-    const { end_label_seq_id, pdbx_end_PDB_ins_code } = cat;
+    const { beg_label_asym_id, beg_auth_seq_id, pdbx_beg_PDB_ins_code } = cat;
+    const { end_auth_seq_id, pdbx_end_PDB_ins_code } = cat;
     const { sheet_id } = cat;
 
     const sheet_id_key = new Map<string, number>();
@@ -132,9 +132,9 @@ function addSheets(cat: StructSheetRange, map: SecondaryStructureMap, sheetCount
             symmetry: void 0
         };
         const entry: SecondaryStructureEntry = {
-            startSeqNumber: beg_label_seq_id.value(i),
+            startSeqNumber: beg_auth_seq_id.value(i),
             startInsCode: pdbx_beg_PDB_ins_code.value(i),
-            endSeqNumber: end_label_seq_id.value(i),
+            endSeqNumber: end_auth_seq_id.value(i),
             endInsCode: pdbx_end_PDB_ins_code.value(i),
             type,
             key: elements.length
@@ -159,12 +159,12 @@ function addSheets(cat: StructSheetRange, map: SecondaryStructureMap, sheetCount
 }
 
 function assignSecondaryStructureEntry(hierarchy: AtomicHierarchy, entry: SecondaryStructureEntry, resStart: ResidueIndex, resEnd: ResidueIndex, data: SecondaryStructureData) {
-    const { label_seq_id, pdbx_PDB_ins_code } = hierarchy.residues;
+    const { auth_seq_id, pdbx_PDB_ins_code } = hierarchy.residues;
     const { endSeqNumber, endInsCode, key, type } = entry;
 
     let rI = resStart;
     while (rI < resEnd) {
-        const seqNumber = label_seq_id.value(rI);
+        const seqNumber = auth_seq_id.value(rI);
         data.type[rI] = type;
         data.key[rI] = key;
 
@@ -180,7 +180,7 @@ function assignSecondaryStructureEntry(hierarchy: AtomicHierarchy, entry: Second
 function assignSecondaryStructureRanges(hierarchy: AtomicHierarchy, map: SecondaryStructureMap, data: SecondaryStructureData) {
     const { count: chainCount } = hierarchy.chainAtomSegments;
     const { label_asym_id } = hierarchy.chains;
-    const { label_seq_id, pdbx_PDB_ins_code } = hierarchy.residues;
+    const { auth_seq_id, pdbx_PDB_ins_code } = hierarchy.residues;
 
     for (let cI = 0 as ChainIndex; cI < chainCount; cI++) {
         const resStart = AtomicHierarchy.chainStartResidueIndex(hierarchy, cI), resEnd = AtomicHierarchy.chainEndResidueIndexExcl(hierarchy, cI);
@@ -189,7 +189,7 @@ function assignSecondaryStructureRanges(hierarchy: AtomicHierarchy, map: Seconda
             const entries = map.get(asymId)!;
 
             for (let rI = resStart; rI < resEnd; rI++) {
-                const seqNumber = label_seq_id.value(rI);
+                const seqNumber = auth_seq_id.value(rI);
                 if (entries.has(seqNumber)) {
                     const entryList = entries.get(seqNumber)!;
                     for (const entry of entryList) {

+ 1 - 0
src/mol-model/structure/export/categories/atom_site.ts

@@ -52,6 +52,7 @@ const atom_site_fields = CifWriter.fields<StructureElement.Location, Structure>(
     .float('Cartn_y', P.atom.y, { digitCount: 3, encoder: E.fixedPoint3 })
     .float('Cartn_z', P.atom.z, { digitCount: 3, encoder: E.fixedPoint3 })
     .float('occupancy', P.atom.occupancy, { digitCount: 2, encoder: E.fixedPoint2 })
+    .float('B_iso_or_equiv', P.atom.B_iso_or_equiv, { digitCount: 2, encoder: E.fixedPoint2 })
     .int('pdbx_formal_charge', P.atom.pdbx_formal_charge, {
         encoder: E.deltaRLE,
         valueKind: (k, d) =>  k.unit.model.atomicHierarchy.atoms.pdbx_formal_charge.valueKind(k.element)

+ 2 - 2
src/mol-model/structure/structure/properties.ts

@@ -52,7 +52,7 @@ const atom = {
     auth_atom_id: p(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.atomicHierarchy.atoms.auth_atom_id.value(l.element)),
     label_alt_id: p(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.atomicHierarchy.atoms.label_alt_id.value(l.element)),
     label_comp_id: p(compId),
-    auth_comp_id: p(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.atomicHierarchy.atoms.auth_comp_id.value(l.unit.residueIndex[l.element])),
+    auth_comp_id: p(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.atomicHierarchy.atoms.auth_comp_id.value(l.element)),
     pdbx_formal_charge: p(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.atomicHierarchy.atoms.pdbx_formal_charge.value(l.element)),
 
     // Derived
@@ -93,7 +93,7 @@ const residue = {
     pdbx_PDB_ins_code: p(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.atomicHierarchy.residues.pdbx_PDB_ins_code.value(l.unit.residueIndex[l.element])),
 
     // Properties
-    isNonStandard: p(l => !Unit.isAtomic(l.unit) ? notAtomic() : microheterogeneityCompIds(l).some(c => l.unit.model.properties.chemicalComponentMap.get(c)!.mon_nstd_flag[0] !== 'y')),
+    isNonStandard: p(l => !Unit.isAtomic(l.unit) ? notAtomic() : microheterogeneityCompIds(l).some(c => l.unit.model.properties.chemicalComponentMap.get(c)!.mon_nstd_flag[0] === 'n')),
     hasMicroheterogeneity: p(hasMicroheterogeneity),
     microheterogeneityCompIds: p(microheterogeneityCompIds),
     secondary_structure_type: p(l => {

+ 45 - 2
src/mol-plugin-state/builder/structure/hierarchy-preset.ts

@@ -16,6 +16,7 @@ import { PluginContext } from '../../../mol-plugin/context';
 import { Vec3 } from '../../../mol-math/linear-algebra';
 import { Model } from '../../../mol-model/structure';
 import { getStructureQuality } from '../../../mol-repr/util';
+import { OperatorNameColorThemeProvider } from '../../../mol-theme/color/operator-name';
 
 export interface TrajectoryHierarchyPresetProvider<P = any, S = {}> extends PresetProvider<PluginStateObject.Molecule.Trajectory, P, S> { }
 export function TrajectoryHierarchyPresetProvider<P, S>(preset: TrajectoryHierarchyPresetProvider<P, S>) { return preset; }
@@ -100,7 +101,7 @@ const allModels = TrajectoryHierarchyPresetProvider({
             structures.push(structure);
 
             const quality = structure.obj ? getStructureQuality(structure.obj.data, { elementCountFactor: tr.length }) : 'medium';
-            await builder.representation.applyPreset(structureProperties, params.representationPreset || 'auto', { globalThemeName: 'model-index', quality });
+            await builder.representation.applyPreset(structureProperties, params.representationPreset || 'auto', { theme: { globalName: 'model-index' }, quality });
         }
 
         return { models, structures };
@@ -125,7 +126,7 @@ async function applyCrystalSymmetry(props: { ijkMin: Vec3, ijkMax: Vec3, theme?:
     const structureProperties = await builder.insertStructureProperties(structure, params.structureProperties);
 
     const unitcell = await builder.tryCreateUnitcell(modelProperties, undefined, { isHidden: false });
-    const representation =  await plugin.builders.structure.representation.applyPreset(structureProperties, params.representationPreset || 'auto', { globalThemeName: props.theme });
+    const representation =  await plugin.builders.structure.representation.applyPreset(structureProperties, params.representationPreset || 'auto', { theme: { globalName: props.theme } });
 
     return {
         model,
@@ -167,10 +168,52 @@ const supercell = TrajectoryHierarchyPresetProvider({
     }
 });
 
+const CrystalContactsParams = (a: PluginStateObject.Molecule.Trajectory | undefined, plugin: PluginContext) => ({
+    model: PD.Optional(PD.Group(StateTransformer.getParamDefinition(StateTransforms.Model.ModelFromTrajectory, a, plugin))),
+    ...CommonParams(a, plugin)
+});
+
+const crystalContacts = TrajectoryHierarchyPresetProvider({
+    id: 'preset-trajectory-crystal-contacts',
+    display: {
+        name: 'Crystal Contacts', group: 'Preset',
+        description: 'Showsasymetric unit and chains from neighbours within 5 \u212B, i.e., symmetry mates.'
+    },
+    isApplicable: o => {
+        return Model.hasCrystalSymmetry(o.data[0]);
+    },
+    params: CrystalContactsParams,
+    async apply(trajectory, params, plugin) {
+        const builder = plugin.builders.structure;
+
+        const model = await builder.createModel(trajectory, params.model);
+        const modelProperties = await builder.insertModelProperties(model, params.modelProperties);
+
+        const structure = await builder.createStructure(modelProperties || model, {
+            name: 'symmetry-mates',
+            params: { radius: 5 }
+        });
+        const structureProperties = await builder.insertStructureProperties(structure, params.structureProperties);
+
+        const unitcell = await builder.tryCreateUnitcell(modelProperties, undefined, { isHidden: true });
+        const representation =  await plugin.builders.structure.representation.applyPreset(structureProperties, params.representationPreset || 'auto', { theme: { globalName: 'operator-name', carbonColor: 'operator-name', focus: { name: 'element-symbol', params: { carbonColor: { name: 'operator-name', params: OperatorNameColorThemeProvider.defaultValues } } } } });
+
+        return {
+            model,
+            modelProperties,
+            unitcell,
+            structure,
+            structureProperties,
+            representation
+        };
+    }
+});
+
 export const PresetTrajectoryHierarchy = {
     'default': defaultPreset,
     'all-models': allModels,
     unitcell,
     supercell,
+    crystalContacts,
 };
 export type PresetTrajectoryHierarchy = typeof PresetTrajectoryHierarchy;

+ 17 - 7
src/mol-plugin-state/builder/structure/representation-preset.ts

@@ -18,6 +18,8 @@ import { StructureSelectionQueries as Q } from '../../helpers/structure-selectio
 import { PluginConfig } from '../../../mol-plugin/config';
 import { StructureFocusRepresentation } from '../../../mol-plugin/behavior/dynamic/selection/structure-focus-representation';
 import { createStructureColorThemeParams } from '../../helpers/structure-representation-params';
+import { ChainIdColorThemeProvider } from '../../../mol-theme/color/chain-id';
+import { OperatorNameColorThemeProvider } from '../../../mol-theme/color/operator-name';
 
 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; }
@@ -34,7 +36,7 @@ export namespace StructureRepresentationPresetProvider {
         quality: PD.Optional(PD.Select<VisualQuality>('auto', VisualQualityOptions)),
         theme: PD.Optional(PD.Group({
             globalName: PD.Optional(PD.Text<ColorTheme.BuiltIn>('')),
-            carbonByChainId: PD.Optional(PD.Boolean(true)),
+            carbonColor: PD.Optional(PD.Select('chain-id', PD.arrayToOptions(['chain-id', 'operator-name', 'element-symbol'] as const))),
             focus: PD.Optional(PD.Group({
                 name: PD.Optional(PD.Text<ColorTheme.BuiltIn>('')),
                 params: PD.Optional(PD.Value<ColorTheme.BuiltInParams<ColorTheme.BuiltIn>>({} as any))
@@ -43,6 +45,14 @@ export namespace StructureRepresentationPresetProvider {
     };
     export type CommonParams = PD.ValuesFor<typeof CommonParams>
 
+    function getCarbonColorParams(name: 'chain-id' | 'operator-name' | 'element-symbol') {
+        return name === 'chain-id'
+            ? { name, params: ChainIdColorThemeProvider.defaultValues }
+            : name === 'operator-name'
+                ? { name, params: OperatorNameColorThemeProvider.defaultValues }
+                : { name, params: {} };
+    }
+
     export function reprBuilder(plugin: PluginContext, params: CommonParams) {
         const update = plugin.state.data.build();
         const builder = plugin.builders.structure.representation;
@@ -53,16 +63,16 @@ export namespace StructureRepresentationPresetProvider {
         if (params.quality && params.quality !== 'auto') typeParams.quality = params.quality;
         if (params.ignoreHydrogens !== void 0) typeParams.ignoreHydrogens = !!params.ignoreHydrogens;
         const color: ColorTheme.BuiltIn | undefined = params.theme?.globalName ? params.theme?.globalName : void 0;
-        const ballAndStickColor: ColorTheme.BuiltInParams<'element-symbol'> = typeof params.theme?.carbonByChainId !== 'undefined' ? { carbonByChainId: !!params.theme?.carbonByChainId } : { };
+        const ballAndStickColor: ColorTheme.BuiltInParams<'element-symbol'> = params.theme?.carbonColor !== undefined
+            ? { carbonColor: getCarbonColorParams(params.theme?.carbonColor) }
+            : { };
 
         return { update, builder, color, typeParams, ballAndStickColor };
     }
 
     export function updateFocusRepr<T extends ColorTheme.BuiltIn>(plugin: PluginContext, structure: Structure, themeName: T | undefined, themeParams: ColorTheme.BuiltInParams<T> | undefined) {
-        if (!themeName && !themeParams) return;
-
         return plugin.state.updateBehavior(StructureFocusRepresentation, p => {
-            const c = createStructureColorThemeParams(plugin, structure, 'ball-and-stick', themeName, themeParams);
+            const c = createStructureColorThemeParams(plugin, structure, 'ball-and-stick', themeName || 'element-symbol', themeParams);
             p.surroundingsParams.colorTheme = c;
             p.targetParams.colorTheme = c;
         });
@@ -155,8 +165,8 @@ const polymerAndLigand = StructureRepresentationPresetProvider({
             branchedBallAndStick: builder.buildRepresentation(update, components.branched, { type: 'ball-and-stick', typeParams: { ...typeParams, alpha: 0.3 }, color, colorParams: ballAndStickColor }, { tag: 'branched-ball-and-stick' }),
             branchedSnfg3d: builder.buildRepresentation(update, components.branched, { type: 'carbohydrate', typeParams, color }, { tag: 'branched-snfg-3d' }),
             water: builder.buildRepresentation(update, components.water, { type: waterType, typeParams: { ...typeParams, alpha: 0.6 }, color }, { tag: 'water' }),
-            ion: builder.buildRepresentation(update, components.ion, { type: 'ball-and-stick', typeParams, color, colorParams: { carbonByChainId: false } }, { tag: 'ion' }),
-            lipid: builder.buildRepresentation(update, components.lipid, { type: lipidType, typeParams: { ...typeParams, alpha: 0.6 }, color, colorParams: { carbonByChainId: false } }, { tag: 'lipid' }),
+            ion: builder.buildRepresentation(update, components.ion, { type: 'ball-and-stick', typeParams, color, colorParams: { carbonColor: { name: 'element-symbol', params: {} } } }, { tag: 'ion' }),
+            lipid: builder.buildRepresentation(update, components.lipid, { type: lipidType, typeParams: { ...typeParams, alpha: 0.6 }, color, colorParams: { carbonColor: { name: 'element-symbol', params: {} } } }, { tag: 'lipid' }),
             coarse: builder.buildRepresentation(update, components.coarse, { type: 'spacefill', typeParams, color: color || 'chain-id' }, { tag: 'coarse' })
         };
 

+ 6 - 0
src/mol-plugin-state/manager/loci-label.ts

@@ -22,6 +22,12 @@ export type LociLabelProvider = {
 export class LociLabelManager {
     providers: LociLabelProvider[] = [];
 
+    clearProviders() {
+        this.providers = [];
+        this.isDirty = true;
+        this.showLabels();
+    }
+
     addProvider(provider: LociLabelProvider) {
         this.providers.push(provider);
         this.providers.sort((a, b) => (b.priority || 0) - (a.priority || 0));

+ 30 - 7
src/mol-plugin-state/transforms/data.ts

@@ -5,7 +5,6 @@
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
 
-import { isTypedArray } from '../../mol-data/db/column-helpers';
 import * as CCP4 from '../../mol-io/reader/ccp4/parser';
 import { CIF } from '../../mol-io/reader/cif';
 import * as DSN6 from '../../mol-io/reader/dsn6/parser';
@@ -135,18 +134,23 @@ const RawData = PluginStateTransform.BuiltIn({
     from: [SO.Root],
     to: [SO.Data.String, SO.Data.Binary],
     params: {
-        data: PD.Value<string | number[]>('', { isHidden: true }),
+        data: PD.Value<string | number[] | ArrayBuffer | Uint8Array>('', { isHidden: true }),
         label: PD.Optional(PD.Text(''))
     }
 })({
     apply({ params: p }) {
         return Task.create('Raw Data', async () => {
-            if (typeof p.data !== 'string' && isTypedArray(p.data)) {
-                throw new Error('Supplied binary data must be a plain array.');
+            if (typeof p.data === 'string') {
+                return new SO.Data.String(p.data as string, { label: p.label ? p.label : 'String' });
+            } else if (Array.isArray(p.data)) {
+                return new SO.Data.Binary(new Uint8Array(p.data), { label: p.label ? p.label : 'Binary' });
+            } else if (p.data instanceof ArrayBuffer) {
+                return new SO.Data.Binary(new Uint8Array(p.data), { label: p.label ? p.label : 'Binary' });
+            } else if (p.data instanceof Uint8Array) {
+                return new SO.Data.Binary(p.data, { label: p.label ? p.label : 'Binary' });
+            } else {
+                throw new Error('Supplied binary data must be a plain array, ArrayBuffer, or Uint8Array.');
             }
-            return typeof p.data === 'string'
-                ? new SO.Data.String(p.data as string, { label: p.label ? p.label : 'String' })
-                : new SO.Data.Binary(new Uint8Array(p.data), { label: p.label ? p.label : 'Binary' });
         });
     },
     update({ oldParams, newParams, b }) {
@@ -156,6 +160,25 @@ const RawData = PluginStateTransform.BuiltIn({
             return StateTransformer.UpdateResult.Updated;
         }
         return StateTransformer.UpdateResult.Unchanged;
+    },
+    customSerialization: {
+        toJSON(p) {
+            if (typeof p.data === 'string' || Array.isArray(p.data)) {
+                return p;
+            } else if (p.data instanceof ArrayBuffer) {
+                const v = new Uint8Array(p.data);
+                const data = new Array(v.length);
+                for (let i = 0, _i = v.length; i < _i; i++) data[i] = v[i];
+                return { data, label: p.label };
+            } else if (p.data instanceof Uint8Array) {
+                const data = new Array(p.data.length);
+                for (let i = 0, _i = p.data.length; i < _i; i++) data[i] = p.data[i];
+                return { data, label: p.label };
+            }
+        },
+        fromJSON(data: any) {
+            return data;
+        }
     }
 });
 

+ 14 - 13
src/mol-plugin/context.ts

@@ -182,8 +182,7 @@ export class PluginContext {
 
             (this.canvas3d as Canvas3D) = Canvas3D.fromCanvas(canvas);
             this.canvas3dInit.next(true);
-            const renderer = this.canvas3d!.props.renderer;
-            PluginCommands.Canvas3D.SetSettings(this, { settings: { renderer: { ...renderer, backgroundColor: Color(0xFCFBF9) } } });
+            this.canvas3d?.setProps(this.spec.components?.viewport?.canvas3d || { renderer: { backgroundColor: Color(0xFCFBF9) } });
             this.canvas3d!.animate();
             (this.helpers.viewportScreenshot as ViewportScreenshotHelper) = new ViewportScreenshotHelper(this);
             return true;
@@ -346,29 +345,31 @@ export class PluginContext {
         }
     }
 
-    constructor(public spec: PluginSpec) {
-        // the reason for this is that sometimes, transform params get modified inline (i.e. palette.valueLabel)
-        // and freezing the params object causes "read-only exception"
-        // TODO: is this the best place to do it?
-        setAutoFreeze(false);
-
+    async init() {
         this.events.log.subscribe(e => this.log.entries = this.log.entries.push(e));
 
         this.initBehaviorEvents();
         this.initBuiltInBehavior();
 
-        this.initBehaviors();
+        (this.managers.interactivity as InteractivityManager) = new InteractivityManager(this);
+        (this.managers.lociLabels as LociLabelManager) = new LociLabelManager(this);
+        (this.builders.structure as StructureBuilder) = new StructureBuilder(this);
+
         this.initDataActions();
         this.initAnimations();
         this.initCustomParamEditors();
 
-        (this.managers.interactivity as InteractivityManager) = new InteractivityManager(this);
-        (this.managers.lociLabels as LociLabelManager) = new LociLabelManager(this);
-
-        (this.builders.structure as StructureBuilder) = new StructureBuilder(this);
+        await this.initBehaviors();
 
         this.log.message(`Mol* Plugin ${PLUGIN_VERSION} [${PLUGIN_VERSION_DATE.toLocaleString()}]`);
         if (!isProductionMode) this.log.message(`Development mode enabled`);
         if (isDebugMode) this.log.message(`Debug mode enabled`);
     }
+
+    constructor(public spec: PluginSpec) {
+        // the reason for this is that sometimes, transform params get modified inline (i.e. palette.valueLabel)
+        // and freezing the params object causes "read-only exception"
+        // TODO: is this the best place to do it?
+        setAutoFreeze(false);
+    }
 }

+ 10 - 0
src/mol-plugin/index.ts

@@ -94,6 +94,16 @@ export const DefaultPluginSpec: PluginSpec = {
 
 export function createPlugin(target: HTMLElement, spec?: PluginSpec): PluginContext {
     const ctx = new PluginContext(spec || DefaultPluginSpec);
+    ctx.init();
     ReactDOM.render(React.createElement(Plugin, { plugin: ctx }), target);
     return ctx;
+}
+
+/** Returns the instance of the plugin after all behaviors have been initialized */
+export async function createPluginAsync(target: HTMLElement, spec?: PluginSpec) {
+    const ctx = new PluginContext(spec || DefaultPluginSpec);
+    const init = ctx.init();
+    ReactDOM.render(React.createElement(Plugin, { plugin: ctx }), target);
+    await init;
+    return ctx;
 }

+ 3 - 1
src/mol-plugin/spec.ts

@@ -10,6 +10,7 @@ import { StateTransformParameters } from '../mol-plugin-ui/state/common';
 import { PluginLayoutStateProps } from './layout';
 import { PluginStateAnimation } from '../mol-plugin-state/animation/model';
 import { PluginConfigItem } from './config';
+import { PartialCanvas3DProps } from '../mol-canvas3d/canvas3d';
 
 export { PluginSpec };
 
@@ -27,7 +28,8 @@ interface PluginSpec {
         structureTools?: React.ComponentClass,
         viewport?: {
             view?: React.ComponentClass,
-            controls?: React.ComponentClass
+            controls?: React.ComponentClass,
+            canvas3d?: PartialCanvas3DProps
         }
     },
     config?: [PluginConfigItem, unknown][]

+ 1 - 1
src/mol-state/transform.ts

@@ -194,7 +194,7 @@ namespace Transform {
     export function fromJSON(t: Serialized): Transform {
         const transformer = StateTransformer.get(t.transformer);
         const pFromJson = transformer.definition.customSerialization
-            ? transformer.definition.customSerialization.toJSON
+            ? transformer.definition.customSerialization.fromJSON
             : _id;
         return {
             parent: t.parent as Ref,

+ 3 - 0
src/mol-theme/color/chain-id.ts

@@ -24,6 +24,9 @@ export const ChainIdColorThemeParams = {
 export type ChainIdColorThemeParams = typeof ChainIdColorThemeParams
 export function getChainIdColorThemeParams(ctx: ThemeDataContext) {
     const params = PD.clone(ChainIdColorThemeParams);
+    if (ctx.structure?.models.some(m => m.coarseHierarchy.isDefined)) {
+        params.asymId.defaultValue = 'label';
+    }
     return params;
 }
 

+ 17 - 6
src/mol-theme/color/element-symbol.ts

@@ -13,7 +13,8 @@ import { ParamDefinition as PD } from '../../mol-util/param-definition';
 import { ThemeDataContext } from '../theme';
 import { TableLegend } from '../../mol-util/legend';
 import { getAdjustedColorMap } from '../../mol-util/color/color';
-import { ChainIdColorTheme, getChainIdColorThemeParams } from './chain-id';
+import { ChainIdColorTheme, ChainIdColorThemeParams } from './chain-id';
+import { OperatorNameColorThemeParams, OperatorNameColorTheme } from './operator-name';
 
 // from Jmol http://jmol.sourceforge.net/jscolors/ (or 0xFFFFFF)
 export const ElementSymbolColors = ColorMap({
@@ -24,8 +25,14 @@ export type ElementSymbolColors = typeof ElementSymbolColors
 const DefaultElementSymbolColor = Color(0xFFFFFF);
 const Description = 'Assigns a color to every atom according to its chemical element.';
 
+// TODO generalise `carbonColor` param to all themes?
+
 export const ElementSymbolColorThemeParams = {
-    carbonByChainId: PD.Boolean(true),
+    carbonColor: PD.MappedStatic('chain-id', {
+        'chain-id': PD.Group({ ...ChainIdColorThemeParams }),
+        'operator-name': PD.Group({ ...OperatorNameColorThemeParams }),
+        'element-symbol': PD.Group({})
+    }, { description: 'Use chain-id coloring for carbon atoms.' }),
     saturation: PD.Numeric(0, { min: -6, max: 6, step: 0.1 }),
     lightness: PD.Numeric(0.2, { min: -6, max: 6, step: 0.1 })
 };
@@ -42,11 +49,15 @@ export function elementSymbolColor(colorMap: ElementSymbolColors, element: Eleme
 export function ElementSymbolColorTheme(ctx: ThemeDataContext, props: PD.Values<ElementSymbolColorThemeParams>): ColorTheme<ElementSymbolColorThemeParams> {
     const colorMap = getAdjustedColorMap(ElementSymbolColors, props.saturation, props.lightness);
 
-    const chainIdColor = ChainIdColorTheme(ctx, PD.getDefaultValues(getChainIdColorThemeParams(ctx))).color;
+    const carbonColor = props.carbonColor.name === 'chain-id'
+        ? ChainIdColorTheme(ctx, props.carbonColor.params).color
+        : props.carbonColor.name === 'operator-name'
+            ? OperatorNameColorTheme(ctx, props.carbonColor.params).color
+            : undefined;
 
     function elementColor(element: ElementSymbol, location: Location) {
-        return (props.carbonByChainId && element === 'C')
-            ? chainIdColor(location, false)
+        return (carbonColor && element === 'C')
+            ? carbonColor(location, false)
             : elementSymbolColor(colorMap, element);
     }
 
@@ -68,7 +79,7 @@ export function ElementSymbolColorTheme(ctx: ThemeDataContext, props: PD.Values<
 
     return {
         factory: ElementSymbolColorTheme,
-        granularity: 'group',
+        granularity: 'groupInstance',
         color,
         props,
         description: Description,

+ 5 - 3
src/mol-theme/color/illustrative.ts

@@ -11,19 +11,21 @@ import { Location } from '../../mol-model/location';
 import { ColorTheme } from '../color';
 import { ParamDefinition as PD } from '../../mol-util/param-definition';
 import { ThemeDataContext } from '../theme';
-import { ChainIdColorTheme, getChainIdColorThemeParams } from './chain-id';
+import { ChainIdColorTheme, ChainIdColorThemeParams } from './chain-id';
 
 const DefaultIllustrativeColor = Color(0xEEEEEE);
 const Description = `Assigns an illustrative color that gives every chain a unique color with lighter carbons (inspired by David Goodsell's Molecule of the Month style).`;
 
-export const IllustrativeColorThemeParams = {};
+export const IllustrativeColorThemeParams = {
+    ...ChainIdColorThemeParams
+};
 export type IllustrativeColorThemeParams = typeof IllustrativeColorThemeParams
 export function getIllustrativeColorThemeParams(ctx: ThemeDataContext) {
     return IllustrativeColorThemeParams; // TODO return copy
 }
 
 export function IllustrativeColorTheme(ctx: ThemeDataContext, props: PD.Values<IllustrativeColorThemeParams>): ColorTheme<IllustrativeColorThemeParams> {
-    const { color: chainIdColor, legend } = ChainIdColorTheme(ctx, PD.getDefaultValues(getChainIdColorThemeParams(ctx)));
+    const { color: chainIdColor, legend } = ChainIdColorTheme(ctx, props);
 
     function illustrativeColor(location: Location, typeSymbol: ElementSymbol) {
         const baseColor = chainIdColor(location, false);

+ 1 - 11
src/mol-theme/color/operator-name.ts

@@ -12,9 +12,8 @@ import { ParamDefinition as PD } from '../../mol-util/param-definition';
 import { ThemeDataContext } from '../theme';
 import { getPaletteParams, getPalette } from '../../mol-util/color/palette';
 import { ScaleLegend, TableLegend } from '../../mol-util/legend';
-import { ColorLists, getColorListFromName } from '../../mol-util/color/lists';
 
-const DefaultList = 'dark-2';
+const DefaultList = 'many-distinct';
 const DefaultColor = Color(0xCCCCCC);
 const Description = `Assigns a color based on the operator name of a transformed chain.`;
 
@@ -24,15 +23,6 @@ export const OperatorNameColorThemeParams = {
 export type OperatorNameColorThemeParams = typeof OperatorNameColorThemeParams
 export function getOperatorNameColorThemeParams(ctx: ThemeDataContext) {
     const params = PD.clone(OperatorNameColorThemeParams);
-    if (ctx.structure) {
-        if (getOperatorNameSerialMap(ctx.structure.root).size > ColorLists[DefaultList].list.length) {
-            params.palette.defaultValue.name = 'colors';
-            params.palette.defaultValue.params = {
-                ...params.palette.defaultValue.params,
-                list: { kind: 'interpolate', colors: getColorListFromName(DefaultList).list }
-            };
-        }
-    }
     return params;
 }
 

+ 16 - 5
src/mol-theme/color/sequence-id.ts

@@ -58,9 +58,14 @@ function getSequenceLength(unit: Unit, element: ElementIndex) {
             break;
     }
     if (entityId === '') return 0;
+
     const entityIndex = model.entities.getEntityIndex(entityId);
     if (entityIndex === -1) return 0;
-    return model.sequence.byEntityKey[entityIndex].sequence.length;
+
+    const entity = model.sequence.byEntityKey[entityIndex];
+    if (entity === undefined) return 0;
+
+    return entity.sequence.length;
 }
 
 export function SequenceIdColorTheme(ctx: ThemeDataContext, props: PD.Values<SequenceIdColorThemeParams>): ColorTheme<SequenceIdColorThemeParams> {
@@ -74,15 +79,21 @@ export function SequenceIdColorTheme(ctx: ThemeDataContext, props: PD.Values<Seq
             const { unit, element } = location;
             const seq_id = getSeqId(unit, element);
             if (seq_id > 0) {
-                scale.setDomain(0, getSequenceLength(unit, element) - 1);
-                return scale.color(seq_id);
+                const seqLen = getSequenceLength(unit, element);
+                if (seqLen) {
+                    scale.setDomain(0, seqLen - 1);
+                    return scale.color(seq_id);
+                }
             }
         } else if (Bond.isLocation(location)) {
             const { aUnit, aIndex } = location;
             const seq_id = getSeqId(aUnit, aUnit.elements[aIndex]);
             if (seq_id > 0) {
-                scale.setDomain(0, getSequenceLength(aUnit, aUnit.elements[aIndex]) - 1);
-                return scale.color(seq_id);
+                const seqLen = getSequenceLength(aUnit, aUnit.elements[aIndex]);
+                if (seqLen) {
+                    scale.setDomain(0, seqLen - 1);
+                    return scale.color(seq_id);
+                }
             }
         }
         return DefaultColor;

+ 1 - 1
src/servers/volume/server/web-api.ts

@@ -153,7 +153,7 @@ function getQueryParams(req: express.Request, isCell: boolean): Data.QueryParams
     const a = [+req.params.a1, +req.params.a2, +req.params.a3];
     const b = [+req.params.b1, +req.params.b2, +req.params.b3];
 
-    const detail = Math.min(Math.max(0, (+req.query.detail) | 0), LimitsConfig.maxOutputSizeInVoxelCountByPrecisionLevel.length - 1);
+    const detail = Math.min(Math.max(0, (+req.query.detail!) | 0), LimitsConfig.maxOutputSizeInVoxelCountByPrecisionLevel.length - 1);
     const isCartesian = (req.query.space as string || '').toLowerCase() !== 'fractional';
 
     const box: Data.QueryParamsBox = isCell

+ 2 - 1
webpack.config.js

@@ -1,6 +1,6 @@
 const { createApp, createExample, createBrowserTest } = require('./webpack.config.common.js');
 
-const examples = ['proteopedia-wrapper', 'basic-wrapper', 'lighting', 'docking-viewer'];
+const examples = ['proteopedia-wrapper', 'basic-wrapper', 'lighting'];
 const tests = [
     'font-atlas',
     'marching-cubes',
@@ -10,6 +10,7 @@ const tests = [
 
 module.exports = [
     createApp('viewer', 'molstar'),
+    createApp('docking-viewer', 'molstar'),
     ...examples.map(createExample),
     ...tests.map(createBrowserTest)
 ]

+ 1 - 0
webpack.config.production.js

@@ -4,5 +4,6 @@ const examples = ['proteopedia-wrapper', 'basic-wrapper', 'lighting'];
 
 module.exports = [
     createApp('viewer', 'molstar'),
+    createApp('docking-viewer', 'molstar'),
     ...examples.map(createExample)
 ]

Algúns arquivos non se mostraron porque demasiados arquivos cambiaron neste cambio