Alexander Rose 6 роки тому
батько
коміт
e01f1c459f

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

@@ -29,7 +29,7 @@ import { PLUGIN_VERSION, PLUGIN_VERSION_DATE } from './version';
 import { PluginLayout } from './layout';
 import { List } from 'immutable';
 import { StateTransformParameters } from './ui/state/common';
-import { DataFormatRegistry } from './state/actions/volume';
+import { DataFormatRegistry } from './state/actions/data-format';
 import { PluginBehavior } from './behavior/behavior';
 import { CustomPropertyRegistry } from 'mol-model-props/common/custom-property-registry';
 

+ 2 - 2
src/mol-plugin/index.ts

@@ -25,8 +25,7 @@ export const DefaultPluginSpec: PluginSpec = {
     actions: [
         PluginSpec.Action(StateActions.Structure.DownloadStructure),
         PluginSpec.Action(StateActions.Volume.DownloadDensity),
-        PluginSpec.Action(StateActions.Structure.OpenStructure),
-        PluginSpec.Action(StateActions.Volume.OpenVolume),
+        PluginSpec.Action(StateActions.DataFormat.OpenFile),
         PluginSpec.Action(StateActions.Structure.CreateComplexRepresentation),
         PluginSpec.Action(StateActions.Structure.EnableModelCustomProps),
 
@@ -35,6 +34,7 @@ export const DefaultPluginSpec: PluginSpec = {
         PluginSpec.Action(StateTransforms.Data.Download),
         PluginSpec.Action(StateTransforms.Data.ParseCif),
         PluginSpec.Action(StateTransforms.Data.ParseCcp4),
+        PluginSpec.Action(StateTransforms.Data.ParseDsn6),
         PluginSpec.Action(StateTransforms.Model.StructureAssemblyFromModel),
         PluginSpec.Action(StateTransforms.Model.StructureSymmetryFromModel),
         PluginSpec.Action(StateTransforms.Model.StructureFromModel),

+ 3 - 1
src/mol-plugin/state/actions.ts

@@ -6,8 +6,10 @@
 
 import * as Structure from './actions/structure'
 import * as Volume from './actions/volume'
+import * as DataFormat from './actions/data-format'
 
 export const StateActions = {
     Structure,
-    Volume
+    Volume,
+    DataFormat
 }

+ 141 - 0
src/mol-plugin/state/actions/data-format.ts

@@ -0,0 +1,141 @@
+/**
+ * Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
+ */
+
+import { PluginContext } from 'mol-plugin/context';
+import { State, StateBuilder, StateAction } from 'mol-state';
+import { Task } from 'mol-task';
+import { FileInfo, getFileInfo } from 'mol-util/file-info';
+import { PluginStateObject } from '../objects';
+import { ParamDefinition as PD } from 'mol-util/param-definition';
+import { Ccp4Provider, Dsn6Provider, DscifProvider } from './volume';
+import { StateTransforms } from '../transforms';
+import { MmcifProvider, PdbProvider } from './structure';
+
+export class DataFormatRegistry<D extends PluginStateObject.Data.Binary | PluginStateObject.Data.String> {
+    private _list: { name: string, provider: DataFormatProvider<D> }[] = []
+    private _map = new Map<string, DataFormatProvider<D>>()
+    private _extensions: Set<string> | undefined = undefined
+    private _binaryExtensions: Set<string> | undefined = undefined
+    private _options: [string, string][] | undefined = undefined
+
+    get types(): [string, string][] {
+        return this._list.map(e => [e.name, e.provider.label] as [string, string]);
+    }
+
+    get extensions() {
+        if (this._extensions) return this._extensions
+        const extensions = new Set<string>()
+        this._list.forEach(({ provider }) => {
+            provider.stringExtensions.forEach(ext => extensions.add(ext))
+            provider.binaryExtensions.forEach(ext => extensions.add(ext))
+        })
+        this._extensions = extensions
+        return extensions
+    }
+
+    get binaryExtensions() {
+        if (this._binaryExtensions) return this._binaryExtensions
+        const binaryExtensions = new Set<string>()
+        this._list.forEach(({ provider }) => provider.binaryExtensions.forEach(ext => binaryExtensions.add(ext)))
+        this._binaryExtensions = binaryExtensions
+        return binaryExtensions
+    }
+
+    get options() {
+        if (this._options) return this._options
+        const options: [string, string][] = [['auto', 'Automatic']]
+        this._list.forEach(({ name, provider }) => options.push([ name, provider.label ]))
+        this._options = options
+        return options
+    }
+
+    constructor() {
+        this.add('ccp4', Ccp4Provider)
+        this.add('dscif', DscifProvider)
+        this.add('dsn6', Dsn6Provider)
+        this.add('mmcif', MmcifProvider)
+        this.add('pdb', PdbProvider)
+    };
+
+    private _clear() {
+        this._extensions = undefined
+        this._binaryExtensions = undefined
+        this._options = undefined
+    }
+
+    add(name: string, provider: DataFormatProvider<D>) {
+        this._clear()
+        this._list.push({ name, provider })
+        this._map.set(name, provider)
+    }
+
+    remove(name: string) {
+        this._clear()
+        this._list.splice(this._list.findIndex(e => e.name === name), 1)
+        this._map.delete(name)
+    }
+
+    auto(info: FileInfo, dataStateObject: D) {
+        for (let i = 0, il = this.list.length; i < il; ++i) {
+            const { provider } = this._list[i]
+            if (provider.isApplicable(info, dataStateObject.data)) return provider
+        }
+        throw new Error('no compatible data format provider available')
+    }
+
+    get(name: string): DataFormatProvider<D> {
+        if (this._map.has(name)) {
+            return this._map.get(name)!
+        } else {
+            throw new Error(`unknown data format name '${name}'`)
+        }
+    }
+
+    get list() {
+        return this._list
+    }
+}
+
+export interface DataFormatProvider<D extends PluginStateObject.Data.Binary | PluginStateObject.Data.String> {
+    label: string
+    description: string
+    stringExtensions: string[]
+    binaryExtensions: string[]
+    isApplicable(info: FileInfo, data: string | Uint8Array): boolean
+    getDefaultBuilder(ctx: PluginContext, data: StateBuilder.To<D>, state?: State): Task<void>
+}
+
+//
+
+export const OpenFile = StateAction.build({
+    display: { name: 'Open File', description: 'Load a file and create its default visual' },
+    from: PluginStateObject.Root,
+    params: (a, ctx: PluginContext) => {
+        const { extensions, options } = ctx.dataFormat.registry
+        return {
+            file: PD.File({ accept: Array.from(extensions).map(e => `.${e}`).join(',')}),
+            format: PD.Select('auto', options),
+        }
+    }
+})(({ params, state }, ctx: PluginContext) => Task.create('Open File', async taskCtx => {
+    const info = getFileInfo(params.file)
+    const data = state.build().toRoot().apply(StateTransforms.Data.ReadFile, { file: params.file, isBinary: ctx.dataFormat.registry.binaryExtensions.has(info.ext) });
+    const dataStateObject = await state.updateTree(data).runInContext(taskCtx);
+
+    // Alternative for more complex states where the builder is not a simple StateBuilder.To<>:
+    /*
+    const dataRef = dataTree.ref;
+    await state.updateTree(dataTree).runInContext(taskCtx);
+    const dataCell = state.select(dataRef)[0];
+    */
+
+    // const data = b.toRoot().apply(StateTransforms.Data.ReadFile, { file: params.file, isBinary: /\.bcif$/i.test(params.file.name) });
+
+    const provider = params.format === 'auto' ? ctx.dataFormat.registry.auto(info, dataStateObject) : ctx.dataFormat.registry.get(params.format)
+    const b = state.build().to(data.ref);
+    // need to await the 2nd update the so that the enclosing Task finishes after the update is done.
+    await provider.getDefaultBuilder(ctx, b, state).runInContext(taskCtx)
+}));

+ 37 - 13
src/mol-plugin/state/actions/structure.ts

@@ -6,15 +6,50 @@
  */
 
 import { PluginContext } from 'mol-plugin/context';
-import { StateAction, StateBuilder, StateSelection, StateTransformer } from 'mol-state';
+import { StateAction, StateBuilder, StateSelection, StateTransformer, State } from 'mol-state';
 import { ParamDefinition as PD } from 'mol-util/param-definition';
 import { PluginStateObject } from '../objects';
 import { StateTransforms } from '../transforms';
 import { Download } from '../transforms/data';
 import { StructureRepresentation3DHelpers } from '../transforms/representation';
 import { CustomModelProperties } from '../transforms/model';
+import { DataFormatProvider } from './data-format';
+import { FileInfo } from 'mol-util/file-info';
+import { Task } from 'mol-task';
+
+export const MmcifProvider: DataFormatProvider<any> = {
+    label: 'mmCIF',
+    description: 'mmCIF',
+    stringExtensions: ['cif', 'mmcif', 'mcif'],
+    binaryExtensions: ['bcif'],
+    isApplicable: (info: FileInfo, data: Uint8Array) => {
+        return info.ext === 'cif' || info.ext === 'mmcif' || info.ext === 'mcif' || info.ext === 'bcif'
+    },
+    getDefaultBuilder: (ctx: PluginContext, data: StateBuilder.To<PluginStateObject.Data.Binary | PluginStateObject.Data.String>, state: State) => {
+        return Task.create('mmCIF default builder', async taskCtx => {
+            const traj = createModelTree(data, 'cif');
+            await state.updateTree(createStructureTree(ctx, traj, false)).runInContext(taskCtx)
+        })
+    }
+}
+
+export const PdbProvider: DataFormatProvider<any> = {
+    label: 'PDB',
+    description: 'PDB',
+    stringExtensions: ['pdb', 'ent'],
+    binaryExtensions: [],
+    isApplicable: (info: FileInfo, data: Uint8Array) => {
+        return info.ext === 'pdb' || info.ext === 'ent'
+    },
+    getDefaultBuilder: (ctx: PluginContext, data: StateBuilder.To<PluginStateObject.Data.String>, state: State) => {
+        return Task.create('mmCIF default builder', async taskCtx => {
+            const traj = createModelTree(data, 'pdb');
+            await state.updateTree(createStructureTree(ctx, traj, false)).runInContext(taskCtx)
+        })
+    }
+}
 
-// TODO: "structure/volume parser provider"
+//
 
 export { DownloadStructure };
 type DownloadStructure = typeof DownloadStructure
@@ -76,17 +111,6 @@ const DownloadStructure = StateAction.build({
     return state.updateTree(createStructureTree(ctx, traj, params.source.params.supportProps));
 });
 
-export const OpenStructure = StateAction.build({
-    display: { name: 'Open Structure', description: 'Load a structure from file and create its default Assembly and visual' },
-    from: PluginStateObject.Root,
-    params: { file: PD.File({ accept: '.cif,.bcif' }) }
-})(({ params, state }, ctx: PluginContext) => {
-    const b = state.build();
-    const data = b.toRoot().apply(StateTransforms.Data.ReadFile, { file: params.file, isBinary: /\.bcif$/i.test(params.file.name) });
-    const traj = createModelTree(data, 'cif');
-    return state.updateTree(createStructureTree(ctx, traj, false));
-});
-
 function createModelTree(b: StateBuilder.To<PluginStateObject.Data.Binary | PluginStateObject.Data.String>, format: 'pdb' | 'cif' = 'cif') {
     const parsed = format === 'cif'
         ? b.apply(StateTransforms.Data.ParseCif, void 0, { props: { isGhost: true }}).apply(StateTransforms.Model.TrajectoryFromMmCif, void 0, { props: { isGhost: true }})

+ 13 - 103
src/mol-plugin/state/actions/volume.ts

@@ -7,7 +7,7 @@
 
 import { VolumeIsoValue } from 'mol-model/volume';
 import { PluginContext } from 'mol-plugin/context';
-import { State, StateAction, StateBuilder, StateObject, StateTransformer } from 'mol-state';
+import { State, StateAction, StateBuilder, StateTransformer } from 'mol-state';
 import { Task } from 'mol-task';
 import { ColorNames } from 'mol-util/color/tables';
 import { FileInfo, getFileInfo } from 'mol-util/file-info';
@@ -17,65 +17,13 @@ import { StateTransforms } from '../transforms';
 import { Download } from '../transforms/data';
 import { VolumeRepresentation3DHelpers } from '../transforms/representation';
 import { VolumeStreaming } from 'mol-plugin/behavior/dynamic/volume';
+import { DataFormatProvider } from './data-format';
 
-export class DataFormatRegistry<D extends PluginStateObject.Data.Binary | PluginStateObject.Data.String, M extends StateObject> {
-    private _list: { name: string, provider: DataFormatProvider<D> }[] = []
-    private _map = new Map<string, DataFormatProvider<D>>()
-
-    get default() { return this._list[0]; }
-    get types(): [string, string][] {
-        return this._list.map(e => [e.name, e.provider.label] as [string, string]);
-    }
-
-    constructor() {
-        this.add('ccp4', Ccp4Provider)
-        this.add('dsn6', Dsn6Provider)
-        this.add('dscif', DscifProvider)
-    };
-
-    add(name: string, provider: DataFormatProvider<D>) {
-        this._list.push({ name, provider })
-        this._map.set(name, provider)
-    }
-
-    remove(name: string) {
-        this._list.splice(this._list.findIndex(e => e.name === name), 1)
-        this._map.delete(name)
-    }
-
-    auto(info: FileInfo, dataStateObject: D) {
-        for (let i = 0, il = this.list.length; i < il; ++i) {
-            const { provider } = this._list[i]
-            if (provider.isApplicable(info, dataStateObject.data)) return provider
-        }
-        throw new Error('no compatible data format provider available')
-    }
-
-    get(name: string): DataFormatProvider<D> {
-        if (this._map.has(name)) {
-            return this._map.get(name)!
-        } else {
-            throw new Error(`unknown data format name '${name}'`)
-        }
-    }
-
-    get list() {
-        return this._list
-    }
-}
-
-interface DataFormatProvider<D extends PluginStateObject.Data.Binary | PluginStateObject.Data.String> {
-    label: string
-    description: string
-    fileExtensions: string[]
-    isApplicable(info: FileInfo, data: string | Uint8Array): boolean
-    getDefaultBuilder(ctx: PluginContext, data: StateBuilder.To<D>, state?: State): Task<void>
-}
-
-const Ccp4Provider: DataFormatProvider<any> = {
+export const Ccp4Provider: DataFormatProvider<any> = {
     label: 'CCP4/MRC/BRIX',
     description: 'CCP4/MRC/BRIX',
-    fileExtensions: ['ccp4', 'mrc', 'map'],
+    stringExtensions: [],
+    binaryExtensions: ['ccp4', 'mrc', 'map'],
     isApplicable: (info: FileInfo, data: Uint8Array) => {
         return info.ext === 'ccp4' || info.ext === 'mrc' || info.ext === 'map'
     },
@@ -89,10 +37,11 @@ const Ccp4Provider: DataFormatProvider<any> = {
     }
 }
 
-const Dsn6Provider: DataFormatProvider<any> = {
+export const Dsn6Provider: DataFormatProvider<any> = {
     label: 'DSN6/BRIX',
     description: 'DSN6/BRIX',
-    fileExtensions: ['dsn6', 'brix'],
+    stringExtensions: [],
+    binaryExtensions: ['dsn6', 'brix'],
     isApplicable: (info: FileInfo, data: Uint8Array) => {
         return info.ext === 'dsn6' || info.ext === 'brix'
     },
@@ -106,12 +55,13 @@ const Dsn6Provider: DataFormatProvider<any> = {
     }
 }
 
-const DscifProvider: DataFormatProvider<any> = {
+export const DscifProvider: DataFormatProvider<any> = {
     label: 'DensityServer CIF',
     description: 'DensityServer CIF',
-    fileExtensions: ['cif'],
+    stringExtensions: ['cif'],
+    binaryExtensions: ['bcif'],
     isApplicable: (info: FileInfo, data: Uint8Array) => {
-        return info.ext === 'cif'
+        return info.ext === 'cif' || info.ext === 'bcif'
     },
     getDefaultBuilder: (ctx: PluginContext, data: StateBuilder.To<PluginStateObject.Data.Binary>, state: State) => {
         return Task.create('DensityServer CIF default builder', async taskCtx => {
@@ -143,53 +93,13 @@ const DscifProvider: DataFormatProvider<any> = {
     }
 }
 
-//
-
-function getDataFormatExtensionsOptions(dataFormatRegistry: DataFormatRegistry<any, any>) {
-    const extensions: string[] = []
-    const options: [string, string][] = [['auto', 'Automatic']]
-    dataFormatRegistry.list.forEach(({ name, provider }) => {
-        extensions.push(...provider.fileExtensions)
-        options.push([ name, provider.label ])
-    })
-    return { extensions, options }
-}
-
-export const OpenVolume = StateAction.build({
-    display: { name: 'Open Volume', description: 'Load a volume from file and create its default visual' },
-    from: PluginStateObject.Root,
-    params: (a, ctx: PluginContext) => {
-        const { extensions, options } = getDataFormatExtensionsOptions(ctx.dataFormat.registry)
-        return {
-            file: PD.File({ accept: extensions.map(e => `.${e}`).join(',')}),
-            format: PD.Select('auto', options),
-            isBinary: PD.Boolean(true), // TOOD should take selected format into account
-        }
-    }
-})(({ params, state }, ctx: PluginContext) => Task.create('Open Volume', async taskCtx => {
-    const data = state.build().toRoot().apply(StateTransforms.Data.ReadFile, { file: params.file, isBinary: params.isBinary });
-    const dataStateObject = await state.updateTree(data).runInContext(taskCtx);
-
-    // Alternative for more complex states where the builder is not a simple StateBuilder.To<>:
-    /*
-    const dataRef = dataTree.ref;
-    await state.updateTree(dataTree).runInContext(taskCtx);
-    const dataCell = state.select(dataRef)[0];
-    */
-
-    const provider = params.format === 'auto' ? ctx.dataFormat.registry.auto(getFileInfo(params.file), dataStateObject) : ctx.dataFormat.registry.get(params.format)
-    const b = state.build().to(data.ref);
-    // need to await the 2nd update the so that the enclosing Task finishes after the update is done.
-    await provider.getDefaultBuilder(ctx, b, state).runInContext(taskCtx)
-}));
-
 export { DownloadDensity };
 type DownloadDensity = typeof DownloadDensity
 const DownloadDensity = StateAction.build({
     from: PluginStateObject.Root,
     display: { name: 'Download Density', description: 'Load a density from the provided source and create its default visual.' },
     params: (a, ctx: PluginContext) => {
-        const { options } = getDataFormatExtensionsOptions(ctx.dataFormat.registry)
+        const { options } = ctx.dataFormat.registry
         return {
             source: PD.MappedStatic('rcsb', {
                 'pdbe': PD.Group({