Ver código fonte

wip state builders

David Sehnal 5 anos atrás
pai
commit
80cf7c1dd2

+ 18 - 27
src/mol-plugin-state/actions/structure.ts

@@ -17,7 +17,7 @@ import { FileInfo } from '../../mol-util/file-info';
 import { Task } from '../../mol-task';
 import { StructureElement } from '../../mol-model/structure';
 import { createDefaultStructureComplex } from '../../mol-plugin/util/structure-complex-helper';
-import { ModelStructureRepresentation } from '../representation/model';
+import { RootStructureDefinition } from '../helpers/root-structure';
 
 export const MmcifProvider: DataFormatProvider<any> = {
     label: 'mmCIF',
@@ -121,7 +121,7 @@ type StructureFormat = 'pdb' | 'cif' | 'gro' | '3dg'
 //
 
 const DownloadModelRepresentationOptions = PD.Group({
-    type: ModelStructureRepresentation.getParams(void 0, 'assembly').type,
+    type: RootStructureDefinition.getParams(void 0, 'assembly').type,
     noRepresentation: PD.Optional(PD.Boolean(false, { description: 'Omit creating default representation.' }))
 }, { isExpanded: false });
 
@@ -141,27 +141,27 @@ const DownloadStructure = StateAction.build({
                 id: PD.Text('1cbs', { label: 'PDB Id(s)', description: 'One or more comma separated PDB ids.' }),
                 structure: DownloadModelRepresentationOptions,
                 options: DownloadStructurePdbIdSourceOptions
-            }, { isFlat: true }),
+            }, { isFlat: true, label: 'PDBe Updated' }),
             'rcsb': PD.Group({
                 id: PD.Text('1tqn', { label: 'PDB Id(s)', description: 'One or more comma separated PDB ids.' }),
                 structure: DownloadModelRepresentationOptions,
                 options: DownloadStructurePdbIdSourceOptions
-            }, { isFlat: true }),
+            }, { isFlat: true, label: 'RCSB' }),
             'pdb-dev': PD.Group({
                 id: PD.Text('PDBDEV_00000001', { label: 'PDBDev Id(s)', description: 'One or more comma separated ids.' }),
                 structure: DownloadModelRepresentationOptions,
                 options: DownloadStructurePdbIdSourceOptions
-            }, { isFlat: true }),
+            }, { isFlat: true, label: 'PDBDEV' }),
             'bcif-static': PD.Group({
                 id: PD.Text('1tqn', { label: 'PDB Id(s)', description: 'One or more comma separated PDB ids.' }),
                 structure: DownloadModelRepresentationOptions,
                 options: DownloadStructurePdbIdSourceOptions
-            }, { isFlat: true }),
+            }, { isFlat: true, label: 'BinaryCIF (static PDBe Updated)' }),
             'swissmodel': PD.Group({
                 id: PD.Text('Q9Y2I8', { label: 'UniProtKB AC(s)', description: 'One or more comma separated ACs.' }),
                 structure: DownloadModelRepresentationOptions,
                 options: DownloadStructurePdbIdSourceOptions
-            }, { isFlat: true, description: 'Loads the best homology model or experimental structure' }),
+            }, { isFlat: true, label: 'SWISS-MODEL', description: 'Loads the best homology model or experimental structure' }),
             'url': PD.Group({
                 url: PD.Text(''),
                 format: PD.Select('cif', [['cif', 'CIF'], ['pdb', 'PDB']] as ['cif' | 'pdb', string][]),
@@ -171,16 +171,7 @@ const DownloadStructure = StateAction.build({
                     supportProps: PD.Optional(PD.Boolean(false)),
                     createRepresentation: PD.Optional(PD.Boolean(true))
                 })
-            }, { isFlat: true })
-        }, {
-            options: [
-                ['pdbe-updated', 'PDBe Updated'],
-                ['rcsb', 'RCSB'],
-                ['pdb-dev', 'PDBDEV'],
-                ['bcif-static', 'BinaryCIF (static PDBe Updated)'],
-                ['swissmodel', 'SWISS-MODEL'],
-                ['url', 'URL']
-            ]
+            }, { isFlat: true, label: 'URL' })
         })
     }
 })(({ params, state }, plugin: PluginContext) => Task.create('Download Structure', async ctx => {
@@ -240,7 +231,7 @@ const DownloadStructure = StateAction.build({
             const struct = createStructure(state.build().to(traj), supportProps, src.params.structure.type);
             await state.updateTree(struct, { revertIfAborted: true }).runInContext(ctx);
             if (createRepr) {
-                await plugin.structureRepresentation.manager.apply(struct.ref, plugin.structureRepresentation.manager.defaultProvider);
+                await plugin.builders.structureRepresentation.apply(struct.ref, 'auto');
             }
         } else {
             for (const download of downloadParams) {
@@ -250,7 +241,7 @@ const DownloadStructure = StateAction.build({
                 const struct = createStructure(traj, supportProps, src.params.structure.type);
                 await state.updateTree(struct, { revertIfAborted: true }).runInContext(ctx);
                 if (createRepr) {
-                    await plugin.structureRepresentation.manager.apply(struct.ref, plugin.structureRepresentation.manager.defaultProvider);
+                    await plugin.builders.structureRepresentation.apply(struct.ref, 'auto');
                 }
             }
         }
@@ -306,7 +297,7 @@ export function createModelTree(b: StateBuilder.To<PluginStateObject.Data.Binary
     return parsed.apply(StateTransforms.Model.ModelFromTrajectory, { modelIndex: 0 });
 }
 
-function createStructure(b: StateBuilder.To<PluginStateObject.Molecule.Model>, supportProps: boolean, params?: ModelStructureRepresentation.Params) {
+function createStructure(b: StateBuilder.To<PluginStateObject.Molecule.Model>, supportProps: boolean, params?: RootStructureDefinition.Params) {
     let root = b;
     if (supportProps) {
         root = root.apply(StateTransforms.Model.CustomModelProperties);
@@ -314,7 +305,7 @@ function createStructure(b: StateBuilder.To<PluginStateObject.Molecule.Model>, s
     return root.apply(StateTransforms.Model.StructureFromModel, { type: params || { name: 'assembly', params: { } } });
 }
 
-function createStructureAndVisuals(ctx: PluginContext, b: StateBuilder.To<PluginStateObject.Molecule.Model>, supportProps: boolean, params?: ModelStructureRepresentation.Params) {
+function createStructureAndVisuals(ctx: PluginContext, b: StateBuilder.To<PluginStateObject.Molecule.Model>, supportProps: boolean, params?: RootStructureDefinition.Params) {
     const structure = createStructure(b, supportProps, params);
     createDefaultStructureComplex(ctx, structure);
     return b;
@@ -323,28 +314,28 @@ function createStructureAndVisuals(ctx: PluginContext, b: StateBuilder.To<Plugin
 export const Create3DRepresentationPreset = StateAction.build({
     display: { name: '3D Representation Preset', description: 'Create one of preset 3D representations.' },
     from: PluginStateObject.Molecule.Structure,
-    isApplicable(a, _, plugin: PluginContext) { return plugin.structureRepresentation.manager.hasProvider(a.data); },
+    isApplicable(a, _, plugin: PluginContext) { return plugin.builders.structureRepresentation.hasProvider(a.data); },
     params(a, plugin: PluginContext) {
         return {
-            type: plugin.structureRepresentation.manager.getOptions(a.data)
+            type: plugin.builders.structureRepresentation.getOptions(a.data)
         };
     }
 })(({ ref, params }, plugin: PluginContext) => {
-    plugin.structureRepresentation.manager.apply(ref, params.type.name, params.type.params);
+    plugin.builders.structureRepresentation.apply(ref, params.type.name, params.type.params);
 });
 
 export const Remove3DRepresentationPreset = StateAction.build({
     display: { name: 'Remove 3D Representation Preset', description: 'Remove 3D representations.' },
     from: PluginStateObject.Molecule.Structure,
-    isApplicable(_, t, plugin: PluginContext) { return plugin.structureRepresentation.manager.hasManagedRepresentation(t.ref); },
+    isApplicable(_, t, plugin: PluginContext) { return plugin.builders.structureRepresentation.hasManagedRepresentation(t.ref); },
     params(a, plugin: PluginContext) {
         return {
-            type: plugin.structureRepresentation.manager.getOptions(a.data).select
+            type: plugin.builders.structureRepresentation.getOptions(a.data).select
         };
     }
 })(({ ref, params }, plugin: PluginContext) => {
     // TODO: this will be completely handled by the managed and is just for testing purposes
-    plugin.structureRepresentation.manager.remove(params.type, ref);
+    plugin.builders.structureRepresentation.remove(params.type, ref);
 });
 
 export const UpdateTrajectory = StateAction.build({

+ 1 - 0
src/mol-plugin-state/builder/parser.ts

@@ -0,0 +1 @@
+// TODO: parsing

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

@@ -0,0 +1 @@
+// TODO: create managed trajectory / model / structure

+ 35 - 26
src/mol-plugin-state/representation/structure.ts → src/mol-plugin-state/builder/structure-representation.ts

@@ -1,12 +1,12 @@
 /**
- * Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
  * @author David Sehnal <david.sehnal@gmail.com>
  */
 
 import { arrayFind } from '../../mol-data/util';
 import { Structure } from '../../mol-model/structure';
-import { StateTransform, StateTree, StateSelection } from '../../mol-state';
+import { StateTransform, StateTree, StateSelection, StateObjectRef } from '../../mol-state';
 import { Task } from '../../mol-task';
 import { isProductionMode } from '../../mol-util/debug';
 import { objectForEach } from '../../mol-util/object';
@@ -19,12 +19,20 @@ import { UniqueArray } from '../../mol-data/generic';
 // TODO: support quality
 // TODO: support ignore hydrogens
 
-export class StructureRepresentationManager {
+export type StructureRepresentationProviderRef = keyof PresetStructureReprentations | StructureRepresentationProvider | string
+
+export class StructureRepresentationBuilder {
     private providers: StructureRepresentationProvider[] = [];
     private providerMap: Map<string, StructureRepresentationProvider> = new Map();
 
     readonly defaultProvider = PresetStructureReprentations.auto;
 
+    private resolveProvider(ref: StructureRepresentationProviderRef) {
+        return typeof ref === 'string'
+            ? PresetStructureReprentations[ref as keyof PresetStructureReprentations] ?? arrayFind(this.providers, p => p.id === ref)
+            : ref;
+    }
+
     hasProvider(s: Structure) {
         for (const p of this.providers) {
             if (!p.isApplicable || p.isApplicable(s, this.plugin)) return true;
@@ -45,10 +53,12 @@ export class StructureRepresentationManager {
         return PD.MappedStatic(options[0][0], map, { options });
     }
 
-    hasManagedRepresentation(ref: StateTransform.Ref) {
-        const tree = this.plugin.state.dataState.tree;
+    hasManagedRepresentation(ref: StateObjectRef) {
         // TODO: make this state selection function?
-        return StateTree.doPreOrder(tree, tree.transforms.get(ref), { found: false, map: this.providerMap }, (n, _, s) => {
+        const tree = this.plugin.state.dataState.tree;
+        const root = StateObjectRef.resolve(this.plugin.state.dataState, ref);
+        if (!root) return false;
+        return StateTree.doPreOrder(tree, root.transform,  { found: false, map: this.providerMap }, (n, _, s) => {
             if (!n.tags) return;
             for (const t of n.tags) {
                 if (s.map.has(t)) {
@@ -59,10 +69,11 @@ export class StructureRepresentationManager {
         }).found;
     }
 
-    getManagedRepresentations(ref: StateTransform.Ref) {
-        // TODO: check if Structure etc.
+    getManagedRepresentations(ref: StateObjectRef) {
         const tree = this.plugin.state.dataState.tree;
-        return StateTree.doPreOrder(tree, tree.transforms.get(ref), { found: UniqueArray.create<string, StructureRepresentationProvider>(), map: this.providerMap }, (n, _, s) => {
+        const root = StateObjectRef.resolve(this.plugin.state.dataState, ref);
+        if (!root) return [];
+        return StateTree.doPreOrder(tree, root.transform, { found: UniqueArray.create<string, StructureRepresentationProvider>(), map: this.providerMap }, (n, _, s) => {
             if (!n.tags) return;
             for (const t of n.tags) {
                 if (s.map.has(t)) UniqueArray.add(s.found, t, s.map.get(t)!);
@@ -79,11 +90,12 @@ export class StructureRepresentationManager {
         this.providerMap.set(provider.id, provider);
     }
 
-    remove(providerOrId: StructureRepresentationProvider | string, structureRoot?: StateTransform.Ref) {
-        const root = structureRoot || StateTransform.RootRef;
-        const id = typeof providerOrId === 'string' ? providerOrId : providerOrId.id;
-
+    remove(providerRef: StructureRepresentationProviderRef, structureRoot?: StateObjectRef) {
+        const id = this.resolveProvider(providerRef)?.id;
+        if (!id) return;
+        
         const state = this.plugin.state.dataState;
+        const root = StateObjectRef.resolveRef(state, structureRoot) || StateTransform.RootRef;
         const reprs = StateSelection.findWithAllTags(state.tree, root, new Set([id, RepresentationProviderTags.Representation]));
 
         const builder = state.build();
@@ -101,33 +113,30 @@ export class StructureRepresentationManager {
         if (builder.editInfo.count === 0) return;
         return this.plugin.runTask(state.updateTree(builder));
     }
-
-    apply<P = any, S = {}>(ref: StateTransform.Ref, providerOrId: StructureRepresentationProvider<P, S> | string, params?: P) {
-        const provider = typeof providerOrId === 'string'
-            ? arrayFind(this.providers, p => p.id === providerOrId)
-            : providerOrId;
+    
+    apply<K extends keyof PresetStructureReprentations>(parent: StateObjectRef, preset: K, params?: StructureRepresentationProvider.Params<PresetStructureReprentations[K]>): Promise<StructureRepresentationProvider.State<PresetStructureReprentations[K]>> | undefined
+    apply<P = any, S = {}>(parent: StateObjectRef, providers: StructureRepresentationProvider<P, S>, params?: P): Promise<S> | undefined
+    apply(parent: StateObjectRef, providerId: string, params?: any): Promise<any> | undefined
+    apply(parent: StateObjectRef, providerRef: string | StructureRepresentationProvider, params?: any): Promise<any> | undefined {
+        const provider = this.resolveProvider(providerRef);
         if (!provider) return;
 
         const state = this.plugin.state.dataState;
-        const cell = state.cells.get(ref);
-        if (!cell || !cell.obj || cell.status !== 'ok') {
+        const cell = StateObjectRef.resolveAndCheck(state, parent);
+        if (!cell) {
             if (!isProductionMode) console.warn(`Applying structure repr. provider to bad cell.`);
             return;
         }
 
         const prms = params || (provider.params
-            ? PD.getDefaultValues(provider.params(cell.obj.data, this.plugin))
+            ? PD.getDefaultValues(provider.params(cell.obj!.data, this.plugin))
             : {})
 
 
-        const task = Task.create<S>(`${provider.display.name}`, ctx => provider.apply(ctx, state, cell, prms, this.plugin) as Promise<S>);
+        const task = Task.create(`${provider.display.name}`, ctx => provider.apply(ctx, state, cell, prms, this.plugin) as Promise<any>);
         return this.plugin.runTask(task);
     }
 
-    // init() {
-    //     objectForEach(PresetStructureReprentations, r => this.register(r));
-    // }
-
     constructor(public plugin: PluginContext) {
         objectForEach(PresetStructureReprentations, r => this.register(r));
     }

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

@@ -0,0 +1 @@
+// TODO: built in structure components

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

@@ -71,7 +71,7 @@ const defaultPreset = StructureRepresentationProvider({
             .applyOrUpdateTagged(reprTags, StateTransforms.Representation.StructureRepresentation3D,
                 StructureRepresentation3DHelpers.getDefaultParamsWithTheme(plugin, 'spacefill', 'polymer-id', structure, {}));
 
-        await state.updateTree(root, { revertIfAborted: true }).runInContext(ctx);
+        await state.updateTree(root, { revertOnError: true }).runInContext(ctx);
 
         return {
             ligand: {
@@ -98,7 +98,7 @@ const proteinAndNucleic = StructureRepresentationProvider({
             .applyOrUpdateTagged(reprTags, StateTransforms.Representation.StructureRepresentation3D,
                 StructureRepresentation3DHelpers.getDefaultParams(plugin, 'gaussian-surface', structure));
 
-        await state.updateTree(root, { revertIfAborted: true }).runInContext(ctx);
+        await state.updateTree(root, { revertOnError: true }).runInContext(ctx);
         return {};
     }
 });
@@ -119,7 +119,7 @@ const capsid = StructureRepresentationProvider({
         applySelection(root, 'polymer')
             .applyOrUpdateTagged(reprTags, StateTransforms.Representation.StructureRepresentation3D, params);
 
-        await state.updateTree(root, { revertIfAborted: true }).runInContext(ctx);
+        await state.updateTree(root, { revertOnError: true }).runInContext(ctx);
         return {};
     }
 });
@@ -143,7 +143,7 @@ const coarseCapsid = StructureRepresentationProvider({
         applySelection(root, 'trace')
             .applyOrUpdateTagged(reprTags, StateTransforms.Representation.StructureRepresentation3D, params);
 
-        await state.updateTree(root, { revertIfAborted: true }).runInContext(ctx);
+        await state.updateTree(root, { revertOnError: true }).runInContext(ctx);
         return {};
     }
 });
@@ -164,7 +164,7 @@ const cartoon = StructureRepresentationProvider({
         applySelection(root, 'polymer')
             .applyOrUpdateTagged(reprTags, StateTransforms.Representation.StructureRepresentation3D, params);
 
-        await state.updateTree(root, { revertIfAborted: true }).runInContext(ctx);
+        await state.updateTree(root, { revertOnError: true }).runInContext(ctx);
         return {};
     }
 });
@@ -186,4 +186,5 @@ export const PresetStructureReprentations = {
     capsid,
     coarseCapsid,
     cartoon
-};
+};
+export type PresetStructureReprentations = typeof PresetStructureReprentations;

+ 5 - 0
src/mol-plugin-state/representation/structure/provider.ts → src/mol-plugin-state/builder/structure/provider.ts

@@ -21,6 +21,11 @@ export interface StructureRepresentationProvider<P = any, S = {}> {
     // remove?(state: State, ref: string, plugin: PluginContext): void
 }
 
+export namespace StructureRepresentationProvider {
+    export type Params<P extends StructureRepresentationProvider> = P extends StructureRepresentationProvider<infer T> ? T : never;
+    export type State<P extends StructureRepresentationProvider> = P extends StructureRepresentationProvider<infer _, infer S> ? S : never;
+}
+
 export const enum RepresentationProviderTags {
     Representation = 'preset-structure-representation',
     Selection = 'preset-structure-selection'

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


+ 1 - 1
src/mol-plugin-state/representation/model.ts → src/mol-plugin-state/helpers/root-structure.ts

@@ -16,7 +16,7 @@ import { Assembly, Symmetry } from '../../mol-model/structure/model/properties/s
 import { PluginStateObject as SO } from '../objects';
 import { ModelSymmetry } from '../../mol-model-formats/structure/property/symmetry';
 
-export namespace ModelStructureRepresentation {
+export namespace RootStructureDefinition {
     export function getParams(model?: Model, defaultValue?: 'deposited' | 'assembly' | 'symmetry' | 'symmetry-mates' | 'symmetry-assembly') {
         const symmetry = model && ModelSymmetry.Provider.get(model)
 

+ 3 - 3
src/mol-plugin-state/transforms/model.ts

@@ -26,7 +26,7 @@ import { parse3DG } from '../../mol-io/reader/3dg/parser';
 import { trajectoryFrom3DG } from '../../mol-model-formats/structure/3dg';
 import { StructureSelectionQueries } from '../../mol-plugin/util/structure-selection-helper';
 import { StructureQueryHelper } from '../../mol-plugin/util/structure-query';
-import { ModelStructureRepresentation } from '../representation/model';
+import { RootStructureDefinition } from '../helpers/root-structure';
 import { parseDcd } from '../../mol-io/reader/dcd/parser';
 import { coordinatesFromDcd } from '../../mol-model-formats/structure/dcd';
 import { topologyFromPsf } from '../../mol-model-formats/structure/psf';
@@ -273,11 +273,11 @@ const StructureFromModel = PluginStateTransform.BuiltIn({
     display: { name: 'Structure', description: 'Create a molecular structure (deposited, assembly, or symmetry) from the specified model.' },
     from: SO.Molecule.Model,
     to: SO.Molecule.Structure,
-    params(a) { return ModelStructureRepresentation.getParams(a && a.data); }
+    params(a) { return RootStructureDefinition.getParams(a && a.data); }
 })({
     apply({ a, params }, plugin: PluginContext) {
         return Task.create('Build Structure', async ctx => {
-            return ModelStructureRepresentation.create(plugin, ctx, a.data, params && params.type);
+            return RootStructureDefinition.create(plugin, ctx, a.data, params && params.type);
         })
     },
     update: ({ a, b, oldParams, newParams }) => {

+ 4 - 4
src/mol-plugin/context.ts

@@ -43,7 +43,7 @@ import { StructureOverpaintHelper } from './util/structure-overpaint-helper';
 import { PluginToastManager } from './util/toast';
 import { StructureMeasurementManager } from './util/structure-measurement';
 import { ViewportScreenshotHelper } from './util/viewport-screenshot';
-import { StructureRepresentationManager } from '../mol-plugin-state/representation/structure';
+import { StructureRepresentationBuilder } from '../mol-plugin-state/builder/structure-representation';
 import { CustomProperty } from '../mol-model-props/common/custom-property';
 import { PluginConfigManager } from './config';
 import { DataBuilder } from '../mol-plugin-state/builder/data';
@@ -113,7 +113,6 @@ export class PluginContext {
     readonly structureRepresentation = {
         registry: new StructureRepresentationRegistry(),
         themeCtx: { colorThemeRegistry: ColorTheme.createRegistry(), sizeThemeRegistry: SizeTheme.createRegistry() } as ThemeRegistryContext,
-        manager: void 0 as any as StructureRepresentationManager
     } as const
 
     readonly volumeRepresentation = {
@@ -126,7 +125,8 @@ export class PluginContext {
     } as const
 
     readonly builders = {
-        data: new DataBuilder(this)
+        data: new DataBuilder(this),
+        structureRepresentation: void 0 as any as StructureRepresentationBuilder
     };
 
     readonly customModelProperties = new CustomProperty.Registry<Model>();
@@ -282,7 +282,7 @@ export class PluginContext {
         this.interactivity = new Interactivity(this);
         this.lociLabels = new LociLabelManager(this);
 
-        (this.structureRepresentation.manager as StructureRepresentationManager)= new StructureRepresentationManager(this);
+        (this.builders.structureRepresentation as StructureRepresentationBuilder)= new StructureRepresentationBuilder(this);
 
         this.log.message(`Mol* Plugin ${PLUGIN_VERSION} [${PLUGIN_VERSION_DATE.toLocaleString()}]`);
         if (!isProductionMode) this.log.message(`Development mode enabled`);

+ 24 - 0
src/mol-state/object.ts

@@ -169,4 +169,28 @@ export class StateObjectSelector<S extends StateObject = StateObject, T extends
 export namespace StateObjectSelector {
     export type Obj<S extends StateObjectSelector> = S extends StateObjectSelector<infer A> ? A : never
     export type Transformer<S extends StateObjectSelector> = S extends StateObjectSelector<any, infer T> ? T : never
+}
+
+export type StateObjectRef<S extends StateObject = StateObject> = StateObjectSelector<S> | StateObjectCell<S> | StateTransform.Ref
+
+export namespace StateObjectRef {
+    export function resolveRef<S extends StateObject>(state: State, ref?: StateObjectRef<S>): StateTransform.Ref | undefined {
+        if (!ref) return;
+        if (typeof ref === 'string') return ref;
+        if (StateObjectCell.is(ref)) return ref.transform.ref;
+        return ref.cell?.transform.ref;
+    }
+
+    export function resolve<S extends StateObject>(state: State, ref?: StateObjectRef<S>): StateObjectCell<S> | undefined {
+        if (!ref) return;
+        if (StateObjectCell.is(ref)) return ref;
+        if (typeof ref === 'string') return state.cells.get(ref) as StateObjectCell<S> | undefined;
+        return ref.cell;
+    }
+
+    export function resolveAndCheck<S extends StateObject>(state: State, ref?: StateObjectRef<S>): StateObjectCell<S> | undefined {
+        const cell = resolve(state, ref);
+        if (!cell || !cell.obj || cell.status !== 'ok') return;
+        return cell;
+    }
 }

+ 1 - 1
src/mol-util/param-definition.ts

@@ -221,7 +221,7 @@ export namespace ParamDefinition {
     export function MappedStatic<C extends Params>(defaultKey: keyof C, map: C, info?: Info & { options?: [keyof C, string][] }): Mapped<NamedParamUnion<C>> {
         const options: [string, string][] = info && info.options
             ? info.options as [string, string][]
-            : Object.keys(map).map(k => [k, stringToWords(k)]) as [string, string][];
+            : Object.keys(map).map(k => [k, map[k].label || stringToWords(k)]) as [string, string][];
         const name = checkDefaultKey(defaultKey, options);
         return setInfo<Mapped<NamedParamUnion<C>>>({
             type: 'mapped',