diff --git a/README.md b/README.md index d35442edd4eaefce2553a3486341583f9c5b44ad..b056d10f9655b711a5d4bf6ba840aa991b2cd85d 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,7 @@ The core of Mol* currently consists of these modules: - `mol-math` Math related (loosely) algorithms and data structures. - `mol-io` Parsing library. Each format is parsed into an interface that corresponds to the data stored by it. Support for common coordinate, experimental/map, and annotation data formats. - `mol-model` Data structures and algorithms (such as querying) for representing molecular data (including coordinate, experimental/map, and annotation data). +- `mol-model-formats` Data format parsers for `mol-model`. - `mol-model-props` Common "custom properties". - `mol-script` A scriting language for creating representations/scenes and querying (includes the [MolQL query language](https://molql.github.io)). - `mol-geo` Creating (molecular) geometries. diff --git a/package.json b/package.json index b139054660070dab572be66c51017642c92bcb75..58063c0f529d6e7e66126062b60c766bf529dfca 100644 --- a/package.json +++ b/package.json @@ -54,6 +54,7 @@ "mol-math($|/.*)": "<rootDir>/src/mol-math$1", "mol-model($|/.*)": "<rootDir>/src/mol-model$1", "mol-model-props($|/.*)": "<rootDir>/src/mol-model-props$1", + "mol-model-formats($|/.*)": "<rootDir>/src/mol-model-formats$1", "mol-plugin($|/.*)": "<rootDir>/src/mol-plugin$1", "mol-ql($|/.*)": "<rootDir>/src/mol-ql$1", "mol-repr($|/.*)": "<rootDir>/src/mol-repr$1", diff --git a/src/apps/basic-wrapper/index.html b/src/apps/basic-wrapper/index.html index c2556fb983ce2989cdb35a33d63861ab3078df76..b4a202a2dddd64da6f63e70dab00eb33de5e9744 100644 --- a/src/apps/basic-wrapper/index.html +++ b/src/apps/basic-wrapper/index.html @@ -3,7 +3,7 @@ <head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0"> - <title>Mol* Viewer</title> + <title>Mol* Plugin Wrapper</title> <style> * { margin: 0; @@ -30,15 +30,19 @@ <script> var pdbId = '5ire', assemblyId= '1'; var url = 'https://www.ebi.ac.uk/pdbe/static/entry/' + pdbId + '_updated.cif'; + var format = 'cif'; + + // var url = 'https://www.ebi.ac.uk/pdbe/entry-files/pdb' + pdbId + '.ent'; + // var format = 'pdb'; BasicMolStarWrapper.init('app' /** or document.getElementById('app') */); BasicMolStarWrapper.setBackground(0xffffff); - BasicMolStarWrapper.loadCif(url, assemblyId); + BasicMolStarWrapper.load({ url: url, format: format, assemblyId: assemblyId }); BasicMolStarWrapper.toggleSpin(); document.getElementById('spin').onclick = () => BasicMolStarWrapper.toggleSpin(); - document.getElementById('asym').onclick = () => BasicMolStarWrapper.loadCif(url); - document.getElementById('asm').onclick = () => BasicMolStarWrapper.loadCif(url, assemblyId); + document.getElementById('asym').onclick = () => BasicMolStarWrapper.load({ url: url, format: format }); + document.getElementById('asm').onclick = () => BasicMolStarWrapper.load({ url: url, format: format, assemblyId: assemblyId }); </script> </body> </html> \ No newline at end of file diff --git a/src/apps/basic-wrapper/index.ts b/src/apps/basic-wrapper/index.ts index 0a4cca600a8226afda02b5ec155e417adc970c75..b45753f912eb4053dc8cf6730f085a2bdb635042 100644 --- a/src/apps/basic-wrapper/index.ts +++ b/src/apps/basic-wrapper/index.ts @@ -10,13 +10,16 @@ import { PluginContext } from 'mol-plugin/context'; import { PluginCommands } from 'mol-plugin/command'; import { StateTransforms } from 'mol-plugin/state/transforms'; import { StructureRepresentation3DHelpers } from 'mol-plugin/state/transforms/representation'; -import { StateTree } from 'mol-state'; import { Color } from 'mol-util/color'; +import { StateTreeBuilder } from 'mol-state/tree/builder'; +import { PluginStateObject as PSO } from 'mol-plugin/state/objects'; require('mol-plugin/skin/light.scss') +type SupportedFormats = 'cif' | 'pdb' +type LoadParams = { url: string, format?: SupportedFormats, assemblyId?: string } + class BasicWrapper { plugin: PluginContext; - stateTemplate: StateTree; init(target: string | HTMLElement) { this.plugin = createPlugin(typeof target === 'string' ? document.getElementById(target)! : target, { @@ -26,15 +29,23 @@ class BasicWrapper { showControls: false } }); + } + + private download(b: StateTreeBuilder.To<PSO.Root>, url: string) { + return b.apply(StateTransforms.Data.Download, { url, isBinary: false }) + } - const state = this.plugin.state.dataState.build(); - const visualRoot = state.toRoot() - .apply(StateTransforms.Data.Download, { url: '', isBinary: false }, { ref: 'url' }) - .apply(StateTransforms.Data.ParseCif) - .apply(StateTransforms.Model.TrajectoryFromMmCif) + private parse(b: StateTreeBuilder.To<PSO.Data.Binary | PSO.Data.String>, format: SupportedFormats, assemblyId: string) { + const parsed = format === 'cif' + ? b.apply(StateTransforms.Data.ParseCif).apply(StateTransforms.Model.TrajectoryFromMmCif) + : b.apply(StateTransforms.Model.TrajectoryFromPDB); + + return parsed .apply(StateTransforms.Model.ModelFromTrajectory, { modelIndex: 0 }) - .apply(StateTransforms.Model.StructureAssemblyFromModel, { id: '' }, { ref: 'asm' }) + .apply(StateTransforms.Model.StructureAssemblyFromModel, { id: assemblyId || 'deposited' }, { ref: 'asm' }); + } + private visual(visualRoot: StateTreeBuilder.To<PSO.Molecule.Structure>) { visualRoot.apply(StateTransforms.Model.StructureComplexElement, { type: 'atomic-sequence' }) .apply(StateTransforms.Representation.StructureRepresentation3D, StructureRepresentation3DHelpers.getDefaultParamsStatic(this.plugin, 'cartoon')); @@ -47,18 +58,33 @@ class BasicWrapper { visualRoot.apply(StateTransforms.Model.StructureComplexElement, { type: 'spheres' }) .apply(StateTransforms.Representation.StructureRepresentation3D, StructureRepresentation3DHelpers.getDefaultParamsStatic(this.plugin, 'spacefill')); - - this.stateTemplate = state.getTree(); + return visualRoot; } - async loadCif(url: string, assemblyId?: string) { - const state = this.stateTemplate.build(); + private loadedParams: LoadParams = { url: '', format: 'cif', assemblyId: '' }; + async load({ url, format = 'cif', assemblyId = '' }: LoadParams) { + let loadType: 'full' | 'update' = 'full'; + + const state = this.plugin.state.dataState; - state.to('url').update(StateTransforms.Data.Download, p => ({ ...p, url })); - state.to('asm').update(StateTransforms.Model.StructureAssemblyFromModel, p => ({ ...p, id: assemblyId })); + if (this.loadedParams.url !== url || this.loadedParams.format !== format) { + loadType = 'full'; + } else if (this.loadedParams.url === url) { + if (state.select('asm').length > 0) loadType = 'update'; + } - await PluginCommands.State.Update.dispatch(this.plugin, { state: this.plugin.state.dataState, tree: state }); + let tree: StateTreeBuilder.Root; + if (loadType === 'full') { + await PluginCommands.State.RemoveObject.dispatch(this.plugin, { state, ref: state.tree.root.ref }); + tree = state.build(); + this.visual(this.parse(this.download(tree.toRoot(), url), format, assemblyId)); + } else { + tree = state.build(); + tree.to('asm').update(StateTransforms.Model.StructureAssemblyFromModel, p => ({ ...p, id: assemblyId || 'deposited' })); + } + await PluginCommands.State.Update.dispatch(this.plugin, { state: this.plugin.state.dataState, tree }); + this.loadedParams = { url, format, assemblyId }; PluginCommands.Camera.Reset.dispatch(this.plugin, { }); } diff --git a/src/apps/structure-info/model.ts b/src/apps/structure-info/model.ts index c01698f91a40bda96ebad2fd4955b18e9a1546a7..58c76c69f32b3202f852b75bf5d1a54126eb3de4 100644 --- a/src/apps/structure-info/model.ts +++ b/src/apps/structure-info/model.ts @@ -9,11 +9,12 @@ import * as argparse from 'argparse' require('util.promisify').shim(); import { CifFrame } from 'mol-io/reader/cif' -import { Model, Structure, StructureElement, Unit, Format, StructureProperties, UnitRing } from 'mol-model/structure' +import { Model, Structure, StructureElement, Unit, StructureProperties, UnitRing } from 'mol-model/structure' // import { Run, Progress } from 'mol-task' import { OrderedSet } from 'mol-data/int'; import { openCif, downloadCif } from './helpers'; import { Vec3 } from 'mol-math/linear-algebra'; +import { trajectoryFromMmCIF } from 'mol-model-formats/structure/mmcif'; async function downloadFromPdb(pdb: string) { @@ -198,7 +199,7 @@ export function printModelStats(models: ReadonlyArray<Model>) { } export async function getModelsAndStructure(frame: CifFrame) { - const models = await Model.create(Format.mmCIF(frame)).run(); + const models = await trajectoryFromMmCIF(frame).run(); const structure = Structure.ofModel(models[0]); return { models, structure }; } @@ -247,7 +248,7 @@ interface Args { download?: string, file?: string, - models?:boolean, + models?: boolean, seq?: boolean, ihm?: boolean, units?: boolean, diff --git a/src/apps/structure-info/volume.ts b/src/apps/structure-info/volume.ts index 866c2995060517323acc31bfbe79ad4c640d1b95..59029e155acce0ccede581f7ece1f1b585e34ddd 100644 --- a/src/apps/structure-info/volume.ts +++ b/src/apps/structure-info/volume.ts @@ -8,7 +8,7 @@ import * as fs from 'fs' import * as argparse from 'argparse' import * as util from 'util' -import { VolumeData, volumeFromDensityServerData, VolumeIsoValue } from 'mol-model/volume' +import { VolumeData, VolumeIsoValue } from 'mol-model/volume' import { downloadCif } from './helpers' import CIF from 'mol-io/reader/cif' import { DensityServer_Data_Database } from 'mol-io/reader/cif/schema/density-server'; @@ -17,6 +17,7 @@ import { StringBuilder } from 'mol-util'; import { Task } from 'mol-task'; import { createVolumeIsosurfaceMesh } from 'mol-repr/volume/isosurface'; import { createEmptyTheme } from 'mol-theme/theme'; +import { volumeFromDensityServerData } from 'mol-model-formats/volume/density-server'; require('util.promisify').shim(); const writeFileAsync = util.promisify(fs.writeFile); diff --git a/src/mol-io/reader/_spec/cif.spec.ts b/src/mol-io/reader/_spec/cif.spec.ts index a2fb03ed952f69e1c9c93878a7b5904d174f3ddd..43e5cd4c5139e3da21b9d7de00d57cae59bae5c0 100644 --- a/src/mol-io/reader/_spec/cif.spec.ts +++ b/src/mol-io/reader/_spec/cif.spec.ts @@ -6,17 +6,16 @@ */ import * as Data from '../cif/data-model' -import TextField from '../cif/text/field' import * as Schema from '../cif/schema' import { Column } from 'mol-data/db' const columnData = `123abc d,e,f '4 5 6'`; // 123abc d,e,f '4 5 6' -const intField = TextField({ data: columnData, indices: [0, 1, 1, 2, 2, 3], count: 3 }, 3); -const strField = TextField({ data: columnData, indices: [3, 4, 4, 5, 5, 6], count: 3 }, 3); -const strListField = TextField({ data: columnData, indices: [7, 12], count: 1 }, 1); -const intListField = TextField({ data: columnData, indices: [14, 19], count: 1 }, 1); +const intField = Data.CifField.ofTokens({ data: columnData, indices: [0, 1, 1, 2, 2, 3], count: 3 }); +const strField = Data.CifField.ofTokens({ data: columnData, indices: [3, 4, 4, 5, 5, 6], count: 3 }); +const strListField = Data.CifField.ofTokens({ data: columnData, indices: [7, 12], count: 1 }); +const intListField = Data.CifField.ofTokens({ data: columnData, indices: [14, 19], count: 1 }); const testBlock = Data.CifBlock(['test'], { test: Data.CifCategory('test', 3, ['int', 'str', 'strList', 'intList'], { diff --git a/src/mol-io/reader/_spec/csv.spec.ts b/src/mol-io/reader/_spec/csv.spec.ts index 0802bc7491fedc90c702ef6bc24d7fa653746895..8977b5909aa73d7714760f377a3d912048a20db5 100644 --- a/src/mol-io/reader/_spec/csv.spec.ts +++ b/src/mol-io/reader/_spec/csv.spec.ts @@ -62,7 +62,7 @@ describe('csv reader', () => { }); it('tabs', async () => { - const parsed = await Csv(tabString, { delimiter: '\t' }).run();; + const parsed = await Csv(tabString, { delimiter: '\t' }).run(); if (parsed.isError) return; const csvFile = parsed.result; diff --git a/src/mol-io/reader/ccp4/parser.ts b/src/mol-io/reader/ccp4/parser.ts index 43cc24a8302bba97fc893f7eaee26ebcdabfb277..d48c343e5a63e4df3623551bd71b0f95f9aff4ce 100644 --- a/src/mol-io/reader/ccp4/parser.ts +++ b/src/mol-io/reader/ccp4/parser.ts @@ -6,7 +6,7 @@ import { Task, RuntimeContext } from 'mol-task'; import { Ccp4File, Ccp4Header } from './schema' -import Result from '../result' +import { ReaderResult as Result } from '../result' import { FileHandle } from '../../common/file-handle'; async function parseInternal(file: FileHandle, ctx: RuntimeContext): Promise<Result<Ccp4File>> { diff --git a/src/mol-io/reader/cif/binary/parser.ts b/src/mol-io/reader/cif/binary/parser.ts index 8a5f0ea1a8133aed0254577b2791d75952042183..4cf3ae57f882844f856d28612a943fd7a3515179 100644 --- a/src/mol-io/reader/cif/binary/parser.ts +++ b/src/mol-io/reader/cif/binary/parser.ts @@ -7,7 +7,7 @@ import * as Data from '../data-model' import { EncodedCategory, EncodedFile } from '../../../common/binary-cif' import Field from './field' -import Result from '../../result' +import { ReaderResult as Result } from '../../result' import decodeMsgPack from '../../../common/msgpack/decode' import { Task } from 'mol-task' diff --git a/src/mol-io/reader/cif/data-model.ts b/src/mol-io/reader/cif/data-model.ts index fe044426017e052ae96a2e9d420f8c13d52c53c1..855cecb9bf74387ad68f08325da0bdfa00da2c8c 100644 --- a/src/mol-io/reader/cif/data-model.ts +++ b/src/mol-io/reader/cif/data-model.ts @@ -5,10 +5,12 @@ * @author Alexander Rose <alexander.rose@weirdbyte.de> */ -import { Column } from 'mol-data/db' +import { Column, ColumnHelpers } from 'mol-data/db' import { Tensor } from 'mol-math/linear-algebra' -import { getNumberType, NumberType } from '../common/text/number-parser'; +import { getNumberType, NumberType, parseInt as fastParseInt, parseFloat as fastParseFloat } from '../common/text/number-parser'; import { Encoding } from '../../common/binary-cif'; +import { Tokens } from '../common/text/tokenizer'; +import { areValuesEqualProvider } from '../common/text/column/token'; export interface CifFile { readonly name?: string, @@ -55,6 +57,19 @@ export namespace CifCategory { export function empty(name: string): CifCategory { return { rowCount: 0, name, fieldNames: [], getField(name: string) { return void 0; } }; }; + + export type SomeFields<S> = { [P in keyof S]?: CifField } + export type Fields<S> = { [P in keyof S]: CifField } + + export function ofFields(name: string, fields: { [name: string]: CifField | undefined }): CifCategory { + const fieldNames = Object.keys(fields); + return { + rowCount: fieldNames.length > 0 ? fields[fieldNames[0]]!.rowCount : 0, + name, + fieldNames, + getField(name) { return fields[name]; } + }; + } } /** @@ -81,6 +96,108 @@ export interface CifField { toFloatArray(params?: Column.ToArrayParams<number>): ReadonlyArray<number> } +export namespace CifField { + export function ofString(value: string) { + return ofStrings([value]); + } + + export function ofStrings(values: string[]): CifField { + const rowCount = values.length; + const str: CifField['str'] = row => { const ret = values[row]; if (!ret || ret === '.' || ret === '?') return ''; return ret; }; + const int: CifField['int'] = row => { const v = values[row]; return fastParseInt(v, 0, v.length) || 0; }; + const float: CifField['float'] = row => { const v = values[row]; return fastParseFloat(v, 0, v.length) || 0; }; + const valueKind: CifField['valueKind'] = row => { + const v = values[row], l = v.length; + if (l > 1) return Column.ValueKind.Present; + if (l === 0) return Column.ValueKind.NotPresent; + const c = v.charCodeAt(0); + if (c === 46 /* . */) return Column.ValueKind.NotPresent; + if (c === 63 /* ? */) return Column.ValueKind.Unknown; + return Column.ValueKind.Present; + }; + + return { + __array: void 0, + binaryEncoding: void 0, + isDefined: true, + rowCount, + str, + int, + float, + valueKind, + areValuesEqual: (rowA, rowB) => values[rowA] === values[rowB], + toStringArray: params => ColumnHelpers.createAndFillArray(rowCount, str, params), + toIntArray: params => ColumnHelpers.createAndFillArray(rowCount, int, params), + toFloatArray: params => ColumnHelpers.createAndFillArray(rowCount, float, params) + } + } + + export function ofNumbers(values: number[]): CifField { + const rowCount = values.length; + const str: CifField['str'] = row => { return '' + values[row]; }; + const float: CifField['float'] = row => values[row]; + const valueKind: CifField['valueKind'] = row => Column.ValueKind.Present; + + return { + __array: void 0, + binaryEncoding: void 0, + isDefined: true, + rowCount, + str, + int: float, + float, + valueKind, + areValuesEqual: (rowA, rowB) => values[rowA] === values[rowB], + toStringArray: params => ColumnHelpers.createAndFillArray(rowCount, str, params), + toIntArray: params => ColumnHelpers.createAndFillArray(rowCount, float, params), + toFloatArray: params => ColumnHelpers.createAndFillArray(rowCount, float, params) + } + } + + export function ofTokens(tokens: Tokens): CifField { + const { data, indices, count: rowCount } = tokens; + + const str: CifField['str'] = row => { + const ret = data.substring(indices[2 * row], indices[2 * row + 1]); + if (ret === '.' || ret === '?') return ''; + return ret; + }; + + const int: CifField['int'] = row => { + return fastParseInt(data, indices[2 * row], indices[2 * row + 1]) || 0; + }; + + const float: CifField['float'] = row => { + return fastParseFloat(data, indices[2 * row], indices[2 * row + 1]) || 0; + }; + + const valueKind: CifField['valueKind'] = row => { + const s = indices[2 * row], l = indices[2 * row + 1] - s; + if (l > 1) return Column.ValueKind.Present; + if (l === 0) return Column.ValueKind.NotPresent; + const v = data.charCodeAt(s); + if (v === 46 /* . */) return Column.ValueKind.NotPresent; + if (v === 63 /* ? */) return Column.ValueKind.Unknown; + return Column.ValueKind.Present; + }; + + return { + __array: void 0, + binaryEncoding: void 0, + isDefined: true, + rowCount, + str, + int, + float, + valueKind, + areValuesEqual: areValuesEqualProvider(tokens), + toStringArray: params => ColumnHelpers.createAndFillArray(rowCount, str, params), + toIntArray: params => ColumnHelpers.createAndFillArray(rowCount, int, params), + toFloatArray: params => ColumnHelpers.createAndFillArray(rowCount, float, params) + } + } +} + export function getTensor(category: CifCategory, field: string, space: Tensor.Space, row: number, zeroIndexed: boolean): Tensor.Data { const ret = space.create(); const offset = zeroIndexed ? 0 : 1; diff --git a/src/mol-io/reader/cif/text/field.ts b/src/mol-io/reader/cif/text/field.ts deleted file mode 100644 index f4e08a090352acb330abfba07c340e820cbd8d9e..0000000000000000000000000000000000000000 --- a/src/mol-io/reader/cif/text/field.ts +++ /dev/null @@ -1,54 +0,0 @@ -/** - * Copyright (c) 2017-2018 mol* contributors, licensed under MIT, See LICENSE file for more info. - * - * @author David Sehnal <david.sehnal@gmail.com> - * @author Alexander Rose <alexander.rose@weirdbyte.de> - */ - -import { Column, ColumnHelpers } from 'mol-data/db' -import * as TokenColumn from '../../common/text/column/token' -import { Tokens } from '../../common/text/tokenizer' -import * as Data from '../data-model' -import { parseInt as fastParseInt, parseFloat as fastParseFloat } from '../../common/text/number-parser' - -export default function CifTextField(tokens: Tokens, rowCount: number): Data.CifField { - const { data, indices } = tokens; - - const str: Data.CifField['str'] = row => { - const ret = data.substring(indices[2 * row], indices[2 * row + 1]); - if (ret === '.' || ret === '?') return ''; - return ret; - }; - - const int: Data.CifField['int'] = row => { - return fastParseInt(data, indices[2 * row], indices[2 * row + 1]) || 0; - }; - - const float: Data.CifField['float'] = row => { - return fastParseFloat(data, indices[2 * row], indices[2 * row + 1]) || 0; - }; - - const valueKind: Data.CifField['valueKind'] = row => { - const s = indices[2 * row]; - if (indices[2 * row + 1] - s !== 1) return Column.ValueKind.Present; - const v = data.charCodeAt(s); - if (v === 46 /* . */) return Column.ValueKind.NotPresent; - if (v === 63 /* ? */) return Column.ValueKind.Unknown; - return Column.ValueKind.Present; - }; - - return { - __array: void 0, - binaryEncoding: void 0, - isDefined: true, - rowCount, - str, - int, - float, - valueKind, - areValuesEqual: TokenColumn.areValuesEqualProvider(tokens), - toStringArray: params => ColumnHelpers.createAndFillArray(rowCount, str, params), - toIntArray: params => ColumnHelpers.createAndFillArray(rowCount, int, params), - toFloatArray: params => ColumnHelpers.createAndFillArray(rowCount, float, params) - } -} \ No newline at end of file diff --git a/src/mol-io/reader/cif/text/parser.ts b/src/mol-io/reader/cif/text/parser.ts index 3ee75e270731600d06f6ab72b39d9359c1227f06..920546393c25b5aa585b34d8740d872222e63670 100644 --- a/src/mol-io/reader/cif/text/parser.ts +++ b/src/mol-io/reader/cif/text/parser.ts @@ -23,9 +23,8 @@ */ import * as Data from '../data-model' -import Field from './field' import { Tokens, TokenBuilder } from '../../common/text/tokenizer' -import Result from '../../result' +import { ReaderResult as Result } from '../../result' import { Task, RuntimeContext, chunkedSubtask } from 'mol-task' /** @@ -445,7 +444,7 @@ function handleSingle(tokenizer: TokenizerState, ctx: FrameContext): CifCategory errorMessage: 'Expected value.' } } - fields[fieldName] = Field({ data: tokenizer.data, indices: [tokenizer.tokenStart, tokenizer.tokenEnd], count: 1 }, 1); + fields[fieldName] = Data.CifField.ofTokens({ data: tokenizer.data, indices: [tokenizer.tokenStart, tokenizer.tokenEnd], count: 1 }); fieldNames[fieldNames.length] = fieldName; moveNext(tokenizer); } @@ -507,7 +506,7 @@ async function handleLoop(tokenizer: TokenizerState, ctx: FrameContext): Promise const rowCountEstimate = name === '_atom_site' ? (tokenizer.data.length / 100) | 0 : 32; const tokens: Tokens[] = []; const fieldCount = fieldNames.length; - for (let i = 0; i < fieldCount; i++) tokens[i] = TokenBuilder.create(tokenizer, rowCountEstimate); + for (let i = 0; i < fieldCount; i++) tokens[i] = TokenBuilder.create(tokenizer.data, rowCountEstimate); const state: LoopReadState = { fieldCount, @@ -529,7 +528,7 @@ async function handleLoop(tokenizer: TokenizerState, ctx: FrameContext): Promise const rowCount = (state.tokenCount / fieldCount) | 0; const fields = Object.create(null); for (let i = 0; i < fieldCount; i++) { - fields[fieldNames[i]] = Field(tokens[i], rowCount); + fields[fieldNames[i]] = Data.CifField.ofTokens(tokens[i]); } const catName = name.substr(1); diff --git a/src/mol-io/reader/common/text/tokenizer.ts b/src/mol-io/reader/common/text/tokenizer.ts index 60f14c1b135bfca7d215f3a3d8939997b4ec2ae5..fce7c9037cc7c9eb546edae1a12fb4d838b06efa 100644 --- a/src/mol-io/reader/common/text/tokenizer.ts +++ b/src/mol-io/reader/common/text/tokenizer.ts @@ -8,7 +8,9 @@ import { chunkedSubtask, RuntimeContext } from 'mol-task' -export interface Tokenizer { +export { Tokenizer } + +interface Tokenizer { data: string, position: number, @@ -25,7 +27,7 @@ export interface Tokens { indices: ArrayLike<number> } -export function Tokenizer(data: string): Tokenizer { +function Tokenizer(data: string): Tokenizer { return { data, position: 0, @@ -36,7 +38,7 @@ export function Tokenizer(data: string): Tokenizer { }; } -export namespace Tokenizer { +namespace Tokenizer { export function getTokenString(state: Tokenizer) { return state.data.substring(state.tokenStart, state.tokenEnd); } @@ -52,7 +54,7 @@ export namespace Tokenizer { /** * Eat everything until a newline occurs. */ - export function eatLine(state: Tokenizer) { + export function eatLine(state: Tokenizer): boolean { const { data } = state; while (state.position < state.length) { switch (data.charCodeAt(state.position)) { @@ -60,7 +62,7 @@ export namespace Tokenizer { state.tokenEnd = state.position; ++state.position; ++state.lineNumber; - return; + return true; case 13: // \r state.tokenEnd = state.position; ++state.position; @@ -68,13 +70,14 @@ export namespace Tokenizer { if (data.charCodeAt(state.position) === 10) { ++state.position; } - return; + return true; default: ++state.position; break; } } state.tokenEnd = state.position; + return state.tokenStart !== state.tokenEnd; } /** Sets the current token start to the current position */ @@ -85,7 +88,7 @@ export namespace Tokenizer { /** Sets the current token start to current position and moves to the next line. */ export function markLine(state: Tokenizer) { state.tokenStart = state.position; - eatLine(state); + return eatLine(state); } /** Advance the state by the given number of lines and return line starts/ends as tokens. */ @@ -95,15 +98,18 @@ export namespace Tokenizer { } function readLinesChunk(state: Tokenizer, count: number, tokens: Tokens) { + let read = 0; for (let i = 0; i < count; i++) { - markLine(state); + if (!markLine(state)) return read; TokenBuilder.addUnchecked(tokens, state.tokenStart, state.tokenEnd); + read++; } + return read; } /** Advance the state by the given number of lines and return line starts/ends as tokens. */ export function readLines(state: Tokenizer, count: number): Tokens { - const lineTokens = TokenBuilder.create(state, count * 2); + const lineTokens = TokenBuilder.create(state.data, count * 2); readLinesChunk(state, count, lineTokens); return lineTokens; } @@ -111,7 +117,7 @@ export namespace Tokenizer { /** Advance the state by the given number of lines and return line starts/ends as tokens. */ export async function readLinesAsync(state: Tokenizer, count: number, ctx: RuntimeContext, initialLineCount = 100000): Promise<Tokens> { const { length } = state; - const lineTokens = TokenBuilder.create(state, count * 2); + const lineTokens = TokenBuilder.create(state.data, count * 2); let linesAlreadyRead = 0; await chunkedSubtask(ctx, initialLineCount, state, (chunkSize, state) => { @@ -124,6 +130,37 @@ export namespace Tokenizer { return lineTokens; } + export function readAllLines(data: string) { + const state = Tokenizer(data); + const tokens = TokenBuilder.create(state.data, Math.max(data.length / 80, 2)) + while (markLine(state)) { + TokenBuilder.add(tokens, state.tokenStart, state.tokenEnd); + } + return tokens; + } + + function readLinesChunkChecked(state: Tokenizer, count: number, tokens: Tokens) { + let read = 0; + for (let i = 0; i < count; i++) { + if (!markLine(state)) return read; + TokenBuilder.add(tokens, state.tokenStart, state.tokenEnd); + read++; + } + return read; + } + + export async function readAllLinesAsync(data: string, ctx: RuntimeContext, chunkSize = 100000) { + const state = Tokenizer(data); + const tokens = TokenBuilder.create(state.data, Math.max(data.length / 80, 2)); + + await chunkedSubtask(ctx, chunkSize, state, (chunkSize, state) => { + readLinesChunkChecked(state, chunkSize, tokens); + return state.position < state.length ? chunkSize : 0; + }, (ctx, state) => ctx.update({ message: 'Parsing...', current: state.position, max: length })); + + return tokens; + } + /** * Eat everything until a whitespace/newline occurs. */ @@ -191,6 +228,7 @@ export namespace Tokenizer { state.tokenStart = s; state.tokenEnd = e + 1; state.position = end; + return state; } } @@ -228,22 +266,24 @@ export namespace TokenBuilder { tokens.count++; } + export function addToken(tokens: Tokens, tokenizer: Tokenizer) { + add(tokens, tokenizer.tokenStart, tokenizer.tokenEnd); + } + export function addUnchecked(tokens: Tokens, start: number, end: number) { (tokens as Builder).indices[(tokens as Builder).offset++] = start; (tokens as Builder).indices[(tokens as Builder).offset++] = end; tokens.count++; } - export function create(tokenizer: Tokenizer, size: number): Tokens { + export function create(data: string, size: number): Tokens { size = Math.max(10, size) return <Builder>{ - data: tokenizer.data, + data, indicesLenMinus2: (size - 2) | 0, count: 0, offset: 0, indices: new Uint32Array(size) } } -} - -export default Tokenizer \ No newline at end of file +} \ No newline at end of file diff --git a/src/mol-io/reader/csv/field.ts b/src/mol-io/reader/csv/field.ts index fdc4c5135d4037d72dbd06385accae9bd805bbfa..48d1f1072aa1a54cc9dd77e94318ecfce6ce14e5 100644 --- a/src/mol-io/reader/csv/field.ts +++ b/src/mol-io/reader/csv/field.ts @@ -4,6 +4,6 @@ * @author Alexander Rose <alexander.rose@weirdbyte.de> */ -import Field from '../cif/text/field' +import { CifField } from '../cif/data-model'; -export default Field \ No newline at end of file +export default CifField.ofTokens \ No newline at end of file diff --git a/src/mol-io/reader/csv/parser.ts b/src/mol-io/reader/csv/parser.ts index d5bc68535344ff6c9d7aed63445b26e19a9220c7..4207202703338f1300bce9ebc0142c8ac6b39294 100644 --- a/src/mol-io/reader/csv/parser.ts +++ b/src/mol-io/reader/csv/parser.ts @@ -8,7 +8,7 @@ import { Tokens, TokenBuilder, Tokenizer } from '../common/text/tokenizer' import * as Data from './data-model' import Field from './field' -import Result from '../result' +import { ReaderResult as Result } from '../result' import { Task, RuntimeContext, chunkedSubtask, } from 'mol-task' const enum CsvTokenType { @@ -231,7 +231,7 @@ function readRecordsChunks(state: State) { function addColumn (state: State) { state.columnNames.push(Tokenizer.getTokenString(state.tokenizer)) - state.tokens.push(TokenBuilder.create(state.tokenizer, state.data.length / 80)) + state.tokens.push(TokenBuilder.create(state.tokenizer.data, state.data.length / 80)) } function init(state: State) { @@ -254,7 +254,7 @@ async function handleRecords(state: State): Promise<Data.CsvTable> { const columns: Data.CsvColumns = Object.create(null); for (let i = 0; i < state.columnCount; ++i) { - columns[state.columnNames[i]] = Field(state.tokens[i], state.recordCount); + columns[state.columnNames[i]] = Field(state.tokens[i]); } return Data.CsvTable(state.recordCount, state.columnNames, columns) diff --git a/src/mol-io/reader/dsn6/parser.ts b/src/mol-io/reader/dsn6/parser.ts index a77c968662036b64dab1f21d0df9a90edd3d117d..35416d7a82ec686f5846ce84eabacc448ced77d3 100644 --- a/src/mol-io/reader/dsn6/parser.ts +++ b/src/mol-io/reader/dsn6/parser.ts @@ -6,7 +6,7 @@ import { Task, RuntimeContext } from 'mol-task'; import { Dsn6File, Dsn6Header } from './schema' -import Result from '../result' +import { ReaderResult as Result } from '../result' import { FileHandle } from '../../common/file-handle'; function parseBrixHeader(str: string): Dsn6Header { diff --git a/src/mol-io/reader/gro/parser.ts b/src/mol-io/reader/gro/parser.ts index 6183a9a5fee6e887e889b4bd6efd4cec5b3de42b..0367a3ee8bbb27a676679345d9d7551edbd9fdbe 100644 --- a/src/mol-io/reader/gro/parser.ts +++ b/src/mol-io/reader/gro/parser.ts @@ -6,10 +6,10 @@ */ import { Column } from 'mol-data/db' -import Tokenizer from '../common/text/tokenizer' +import { Tokenizer } from '../common/text/tokenizer' import FixedColumn from '../common/text/column/fixed' import * as Schema from './schema' -import Result from '../result' +import { ReaderResult as Result } from '../result' import { Task, RuntimeContext } from 'mol-task' interface State { diff --git a/src/mol-io/reader/mol2/parser.ts b/src/mol-io/reader/mol2/parser.ts index 297e1502618329594b0966427649ba7b014aaa87..0a11a9a0aa93da4402fcf43f2c16270e4728d16e 100644 --- a/src/mol-io/reader/mol2/parser.ts +++ b/src/mol-io/reader/mol2/parser.ts @@ -15,7 +15,7 @@ import { Column } from 'mol-data/db' import { TokenBuilder, Tokenizer } from '../common/text/tokenizer' import TokenColumn from '../common/text/column/token' import * as Schema from './schema' -import Result from '../result' +import { ReaderResult as Result } from '../result' import { Task, RuntimeContext, chunkedSubtask } from 'mol-task' const { skipWhitespace, eatValue, markLine, getTokenString, readLine } = Tokenizer; @@ -130,12 +130,12 @@ async function handleAtoms(state: State): Promise<Schema.Mol2Atoms> { } // required columns - const atom_idTokens = TokenBuilder.create(tokenizer, molecule.num_atoms * 2); - const atom_nameTokens = TokenBuilder.create(tokenizer, molecule.num_atoms * 2); - const xTokens = TokenBuilder.create(tokenizer, molecule.num_atoms * 2); - const yTokens = TokenBuilder.create(tokenizer, molecule.num_atoms * 2); - const zTokens = TokenBuilder.create(tokenizer, molecule.num_atoms * 2); - const atom_typeTokens = TokenBuilder.create(tokenizer, molecule.num_atoms * 2); + const atom_idTokens = TokenBuilder.create(tokenizer.data, molecule.num_atoms * 2); + const atom_nameTokens = TokenBuilder.create(tokenizer.data, molecule.num_atoms * 2); + const xTokens = TokenBuilder.create(tokenizer.data, molecule.num_atoms * 2); + const yTokens = TokenBuilder.create(tokenizer.data, molecule.num_atoms * 2); + const zTokens = TokenBuilder.create(tokenizer.data, molecule.num_atoms * 2); + const atom_typeTokens = TokenBuilder.create(tokenizer.data, molecule.num_atoms * 2); const atom_idTokenColumn = TokenColumn(atom_idTokens); const atom_nameTokenColumn = TokenColumn(atom_nameTokens); @@ -145,10 +145,10 @@ async function handleAtoms(state: State): Promise<Schema.Mol2Atoms> { const atom_typeColumn = TokenColumn(atom_typeTokens); // optional columns - const subst_idTokens = TokenBuilder.create(tokenizer, molecule.num_atoms * 2); - const subst_nameTokens = TokenBuilder.create(tokenizer, molecule.num_atoms * 2); - const chargeTokens = TokenBuilder.create(tokenizer, molecule.num_atoms * 2); - const status_bitTokens = TokenBuilder.create(tokenizer, molecule.num_atoms * 2); + const subst_idTokens = TokenBuilder.create(tokenizer.data, molecule.num_atoms * 2); + const subst_nameTokens = TokenBuilder.create(tokenizer.data, molecule.num_atoms * 2); + const chargeTokens = TokenBuilder.create(tokenizer.data, molecule.num_atoms * 2); + const status_bitTokens = TokenBuilder.create(tokenizer.data, molecule.num_atoms * 2); const subst_idTokenColumn = TokenColumn(subst_idTokens); const subst_nameTokenColumn = TokenColumn(subst_nameTokens); @@ -257,10 +257,10 @@ async function handleBonds(state: State): Promise<Schema.Mol2Bonds> { } // required columns - const bond_idTokens = TokenBuilder.create(tokenizer, molecule.num_bonds * 2); - const origin_bond_idTokens = TokenBuilder.create(tokenizer, molecule.num_bonds * 2); - const target_bond_idTokens = TokenBuilder.create(tokenizer, molecule.num_bonds * 2); - const bondTypeTokens = TokenBuilder.create(tokenizer, molecule.num_bonds * 2); + const bond_idTokens = TokenBuilder.create(tokenizer.data, molecule.num_bonds * 2); + const origin_bond_idTokens = TokenBuilder.create(tokenizer.data, molecule.num_bonds * 2); + const target_bond_idTokens = TokenBuilder.create(tokenizer.data, molecule.num_bonds * 2); + const bondTypeTokens = TokenBuilder.create(tokenizer.data, molecule.num_bonds * 2); const bond_idTokenColumn = TokenColumn(bond_idTokens); const origin_bond_idTokenColumn = TokenColumn(origin_bond_idTokens); @@ -268,7 +268,7 @@ async function handleBonds(state: State): Promise<Schema.Mol2Bonds> { const bondTypeTokenColumn = TokenColumn(bondTypeTokens); // optional columns - const status_bitTokens = TokenBuilder.create(tokenizer, molecule.num_bonds * 2); + const status_bitTokens = TokenBuilder.create(tokenizer.data, molecule.num_bonds * 2); const status_bitTokenColumn = TokenColumn(status_bitTokens); const undefStr = Column.Undefined(molecule.num_bonds, Column.Schema.str); diff --git a/src/mol-io/reader/obj/parser.ts b/src/mol-io/reader/obj/parser.ts index 046143b305b51530b1e8d0e1cad7f8cb097fa50c..a8b113a33d0c88f5af90189e79359df8b5f097ae 100644 --- a/src/mol-io/reader/obj/parser.ts +++ b/src/mol-io/reader/obj/parser.ts @@ -4,7 +4,7 @@ * @author Alexander Rose <alexander.rose@weirdbyte.de> */ -import Result from '../result' +import { ReaderResult as Result } from '../result' import { Task, RuntimeContext } from 'mol-task' import { Mesh } from 'mol-geo/geometry/mesh/mesh'; diff --git a/src/mol-io/reader/pdb/parser.ts b/src/mol-io/reader/pdb/parser.ts new file mode 100644 index 0000000000000000000000000000000000000000..600ac278e0bbf6290e773c028bd5d0140dd5d201 --- /dev/null +++ b/src/mol-io/reader/pdb/parser.ts @@ -0,0 +1,14 @@ +/** + * Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author David Sehnal <david.sehnal@gmail.com> + */ + +import { PdbFile } from './schema'; +import { Task } from 'mol-task'; +import { ReaderResult } from '../result'; +import { Tokenizer } from '../common/text/tokenizer'; + +export function parsePDB(data: string, id?: string): Task<ReaderResult<PdbFile>> { + return Task.create('Parse PDB', async ctx => ReaderResult.success({ id, lines: await Tokenizer.readAllLinesAsync(data, ctx) })); +} \ No newline at end of file diff --git a/src/mol-io/reader/pdb/schema.ts b/src/mol-io/reader/pdb/schema.ts new file mode 100644 index 0000000000000000000000000000000000000000..3031f1aea0e085a50ec47a7ae352534ef4e2ce4a --- /dev/null +++ b/src/mol-io/reader/pdb/schema.ts @@ -0,0 +1,12 @@ +/** + * Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author David Sehnal <david.sehnal@gmail.com> + */ + +import { Tokens } from '../common/text/tokenizer'; + +export interface PdbFile { + id?: string, + lines: Tokens +} \ No newline at end of file diff --git a/src/mol-io/reader/result.ts b/src/mol-io/reader/result.ts index 4eb76dd373929b858e09e2de3d7f649abd078f86..255ae0c9eac4e20d0068d2c266db1fa47e696b8c 100644 --- a/src/mol-io/reader/result.ts +++ b/src/mol-io/reader/result.ts @@ -5,7 +5,7 @@ * @author David Sehnal <david.sehnal@gmail.com> */ -type ReaderResult<T> = Success<T> | Error +type ReaderResult<T> = ReaderResult.Success<T> | ReaderResult.Error namespace ReaderResult { export function error<T>(message: string, line = -1): ReaderResult<T> { @@ -15,28 +15,28 @@ namespace ReaderResult { export function success<T>(result: T, warnings: string[] = []): ReaderResult<T> { return new Success<T>(result, warnings); } -} -export class Error { - isError: true = true; + export class Error { + isError: true = true; - toString() { - if (this.line >= 0) { - return `[Line ${this.line}] ${this.message}`; + toString() { + if (this.line >= 0) { + return `[Line ${this.line}] ${this.message}`; + } + return this.message; } - return this.message; - } - constructor( - public message: string, - public line: number) { + constructor( + public message: string, + public line: number) { + } } -} -export class Success<T> { - isError: false = false; + export class Success<T> { + isError: false = false; - constructor(public result: T, public warnings: string[]) { } + constructor(public result: T, public warnings: string[]) { } + } } -export default ReaderResult \ No newline at end of file +export { ReaderResult } \ No newline at end of file diff --git a/src/mol-math/linear-algebra/3d/mat4.ts b/src/mol-math/linear-algebra/3d/mat4.ts index b18fb3e5cc57036ca1ea9d69a9dba0f5162bf7cb..abf5cfb9660c20a43039510c799baa7e568f88f2 100644 --- a/src/mol-math/linear-algebra/3d/mat4.ts +++ b/src/mol-math/linear-algebra/3d/mat4.ts @@ -119,6 +119,10 @@ namespace Mat4 { a[4 * j + i] = value; } + export function getValue(a: Mat4, i: number, j: number) { + return a[4 * j + i]; + } + export function toArray(a: Mat4, out: NumberArray, offset: number) { out[offset + 0] = a[0]; out[offset + 1] = a[1]; diff --git a/src/mol-model-formats/structure/format.ts b/src/mol-model-formats/structure/format.ts new file mode 100644 index 0000000000000000000000000000000000000000..d8ecc3d822830da08eb6718c50166457d89d644a --- /dev/null +++ b/src/mol-model-formats/structure/format.ts @@ -0,0 +1,18 @@ +/** + * Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author David Sehnal <david.sehnal@gmail.com> + */ + +import { mmCIF_Database } from 'mol-io/reader/cif/schema/mmcif'; +import CIF, { CifFrame } from 'mol-io/reader/cif'; + +type ModelFormat = + | ModelFormat.mmCIF + +namespace ModelFormat { + export interface mmCIF { kind: 'mmCIF', data: mmCIF_Database, frame: CifFrame } + export function mmCIF(frame: CifFrame, data?: mmCIF_Database): mmCIF { return { kind: 'mmCIF', data: data || CIF.schema.mmCIF(frame), frame }; } +} + +export { ModelFormat } \ No newline at end of file diff --git a/src/mol-model/structure/model/formats/gro.ts b/src/mol-model-formats/structure/gro.ts similarity index 100% rename from src/mol-model/structure/model/formats/gro.ts rename to src/mol-model-formats/structure/gro.ts diff --git a/src/mol-model-formats/structure/mmcif.ts b/src/mol-model-formats/structure/mmcif.ts new file mode 100644 index 0000000000000000000000000000000000000000..6eecc06b6b3e94ae8e5da0aa09141a4582fdb15c --- /dev/null +++ b/src/mol-model-formats/structure/mmcif.ts @@ -0,0 +1,16 @@ +/** + * Copyright (c) 2017-2018 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author David Sehnal <david.sehnal@gmail.com> + * @author Alexander Rose <alexander.rose@weirdbyte.de> + */ + +import { Model } from 'mol-model/structure/model/model'; +import { Task } from 'mol-task'; +import { ModelFormat } from './format'; +import { _parse_mmCif } from './mmcif/parser'; +import { CifFrame } from 'mol-io/reader/cif'; + +export function trajectoryFromMmCIF(frame: CifFrame): Task<Model.Trajectory> { + return Task.create('Create mmCIF Model', ctx => _parse_mmCif(ModelFormat.mmCIF(frame), ctx)); +} \ No newline at end of file diff --git a/src/mol-model/structure/model/formats/mmcif/assembly.ts b/src/mol-model-formats/structure/mmcif/assembly.ts similarity index 94% rename from src/mol-model/structure/model/formats/mmcif/assembly.ts rename to src/mol-model-formats/structure/mmcif/assembly.ts index d0c9eddeac44e99333be0e4f0b77c14d189cfe56..ad0fa39b9b9cf687478b2d6b5fc6730b4ea5bc03 100644 --- a/src/mol-model/structure/model/formats/mmcif/assembly.ts +++ b/src/mol-model-formats/structure/mmcif/assembly.ts @@ -6,12 +6,11 @@ import { Mat4, Tensor } from 'mol-math/linear-algebra' import { SymmetryOperator } from 'mol-math/geometry/symmetry-operator' -import Format from '../../format' -import { Assembly, OperatorGroup, OperatorGroups } from '../../properties/symmetry' -import { Queries as Q } from '../../../query' - -import mmCIF_Format = Format.mmCIF -import { StructureProperties } from '../../../structure'; +import { Assembly, OperatorGroup, OperatorGroups } from 'mol-model/structure/model/properties/symmetry' +import { Queries as Q } from 'mol-model/structure' +import { StructureProperties } from 'mol-model/structure'; +import { ModelFormat } from '../format'; +import mmCIF_Format = ModelFormat.mmCIF export function createAssemblies(format: mmCIF_Format): ReadonlyArray<Assembly> { const { pdbx_struct_assembly } = format.data; diff --git a/src/mol-model/structure/model/formats/mmcif/atomic.ts b/src/mol-model-formats/structure/mmcif/atomic.ts similarity index 84% rename from src/mol-model/structure/model/formats/mmcif/atomic.ts rename to src/mol-model-formats/structure/mmcif/atomic.ts index 8ae819b489f88f991e96dac5701c74f9699e8004..422d45776d1fdd1f8b89378a0f7f4e902c8de3f5 100644 --- a/src/mol-model/structure/model/formats/mmcif/atomic.ts +++ b/src/mol-model-formats/structure/mmcif/atomic.ts @@ -8,18 +8,18 @@ import { Column, Table } from 'mol-data/db'; import { Interval, Segmentation } from 'mol-data/int'; import { mmCIF_Database } from 'mol-io/reader/cif/schema/mmcif'; import UUID from 'mol-util/uuid'; -import { ElementIndex } from '../../../../structure'; -import Format from '../../format'; -import { Model } from '../../model'; -import { AtomicConformation, AtomicData, AtomicHierarchy, AtomicSegments, AtomsSchema, ChainsSchema, ResiduesSchema } from '../../properties/atomic'; -import { getAtomicIndex } from '../../properties/utils/atomic-index'; -import { ElementSymbol } from '../../types'; -import { Entities } from '../../properties/common'; - -import mmCIF_Format = Format.mmCIF -import { getAtomicRanges } from '../../properties/utils/atomic-ranges'; -import { FormatData } from '../mmcif'; -import { getAtomicDerivedData } from '../../properties/utils/atomic-derived'; +import { ElementIndex } from 'mol-model/structure'; +import { Model } from 'mol-model/structure/model/model'; +import { AtomicConformation, AtomicData, AtomicHierarchy, AtomicSegments, AtomsSchema, ChainsSchema, ResiduesSchema } from 'mol-model/structure/model/properties/atomic'; +import { getAtomicIndex } from 'mol-model/structure/model/properties/utils/atomic-index'; +import { ElementSymbol } from 'mol-model/structure/model/types'; +import { Entities } from 'mol-model/structure/model/properties/common'; +import { getAtomicRanges } from 'mol-model/structure/model/properties/utils/atomic-ranges'; +import { getAtomicDerivedData } from 'mol-model/structure/model/properties/utils/atomic-derived'; +import { ModelFormat } from '../format'; +import mmCIF_Format = ModelFormat.mmCIF +import { FormatData } from './parser'; + type AtomSite = mmCIF_Database['atom_site'] @@ -101,7 +101,7 @@ export function getAtomicHierarchyAndConformation(format: mmCIF_Format, atom_sit const index = getAtomicIndex(hierarchyData, entities, hierarchySegments); const derived = getAtomicDerivedData(hierarchyData, index, formatData.chemicalComponentMap); - const hierarchyRanges = getAtomicRanges(hierarchyData, hierarchySegments, conformation, formatData.chemicalComponentMap); + const hierarchyRanges = getAtomicRanges(hierarchyData, hierarchySegments, conformation, derived.residue.moleculeType); const hierarchy: AtomicHierarchy = { ...hierarchyData, ...hierarchySegments, ...hierarchyRanges, index, derived }; return { sameAsPrevious: false, hierarchy, conformation }; } \ No newline at end of file diff --git a/src/mol-model/structure/model/formats/mmcif/bonds.ts b/src/mol-model-formats/structure/mmcif/bonds.ts similarity index 100% rename from src/mol-model/structure/model/formats/mmcif/bonds.ts rename to src/mol-model-formats/structure/mmcif/bonds.ts diff --git a/src/mol-model/structure/model/formats/mmcif/bonds/comp.ts b/src/mol-model-formats/structure/mmcif/bonds/comp.ts similarity index 96% rename from src/mol-model/structure/model/formats/mmcif/bonds/comp.ts rename to src/mol-model-formats/structure/mmcif/bonds/comp.ts index 4304807338aded73a011f2d8bbf8100fd276cf18..eceb64760607d51d544a8a9a587a46471bd9334d 100644 --- a/src/mol-model/structure/model/formats/mmcif/bonds/comp.ts +++ b/src/mol-model-formats/structure/mmcif/bonds/comp.ts @@ -5,11 +5,11 @@ * @author Alexander Rose <alexander.rose@weirdbyte.de> */ -import { Model } from '../../../model' -import { LinkType } from '../../../types' -import { ModelPropertyDescriptor } from '../../../properties/custom'; +import { Model } from 'mol-model/structure/model/model' +import { LinkType } from 'mol-model/structure/model/types' +import { ModelPropertyDescriptor } from 'mol-model/structure/model/properties/custom'; import { mmCIF_Database } from 'mol-io/reader/cif/schema/mmcif'; -import { Structure, Unit, StructureProperties, StructureElement } from '../../../../structure'; +import { Structure, Unit, StructureProperties, StructureElement } from 'mol-model/structure'; import { Segmentation } from 'mol-data/int'; import { CifWriter } from 'mol-io/writer/cif' diff --git a/src/mol-model/structure/model/formats/mmcif/bonds/struct_conn.ts b/src/mol-model-formats/structure/mmcif/bonds/struct_conn.ts similarity index 96% rename from src/mol-model/structure/model/formats/mmcif/bonds/struct_conn.ts rename to src/mol-model-formats/structure/mmcif/bonds/struct_conn.ts index 486c071a1de7c2bc4f5f9b1e144add4b9187512c..21a5b9e57f6b076c6b7184738dee962ecd58164d 100644 --- a/src/mol-model/structure/model/formats/mmcif/bonds/struct_conn.ts +++ b/src/mol-model-formats/structure/mmcif/bonds/struct_conn.ts @@ -5,16 +5,16 @@ * @author Alexander Rose <alexander.rose@weirdbyte.de> */ -import { Model } from '../../../model' -import { Structure } from '../../../../structure' -import { LinkType } from '../../../types' +import { Model } from 'mol-model/structure/model/model' +import { Structure } from 'mol-model/structure' +import { LinkType } from 'mol-model/structure/model/types' import { findEntityIdByAsymId, findAtomIndexByLabelName } from '../util' import { Column } from 'mol-data/db' -import { ModelPropertyDescriptor } from '../../../properties/custom'; +import { ModelPropertyDescriptor } from 'mol-model/structure/model/properties/custom'; import { mmCIF_Database, mmCIF_Schema } from 'mol-io/reader/cif/schema/mmcif'; import { SortedArray } from 'mol-data/int'; import { CifWriter } from 'mol-io/writer/cif' -import { ElementIndex, ResidueIndex } from '../../../indexing'; +import { ElementIndex, ResidueIndex } from 'mol-model/structure/model/indexing'; export interface StructConn { getResidueEntries(residueAIndex: ResidueIndex, residueBIndex: ResidueIndex): ReadonlyArray<StructConn.Entry>, diff --git a/src/mol-model/structure/model/formats/mmcif/ihm.ts b/src/mol-model-formats/structure/mmcif/ihm.ts similarity index 89% rename from src/mol-model/structure/model/formats/mmcif/ihm.ts rename to src/mol-model-formats/structure/mmcif/ihm.ts index 8cba685fb7c13ecaed3ca2c39cb91851b0d81534..731af9e3af83de9784c51bba90da32d0c15de777 100644 --- a/src/mol-model/structure/model/formats/mmcif/ihm.ts +++ b/src/mol-model-formats/structure/mmcif/ihm.ts @@ -5,16 +5,16 @@ */ import { mmCIF_Database as mmCIF, mmCIF_Schema } from 'mol-io/reader/cif/schema/mmcif' -import { CoarseHierarchy, CoarseConformation, CoarseElementData, CoarseSphereConformation, CoarseGaussianConformation } from '../../properties/coarse' -import { Entities } from '../../properties/common'; +import { CoarseHierarchy, CoarseConformation, CoarseElementData, CoarseSphereConformation, CoarseGaussianConformation } from 'mol-model/structure/model/properties/coarse' +import { Entities } from 'mol-model/structure/model/properties/common'; import { Column } from 'mol-data/db'; -import { getCoarseKeys } from '../../properties/utils/coarse-keys'; +import { getCoarseKeys } from 'mol-model/structure/model/properties/utils/coarse-keys'; import { UUID } from 'mol-util'; import { Segmentation, Interval } from 'mol-data/int'; import { Mat3, Tensor } from 'mol-math/linear-algebra'; -import { ElementIndex, ChainIndex } from '../../indexing'; -import { getCoarseRanges } from '../../properties/utils/coarse-ranges'; -import { FormatData } from '../mmcif'; +import { ElementIndex, ChainIndex } from 'mol-model/structure/model/indexing'; +import { getCoarseRanges } from 'mol-model/structure/model/properties/utils/coarse-ranges'; +import { FormatData } from './parser'; export interface IHMData { model_id: number, diff --git a/src/mol-model/structure/model/formats/mmcif/pair-restraint.ts b/src/mol-model-formats/structure/mmcif/pair-restraint.ts similarity index 100% rename from src/mol-model/structure/model/formats/mmcif/pair-restraint.ts rename to src/mol-model-formats/structure/mmcif/pair-restraint.ts diff --git a/src/mol-model/structure/model/formats/mmcif/pair-restraints/cross-links.ts b/src/mol-model-formats/structure/mmcif/pair-restraints/cross-links.ts similarity index 96% rename from src/mol-model/structure/model/formats/mmcif/pair-restraints/cross-links.ts rename to src/mol-model-formats/structure/mmcif/pair-restraints/cross-links.ts index 0aebfc74fe1146a789d35b54804064dc5c3acf66..2964745e9b5723e3621f97551592d9961e1277df 100644 --- a/src/mol-model/structure/model/formats/mmcif/pair-restraints/cross-links.ts +++ b/src/mol-model-formats/structure/mmcif/pair-restraints/cross-links.ts @@ -4,12 +4,12 @@ * @author Alexander Rose <alexander.rose@weirdbyte.de> */ -import { Model } from '../../../model' +import { Model } from 'mol-model/structure/model/model' import { Table } from 'mol-data/db' import { mmCIF_Schema } from 'mol-io/reader/cif/schema/mmcif'; import { findAtomIndexByLabelName } from '../util'; -import { Unit } from '../../../../structure'; -import { ElementIndex } from '../../../indexing'; +import { Unit } from 'mol-model/structure'; +import { ElementIndex } from 'mol-model/structure/model/indexing'; function findAtomIndex(model: Model, entityId: string, asymId: string, seqId: number, atomId: string) { if (!model.atomicHierarchy.atoms.auth_atom_id.isDefined) return -1 diff --git a/src/mol-model/structure/model/formats/mmcif/pair-restraints/predicted-contacts.ts b/src/mol-model-formats/structure/mmcif/pair-restraints/predicted-contacts.ts similarity index 100% rename from src/mol-model/structure/model/formats/mmcif/pair-restraints/predicted-contacts.ts rename to src/mol-model-formats/structure/mmcif/pair-restraints/predicted-contacts.ts diff --git a/src/mol-model/structure/model/formats/mmcif.ts b/src/mol-model-formats/structure/mmcif/parser.ts similarity index 83% rename from src/mol-model/structure/model/formats/mmcif.ts rename to src/mol-model-formats/structure/mmcif/parser.ts index 971eebd0ab62dd2edd7799954dddffc5fa759bd7..4a16920ba94f80923960add8ee65176df8d8eb66 100644 --- a/src/mol-model/structure/model/formats/mmcif.ts +++ b/src/mol-model-formats/structure/mmcif/parser.ts @@ -9,26 +9,32 @@ import { Column, Table } from 'mol-data/db'; import { mmCIF_Database, mmCIF_Schema } from 'mol-io/reader/cif/schema/mmcif'; import { Spacegroup, SpacegroupCell, SymmetryOperator } from 'mol-math/geometry'; import { Tensor, Vec3 } from 'mol-math/linear-algebra'; -import { Task, RuntimeContext } from 'mol-task'; +import { RuntimeContext } from 'mol-task'; import UUID from 'mol-util/uuid'; -import Format from '../format'; -import { Model } from '../model'; -import { Entities } from '../properties/common'; -import { CustomProperties } from '../properties/custom'; -import { ModelSymmetry } from '../properties/symmetry'; -import { createAssemblies } from './mmcif/assembly'; -import { getAtomicHierarchyAndConformation } from './mmcif/atomic'; -import { ComponentBond } from './mmcif/bonds'; -import { getIHMCoarse, EmptyIHMCoarse, IHMData } from './mmcif/ihm'; -import { getSecondaryStructureMmCif } from './mmcif/secondary-structure'; -import { getSequence } from './mmcif/sequence'; -import { sortAtomSite } from './mmcif/sort'; -import { StructConn } from './mmcif/bonds/struct_conn'; -import { ChemicalComponent, ChemicalComponentMap } from '../properties/chemical-component'; -import { ComponentType, getMoleculeType, MoleculeType } from '../types'; - -import mmCIF_Format = Format.mmCIF +import { Model } from 'mol-model/structure/model/model'; +import { Entities } from 'mol-model/structure/model/properties/common'; +import { CustomProperties } from 'mol-model/structure/model/properties/custom'; +import { ModelSymmetry } from 'mol-model/structure/model/properties/symmetry'; +import { createAssemblies } from './assembly'; +import { getAtomicHierarchyAndConformation } from './atomic'; +import { ComponentBond } from './bonds'; +import { getIHMCoarse, EmptyIHMCoarse, IHMData } from './ihm'; +import { getSecondaryStructureMmCif } from './secondary-structure'; +import { getSequence } from './sequence'; +import { sortAtomSite } from './sort'; +import { StructConn } from './bonds/struct_conn'; +import { ChemicalComponent } from 'mol-model/structure/model/properties/chemical-component'; +import { getMoleculeType, MoleculeType } from 'mol-model/structure/model/types'; +import { ModelFormat } from '../format'; import { SaccharideComponentMap, SaccharideComponent, SaccharidesSnfgMap, SaccharideCompIdMap, UnknownSaccharideComponent } from 'mol-model/structure/structure/carbohydrates/constants'; +import mmCIF_Format = ModelFormat.mmCIF +import { memoize1 } from 'mol-util/memoize'; + +export async function _parse_mmCif(format: mmCIF_Format, ctx: RuntimeContext) { + const formatData = getFormatData(format) + const isIHM = format.data.ihm_model_list._rowCount > 0; + return isIHM ? await readIHM(ctx, format, formatData) : await readStandard(ctx, format, formatData); +} type AtomSite = mmCIF_Database['atom_site'] @@ -73,6 +79,7 @@ function getNcsOperators(format: mmCIF_Format) { } return opers; } + function getModifiedResidueNameMap(format: mmCIF_Format): Model['properties']['modifiedResidues'] { const data = format.data.pdbx_struct_mod_residue; const parentId = new Map<string, string>(); @@ -89,22 +96,14 @@ function getModifiedResidueNameMap(format: mmCIF_Format): Model['properties']['m return { parentId, details }; } -function getChemicalComponentMap(format: mmCIF_Format): ChemicalComponentMap { +function getChemicalComponentMap(format: mmCIF_Format): Model['properties']['chemicalComponentMap'] { const map = new Map<string, ChemicalComponent>(); - const { id, type, name, pdbx_synonyms, formula, formula_weight } = format.data.chem_comp - for (let i = 0, il = id.rowCount; i < il; ++i) { - const _id = id.value(i) - const _type = type.value(i) - const cc: ChemicalComponent = { - id: _id, - type: ComponentType[_type], - moleculeType: getMoleculeType(_type, _id), - name: name.value(i), - synonyms: pdbx_synonyms.value(i), - formula: formula.value(i), - formulaWeight: formula_weight.value(i), + const { chem_comp } = format.data + if (chem_comp._rowCount > 0) { + const { id } = format.data.chem_comp + for (let i = 0, il = id.rowCount; i < il; ++i) { + map.set(id.value(i), Table.getRow(format.data.chem_comp, i)) } - map.set(_id, cc) } return map } @@ -137,12 +136,24 @@ function getSaccharideComponentMap(format: mmCIF_Format): SaccharideComponentMap } } } else { - // TODO check if present in format.data.atom_site.label_comp_id - SaccharideCompIdMap.forEach((v, k) => map.set(k, v)) + const uniqueNames = getUniqueComponentNames(format) + SaccharideCompIdMap.forEach((v, k) => { + if (uniqueNames.has(k)) map.set(k, v) + }) } return map } +const getUniqueComponentNames = memoize1((format: mmCIF_Format) => { + const uniqueNames = new Set<string>() + const data = format.data.atom_site + const comp_id = data.label_comp_id.isDefined ? data.label_comp_id : data.auth_comp_id; + for (let i = 0, il = comp_id.rowCount; i < il; ++i) { + uniqueNames.add(comp_id.value(i)) + } + return uniqueNames +}) + export interface FormatData { modifiedResidues: Model['properties']['modifiedResidues'] chemicalComponentMap: Model['properties']['chemicalComponentMap'] @@ -298,14 +309,4 @@ async function readIHM(ctx: RuntimeContext, format: mmCIF_Format, formatData: Fo } return models; -} - -function buildModels(format: mmCIF_Format): Task<ReadonlyArray<Model>> { - const formatData = getFormatData(format) - return Task.create('Create mmCIF Model', async ctx => { - const isIHM = format.data.ihm_model_list._rowCount > 0; - return isIHM ? await readIHM(ctx, format, formatData) : await readStandard(ctx, format, formatData); - }); -} - -export default buildModels; \ No newline at end of file +} \ No newline at end of file diff --git a/src/mol-model/structure/model/formats/mmcif/secondary-structure.ts b/src/mol-model-formats/structure/mmcif/secondary-structure.ts similarity index 95% rename from src/mol-model/structure/model/formats/mmcif/secondary-structure.ts rename to src/mol-model-formats/structure/mmcif/secondary-structure.ts index d871052c96890e86bdd7c0b241755a944d2e5b20..3c0def78910279781a359199a4a06a0e841dad20 100644 --- a/src/mol-model/structure/model/formats/mmcif/secondary-structure.ts +++ b/src/mol-model-formats/structure/mmcif/secondary-structure.ts @@ -6,11 +6,11 @@ */ import { mmCIF_Database as mmCIF, mmCIF_Database } from 'mol-io/reader/cif/schema/mmcif' -import { SecondaryStructureType } from '../../types'; -import { AtomicHierarchy } from '../../properties/atomic'; -import { SecondaryStructure } from '../../properties/seconday-structure'; +import { SecondaryStructureType } from 'mol-model/structure/model/types'; +import { AtomicHierarchy } from 'mol-model/structure/model/properties/atomic'; +import { SecondaryStructure } from 'mol-model/structure/model/properties/seconday-structure'; import { Column } from 'mol-data/db'; -import { ChainIndex, ResidueIndex } from '../../indexing'; +import { ChainIndex, ResidueIndex } from 'mol-model/structure/model/indexing'; export function getSecondaryStructureMmCif(data: mmCIF_Database, hierarchy: AtomicHierarchy): SecondaryStructure { const map: SecondaryStructureMap = new Map(); diff --git a/src/mol-model/structure/model/formats/mmcif/sequence.ts b/src/mol-model-formats/structure/mmcif/sequence.ts similarity index 87% rename from src/mol-model/structure/model/formats/mmcif/sequence.ts rename to src/mol-model-formats/structure/mmcif/sequence.ts index 70caf352fd19e0ffa1177dc0615c90b825f72417..9915651b634980c5c522935caa45eabc3c702aee 100644 --- a/src/mol-model/structure/model/formats/mmcif/sequence.ts +++ b/src/mol-model-formats/structure/mmcif/sequence.ts @@ -5,11 +5,11 @@ */ import { mmCIF_Database as mmCIF } from 'mol-io/reader/cif/schema/mmcif' -import StructureSequence from '../../properties/sequence' +import StructureSequence from 'mol-model/structure/model/properties/sequence' import { Column } from 'mol-data/db'; -import { AtomicHierarchy } from '../../properties/atomic'; -import { Entities } from '../../properties/common'; -import { Sequence } from '../../../../sequence'; +import { AtomicHierarchy } from 'mol-model/structure/model/properties/atomic'; +import { Entities } from 'mol-model/structure/model/properties/common'; +import { Sequence } from 'mol-model/sequence'; // TODO how to handle microheterogeneity // see http://mmcif.wwpdb.org/dictionaries/mmcif_pdbx_v50.dic/Categories/entity_poly_seq.html diff --git a/src/mol-model/structure/model/formats/mmcif/sort.ts b/src/mol-model-formats/structure/mmcif/sort.ts similarity index 100% rename from src/mol-model/structure/model/formats/mmcif/sort.ts rename to src/mol-model-formats/structure/mmcif/sort.ts diff --git a/src/mol-model/structure/model/formats/mmcif/util.ts b/src/mol-model-formats/structure/mmcif/util.ts similarity index 89% rename from src/mol-model/structure/model/formats/mmcif/util.ts rename to src/mol-model-formats/structure/mmcif/util.ts index 044dee3a6bc4aba02a65e9d047ec0c3e7afe2188..1f398a11bee1cfd425447854ecc9e0bc36bfe71a 100644 --- a/src/mol-model/structure/model/formats/mmcif/util.ts +++ b/src/mol-model-formats/structure/mmcif/util.ts @@ -4,8 +4,8 @@ * @author Alexander Rose <alexander.rose@weirdbyte.de> */ -import { Model } from '../../model' -import { ElementIndex } from '../../indexing'; +import { Model } from 'mol-model/structure/model' +import { ElementIndex } from 'mol-model/structure/model/indexing'; export function findEntityIdByAsymId(model: Model, asymId: string) { if (model.sourceData.kind !== 'mmCIF') return '' diff --git a/src/mol-model-formats/structure/pdb.ts b/src/mol-model-formats/structure/pdb.ts new file mode 100644 index 0000000000000000000000000000000000000000..a8695876900d671bf514d1c8ec01872fdcfbae31 --- /dev/null +++ b/src/mol-model-formats/structure/pdb.ts @@ -0,0 +1,21 @@ +/** + * Copyright (c) 2017-2018 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author David Sehnal <david.sehnal@gmail.com> + */ + +import { PdbFile } from 'mol-io/reader/pdb/schema'; +import { pdbToMmCif } from './pdb/to-cif'; +import { Model } from 'mol-model/structure/model'; +import { Task } from 'mol-task'; +import { ModelFormat } from './format'; +import { _parse_mmCif } from './mmcif/parser'; + +export function trajectoryFromPDB(pdb: PdbFile): Task<Model.Trajectory> { + return Task.create('Parse PDB', async ctx => { + await ctx.update('Converting to mmCIF'); + const cif = await pdbToMmCif(pdb); + const format = ModelFormat.mmCIF(cif); + return _parse_mmCif(format, ctx); + }) +} diff --git a/src/mol-model-formats/structure/pdb/assembly.ts b/src/mol-model-formats/structure/pdb/assembly.ts new file mode 100644 index 0000000000000000000000000000000000000000..20ae0fdc51476f66d0f1c0cd7503651b79299047 --- /dev/null +++ b/src/mol-model-formats/structure/pdb/assembly.ts @@ -0,0 +1,234 @@ +/** + * Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author David Sehnal <david.sehnal@gmail.com> + */ + +import { CifCategory, CifField } from 'mol-io/reader/cif'; +import { mmCIF_Schema } from 'mol-io/reader/cif/schema/mmcif'; +import { Mat4 } from 'mol-math/linear-algebra'; +import { Tokens } from 'mol-io/reader/common/text/tokenizer'; + +export function parseCryst1(id: string, record: string): CifCategory[] { + // COLUMNS DATA TYPE CONTENTS + // -------------------------------------------------------------------------------- + // 1 - 6 Record name "CRYST1" + // 7 - 15 Real(9.3) a (Angstroms) + // 16 - 24 Real(9.3) b (Angstroms) + // 25 - 33 Real(9.3) c (Angstroms) + // 34 - 40 Real(7.2) alpha (degrees) + // 41 - 47 Real(7.2) beta (degrees) + // 48 - 54 Real(7.2) gamma (degrees) + // 56 - 66 LString Space group + // 67 - 70 Integer Z value + + const get = (s: number, l: number) => (record.substr(s, l) || '').trim() + + const cell: CifCategory.Fields<mmCIF_Schema['cell']> = { + entry_id: CifField.ofString(id), + length_a: CifField.ofString(get(6, 9)), + length_b: CifField.ofString(get(15, 9)), + length_c: CifField.ofString(get(24, 9)), + angle_alpha: CifField.ofString(get(33, 7)), + angle_beta: CifField.ofString(get(40, 7)), + angle_gamma: CifField.ofString(get(47, 7)), + Z_PDB: CifField.ofString(get(66, 4)), + pdbx_unique_axis: CifField.ofString('?') + }; + const symmetry: CifCategory.Fields<mmCIF_Schema['symmetry']> = { + entry_id: CifField.ofString(id), + 'space_group_name_H-M': CifField.ofString(get(55, 11)), + Int_Tables_number: CifField.ofString('?'), + cell_setting: CifField.ofString('?'), + space_group_name_Hall: CifField.ofString('?') + } + return [CifCategory.ofFields('cell', cell), CifCategory.ofFields('symmetry', symmetry)]; +} + +interface PdbAssembly { + id: string, + details: string, + groups: { chains: string[], operators: { id: number, matrix: Mat4 }[] }[] +} + +function PdbAssembly(id: string, details: string): PdbAssembly { + return { id, details, groups: [] }; +} + +export function parseRemark350(lines: Tokens, lineStart: number, lineEnd: number): CifCategory[] { + const assemblies: PdbAssembly[] = []; + + // Read the assemblies + let current: PdbAssembly, group: PdbAssembly['groups'][0], matrix: Mat4, operId = 1; + const getLine = (n: number) => lines.data.substring(lines.indices[2 * n], lines.indices[2 * n + 1]); + for (let i = lineStart; i < lineEnd; i++) { + let line = getLine(i); + if (line.substr(11, 12) === 'BIOMOLECULE:') { + const id = line.substr(23).trim(); + let details: string = `Biomolecule ` + id; + line = getLine(i + 1); + if (line.substr(11, 30) !== 'APPLY THE FOLLOWING TO CHAINS:') { + i++; + details = line.substr(11).trim(); + } + current = PdbAssembly(id, details); + assemblies.push(current); + } else if (line.substr(13, 5) === 'BIOMT') { + const biomt = line.split(/\s+/) + const row = parseInt(line[18]) - 1 + + if (row === 0) { + matrix = Mat4.identity(); + group!.operators.push({ id: operId++, matrix }); + } + + Mat4.setValue(matrix!, row, 0, parseFloat(biomt[4])); + Mat4.setValue(matrix!, row, 1, parseFloat(biomt[5])); + Mat4.setValue(matrix!, row, 2, parseFloat(biomt[6])); + Mat4.setValue(matrix!, row, 3, parseFloat(biomt[7])); + } else if ( + line.substr(11, 30) === 'APPLY THE FOLLOWING TO CHAINS:' || + line.substr(11, 30) === ' AND CHAINS:') { + + if (line.substr(11, 5) === 'APPLY') { + group = { chains: [], operators: [] }; + current!.groups.push(group); + } + + const chainList = line.substr(41, 30).split(','); + for (let j = 0, jl = chainList.length; j < jl; ++j) { + const c = chainList[j].trim(); + if (c) group!.chains.push(c); + } + } + } + + if (assemblies.length === 0) return []; + + // Generate CIF + + // pdbx_struct_assembly + const pdbx_struct_assembly: CifCategory.SomeFields<mmCIF_Schema['pdbx_struct_assembly']> = { + id: CifField.ofStrings(assemblies.map(a => a.id)), + details: CifField.ofStrings(assemblies.map(a => a.details)) + }; + + + // pdbx_struct_assembly_gen + const pdbx_struct_assembly_gen_rows: { [P in keyof CifCategory.Fields<mmCIF_Schema['pdbx_struct_assembly_gen']>]: string }[] = []; + for (const asm of assemblies) { + for (const group of asm.groups) { + pdbx_struct_assembly_gen_rows.push({ + assembly_id: asm.id, + oper_expression: group.operators.map(o => o.id).join(','), + asym_id_list: group.chains.join(',') + }); + } + } + const pdbx_struct_assembly_gen: CifCategory.Fields<mmCIF_Schema['pdbx_struct_assembly_gen']> = { + assembly_id: CifField.ofStrings(pdbx_struct_assembly_gen_rows.map(r => r.assembly_id)), + oper_expression: CifField.ofStrings(pdbx_struct_assembly_gen_rows.map(r => r.oper_expression)), + asym_id_list: CifField.ofStrings(pdbx_struct_assembly_gen_rows.map(r => r.asym_id_list)) + }; + + // pdbx_struct_oper_list + const pdbx_struct_oper_list_rows: { [P in keyof CifCategory.Fields<mmCIF_Schema['pdbx_struct_oper_list']>]?: string }[] = []; + for (const asm of assemblies) { + for (const group of asm.groups) { + for (const oper of group.operators) { + const row = { + id: '' + oper.id, + type: '?', + name: '?', + symmetry_operation: '?' + } as (typeof pdbx_struct_oper_list_rows)[0] as any; + for (let i = 0; i < 3; i++) { + for (let j = 0; j < 3; j++) { + row[`matrix[${i + 1}][${j + 1}]`] = '' + Mat4.getValue(oper.matrix, i, j); + } + row[`vector[${i + 1}]`] = '' + Mat4.getValue(oper.matrix, i, 3); + } + pdbx_struct_oper_list_rows.push(row); + } + } + } + + const pdbx_struct_oper_list: CifCategory.SomeFields<mmCIF_Schema['pdbx_struct_oper_list']> = { + id: CifField.ofStrings(pdbx_struct_oper_list_rows.map(r => r.id!)), + type: CifField.ofStrings(pdbx_struct_oper_list_rows.map(r => r.type!)), + name: CifField.ofStrings(pdbx_struct_oper_list_rows.map(r => r.name!)), + symmetry_operation: CifField.ofStrings(pdbx_struct_oper_list_rows.map(r => r.symmetry_operation!)) + }; + for (let i = 0; i < 3; i++) { + for (let j = 0; j < 3; j++) { + const k = `matrix[${i + 1}][${j + 1}]`; + (pdbx_struct_oper_list as any)[k] = CifField.ofStrings(pdbx_struct_oper_list_rows.map(r => (r as any)[k]!)); + } + const k = `vector[${i + 1}]`; + (pdbx_struct_oper_list as any)[k] = CifField.ofStrings(pdbx_struct_oper_list_rows.map(r => (r as any)[k]!)); + } + + return [ + CifCategory.ofFields('pdbx_struct_assembly', pdbx_struct_assembly), + CifCategory.ofFields('pdbx_struct_assembly_gen', pdbx_struct_assembly_gen), + CifCategory.ofFields('pdbx_struct_oper_list', pdbx_struct_oper_list) + ]; +} + +export function parseMtrix(lines: Tokens, lineStart: number, lineEnd: number): CifCategory[] { + const matrices: Mat4[] = []; + let matrix: Mat4; + + const getLine = (n: number) => lines.data.substring(lines.indices[2 * n], lines.indices[2 * n + 1]); + for (let i = lineStart; i < lineEnd; i++) { + let line = getLine(i); + + const ncs = line.split(/\s+/); + const row = parseInt(line[5]) - 1; + + if (row === 0) { + matrix = Mat4.identity(); + matrices.push(matrix); + } + + Mat4.setValue(matrix!, row, 0, parseFloat(ncs[2])); + Mat4.setValue(matrix!, row, 1, parseFloat(ncs[3])); + Mat4.setValue(matrix!, row, 2, parseFloat(ncs[4])); + Mat4.setValue(matrix!, row, 3, parseFloat(ncs[5])); + } + + if (matrices.length === 0) return []; + + const struct_ncs_oper_rows: { [P in keyof CifCategory.Fields<mmCIF_Schema['struct_ncs_oper']>]?: string }[] = []; + let id = 1; + for (const oper of matrices) { + const row = { + id: 'ncsop' + (id++), + code: '.', + details: '.' + } as (typeof struct_ncs_oper_rows)[0] as any; + for (let i = 0; i < 3; i++) { + for (let j = 0; j < 3; j++) { + row[`matrix[${i + 1}][${j + 1}]`] = '' + Mat4.getValue(oper, i, j); + } + row[`vector[${i + 1}]`] = '' + Mat4.getValue(oper, i, 3); + } + struct_ncs_oper_rows.push(row); + } + + const struct_ncs_oper: CifCategory.SomeFields<mmCIF_Schema['struct_ncs_oper']> = { + id: CifField.ofStrings(struct_ncs_oper_rows.map(r => r.id!)), + code: CifField.ofStrings(struct_ncs_oper_rows.map(r => r.code!)), + details: CifField.ofStrings(struct_ncs_oper_rows.map(r => r.details!)), + }; + for (let i = 0; i < 3; i++) { + for (let j = 0; j < 3; j++) { + const k = `matrix[${i + 1}][${j + 1}]`; + (struct_ncs_oper as any)[k] = CifField.ofStrings(struct_ncs_oper_rows.map(r => (r as any)[k]!)); + } + const k = `vector[${i + 1}]`; + (struct_ncs_oper as any)[k] = CifField.ofStrings(struct_ncs_oper_rows.map(r => (r as any)[k]!)); + } + + return [CifCategory.ofFields('struct_ncs_oper', struct_ncs_oper)]; +} \ No newline at end of file diff --git a/src/mol-model-formats/structure/pdb/secondary-structure.ts b/src/mol-model-formats/structure/pdb/secondary-structure.ts new file mode 100644 index 0000000000000000000000000000000000000000..d5b92c64a2075c694037ee5a4ea38f7f4d9d82b3 --- /dev/null +++ b/src/mol-model-formats/structure/pdb/secondary-structure.ts @@ -0,0 +1,262 @@ +/** + * Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author Alexander Rose <alexander.rose@weirdbyte.de> + */ + +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'; + +const HelixTypes: {[k: string]: mmCIF_Schema['struct_conf']['conf_type_id']['T']} = { + // CLASS NUMBER + // TYPE OF HELIX (COLUMNS 39 - 40) + // -------------------------------------------------------------- + // Right-handed alpha (default) 1 + // Right-handed omega 2 + // Right-handed pi 3 + // Right-handed gamma 4 + // Right-handed 3 - 10 5 + // Left-handed alpha 6 + // Left-handed omega 7 + // Left-handed gamma 8 + // 2 - 7 ribbon/helix 9 + // Polyproline 10 + 1: 'HELX_RH_AL_P', + 2: 'HELX_RH_OM_P', + 3: 'HELX_RH_PI_P', + 4: 'HELX_RH_GA_P', + 5: 'HELX_RH_3T_P', + 6: 'HELX_LH_AL_P', + 7: 'HELX_LH_OM_P', + 8: 'HELX_LH_GA_P', + 9: 'HELX_RH_27_P', // TODO or left-handed??? + 10: 'HELX_RH_PP_P', // TODO or left-handed??? +} +function getStructConfTypeId(type: string): mmCIF_Schema['struct_conf']['conf_type_id']['T'] { + return HelixTypes[type] || 'HELX_P' +} + +interface PdbHelix { + serNum: string, + helixID: string, + initResName: string, + initChainID: string, + initSeqNum: string, + initICode: string, + endResName: string, + endChainID: string, + endSeqNum: string, + endICode: string, + helixClass: string, + comment: string, + length: string +} + +export function parseHelix(lines: Tokens, lineStart: number, lineEnd: number): CifCategory { + const helices: PdbHelix[] = [] + const getLine = (n: number) => lines.data.substring(lines.indices[2 * n], lines.indices[2 * n + 1]); + + for (let i = lineStart; i < lineEnd; i++) { + let line = getLine(i); + // COLUMNS DATA TYPE FIELD DEFINITION + // ----------------------------------------------------------------------------------- + // 1 - 6 Record name "HELIX " + // 8 - 10 Integer serNum Serial number of the helix. This starts + // at 1 and increases incrementally. + // 12 - 14 LString(3) helixID Helix identifier. In addition to a serial + // number, each helix is given an + // alphanumeric character helix identifier. + // 16 - 18 Residue name initResName Name of the initial residue. + // 20 Character initChainID Chain identifier for the chain containing + // this helix. + // 22 - 25 Integer initSeqNum Sequence number of the initial residue. + // 26 AChar initICode Insertion code of the initial residue. + // 28 - 30 Residue name endResName Name of the terminal residue of the helix. + // 32 Character endChainID Chain identifier for the chain containing + // this helix. + // 34 - 37 Integer endSeqNum Sequence number of the terminal residue. + // 38 AChar endICode Insertion code of the terminal residue. + // 39 - 40 Integer helixClass Helix class (see below). + // 41 - 70 String comment Comment about this helix. + // 72 - 76 Integer length Length of this helix. + helices.push({ + serNum: line.substr(7, 3).trim(), + helixID: line.substr(11, 3).trim(), + initResName: line.substr(15, 3).trim(), + initChainID: line.substr(19, 1).trim(), + initSeqNum: line.substr(21, 4).trim(), + initICode: line.substr(25, 1).trim(), + endResName: line.substr(27, 3).trim(), + endChainID: line.substr(31, 3).trim(), + endSeqNum: line.substr(33, 4).trim(), + endICode: line.substr(37, 1).trim(), + helixClass: line.substr(38, 2).trim(), + comment: line.substr(40, 30).trim(), + length: line.substr(71, 5).trim() + }) + } + + 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 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_auth_asym_id, + beg_auth_comp_id, + beg_auth_seq_id, + + 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_auth_asym_id, + end_auth_comp_id, + end_auth_seq_id, + + id: CifField.ofStrings(helices.map(h => h.serNum)), + pdbx_beg_PDB_ins_code: CifField.ofStrings(helices.map(h => h.initICode)), + pdbx_end_PDB_ins_code: CifField.ofStrings(helices.map(h => h.endICode)), + pdbx_PDB_helix_class: CifField.ofStrings(helices.map(h => h.helixClass)), + pdbx_PDB_helix_length: CifField.ofStrings(helices.map(h => h.length)), + pdbx_PDB_helix_id: CifField.ofStrings(helices.map(h => h.helixID)), + }; + return CifCategory.ofFields('struct_conf', struct_conf); +} + +// + +interface PdbSheet { + strand: string, + sheetID: string, + numStrands: string, + initResName: string, + initChainID: string, + initSeqNum: string, + initICode: string, + endResName: string, + endChainID: string, + endSeqNum: string, + endICode: string, + sense: string, + curAtom: string, + curResName: string, + curChainId: string, + curResSeq: string, + curICode: string, + prevAtom: string, + prevResName: string, + prevChainId: string, + prevResSeq: string, + prevICode: string, +} + +export function parseSheet(lines: Tokens, lineStart: number, lineEnd: number): CifCategory { + const sheets: PdbSheet[] = [] + const getLine = (n: number) => lines.data.substring(lines.indices[2 * n], lines.indices[2 * n + 1]); + + for (let i = lineStart; i < lineEnd; i++) { + let line = getLine(i); + // COLUMNS DATA TYPE FIELD DEFINITION + // ------------------------------------------------------------------------------------- + // 1 - 6 Record name "SHEET " + // 8 - 10 Integer strand Strand number which starts at 1 for each + // strand within a sheet and increases by one. + // 12 - 14 LString(3) sheetID Sheet identifier. + // 15 - 16 Integer numStrands Number of strands in sheet. + // 18 - 20 Residue name initResName Residue name of initial residue. + // 22 Character initChainID Chain identifier of initial residue + // in strand. + // 23 - 26 Integer initSeqNum Sequence number of initial residue + // in strand. + // 27 AChar initICode Insertion code of initial residue + // in strand. + // 29 - 31 Residue name endResName Residue name of terminal residue. + // 33 Character endChainID Chain identifier of terminal residue. + // 34 - 37 Integer endSeqNum Sequence number of terminal residue. + // 38 AChar endICode Insertion code of terminal residue. + // 39 - 40 Integer sense Sense of strand with respect to previous + // strand in the sheet. 0 if first strand, + // 1 if parallel,and -1 if anti-parallel. + // 42 - 45 Atom curAtom Registration. Atom name in current strand. + // 46 - 48 Residue name curResName Registration. Residue name in current strand + // 50 Character curChainId Registration. Chain identifier in + // current strand. + // 51 - 54 Integer curResSeq Registration. Residue sequence number + // in current strand. + // 55 AChar curICode Registration. Insertion code in + // current strand. + // 57 - 60 Atom prevAtom Registration. Atom name in previous strand. + // 61 - 63 Residue name prevResName Registration. Residue name in + // previous strand. + // 65 Character prevChainId Registration. Chain identifier in + // previous strand. + // 66 - 69 Integer prevResSeq Registration. Residue sequence number + // in previous strand. + // 70 AChar prevICode Registration. Insertion code in + // previous strand. + sheets.push({ + strand: line.substr(7, 3).trim(), + sheetID: line.substr(11, 3).trim(), + numStrands: line.substr(14, 2).trim(), + initResName: line.substr(17, 3).trim(), + initChainID: line.substr(21, 1).trim(), + initSeqNum: line.substr(22, 4).trim(), + initICode: line.substr(26, 1).trim(), + endResName: line.substr(28, 3).trim(), + endChainID: line.substr(32, 1).trim(), + endSeqNum: line.substr(33, 4).trim(), + endICode: line.substr(37, 1).trim(), + sense: line.substr(38, 2).trim(), + curAtom: line.substr(41, 4).trim(), + curResName: line.substr(45, 3).trim(), + curChainId: line.substr(49, 1).trim(), + curResSeq: line.substr(50, 4).trim(), + curICode: line.substr(54, 1).trim(), + prevAtom: line.substr(56, 4).trim(), + prevResName: line.substr(60, 3).trim(), + prevChainId: line.substr(64, 1).trim(), + prevResSeq: line.substr(65, 4).trim(), + prevICode: line.substr(69, 1).trim(), + }) + } + + const beg_auth_asym_id = CifField.ofStrings(sheets.map(s => s.initChainID)) + const beg_auth_comp_id = CifField.ofStrings(sheets.map(s => s.initResName)) + const beg_auth_seq_id = CifField.ofStrings(sheets.map(s => s.initSeqNum)) + + const end_auth_asym_id = CifField.ofStrings(sheets.map(s => s.endChainID)) + const end_auth_comp_id = CifField.ofStrings(sheets.map(s => s.endResName)) + const end_auth_seq_id = CifField.ofStrings(sheets.map(s => s.endSeqNum)) + + const struct_sheet_range: CifCategory.Fields<mmCIF_Schema['struct_sheet_range']> = { + 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_auth_asym_id, + beg_auth_comp_id, + beg_auth_seq_id, + + 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_auth_asym_id, + end_auth_comp_id, + end_auth_seq_id, + + id: CifField.ofStrings(sheets.map(s => s.strand)), + sheet_id: CifField.ofStrings(sheets.map(s => s.sheetID)), // TODO wrong, needs to point to _struct_sheet.id + pdbx_beg_PDB_ins_code: CifField.ofStrings(sheets.map(s => s.initICode)), + pdbx_end_PDB_ins_code: CifField.ofStrings(sheets.map(s => s.endICode)), + }; + return CifCategory.ofFields('struct_sheet_range', struct_sheet_range); +} \ No newline at end of file diff --git a/src/mol-model-formats/structure/pdb/to-cif.ts b/src/mol-model-formats/structure/pdb/to-cif.ts new file mode 100644 index 0000000000000000000000000000000000000000..853a1b9319eb97121a5394a55e772dde2621608a --- /dev/null +++ b/src/mol-model-formats/structure/pdb/to-cif.ts @@ -0,0 +1,296 @@ +/** + * Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author David Sehnal <david.sehnal@gmail.com> + * @author Alexander Rose <alexander.rose@weirdbyte.de> + */ + +import { substringStartsWith } from 'mol-util/string'; +import { CifField, CifCategory, CifFrame } 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 { PdbFile } from 'mol-io/reader/pdb/schema'; +import { parseCryst1, parseRemark350, parseMtrix } from './assembly'; +import { WaterNames } from 'mol-model/structure/model/types'; +import { parseHelix, parseSheet } from './secondary-structure'; + +function _entity(): { [K in keyof mmCIF_Schema['entity']]?: CifField } { + return { + id: CifField.ofStrings(['1', '2', '3']), + type: CifField.ofStrings(['polymer', 'non-polymer', 'water']) + } +} + +type AtomSiteTemplate = typeof atom_site_template extends (...args: any) => infer T ? T : never +function atom_site_template(data: string, count: number) { + const str = () => [] as string[]; + const ts = () => TokenBuilder.create(data, 2 * count); + return { + index: 0, + count, + group_PDB: ts(), + id: str(), + auth_atom_id: ts(), + label_alt_id: ts(), + auth_comp_id: ts(), + auth_asym_id: ts(), + auth_seq_id: ts(), + pdbx_PDB_ins_code: ts(), + Cartn_x: ts(), + Cartn_y: ts(), + Cartn_z: ts(), + occupancy: ts(), + B_iso_or_equiv: ts(), + type_symbol: ts(), + pdbx_PDB_model_num: str(), + label_entity_id: str() + }; +} + +function _atom_site(sites: AtomSiteTemplate): { [K in keyof mmCIF_Schema['atom_site']]?: CifField } { + 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, + 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), + Cartn_z: CifField.ofTokens(sites.Cartn_z), + group_PDB: CifField.ofTokens(sites.group_PDB), + id: CifField.ofStrings(sites.id), + + label_alt_id: CifField.ofTokens(sites.label_alt_id), + + 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_entity_id: CifField.ofStrings(sites.label_entity_id), + + occupancy: CifField.ofTokens(sites.occupancy), + type_symbol: CifField.ofTokens(sites.type_symbol), + + pdbx_PDB_ins_code: CifField.ofTokens(sites.pdbx_PDB_ins_code), + pdbx_PDB_model_num: CifField.ofStrings(sites.pdbx_PDB_model_num) + }; +} + +function getEntityId(residueName: string, isHet: boolean) { + if (isHet) { + if (WaterNames.has(residueName)) return '3'; + return '2'; + } + return '1'; +} + +function addAtom(sites: AtomSiteTemplate, model: string, data: Tokenizer, s: number, e: number, isHet: boolean) { + const { data: str } = data; + const length = e - s; + + // TODO: filter invalid atoms + + // COLUMNS DATA TYPE CONTENTS + // -------------------------------------------------------------------------------- + // 1 - 6 Record name "ATOM " + TokenBuilder.addToken(sites.group_PDB, Tokenizer.trim(data, s, s + 6)); + + // 7 - 11 Integer Atom serial number. + // TODO: support HEX + Tokenizer.trim(data, s + 6, s + 11); + sites.id[sites.index] = data.data.substring(data.tokenStart, data.tokenEnd); + + // 13 - 16 Atom Atom name. + TokenBuilder.addToken(sites.auth_atom_id, Tokenizer.trim(data, s + 12, s + 16)); + + // 17 Character Alternate location indicator. + if (str.charCodeAt(s + 16) === 32) { // ' ' + TokenBuilder.add(sites.label_alt_id, 0, 0); + } else { + TokenBuilder.add(sites.label_alt_id, s + 16, s + 17); + } + + // 18 - 20 Residue name Residue name. + TokenBuilder.addToken(sites.auth_comp_id, Tokenizer.trim(data, s + 17, s + 20)); + const residueName = str.substring(data.tokenStart, data.tokenEnd); + + // 22 Character Chain identifier. + TokenBuilder.add(sites.auth_asym_id, s + 21, s + 22); + + // 23 - 26 Integer Residue sequence number. + // TODO: support HEX + TokenBuilder.addToken(sites.auth_seq_id, Tokenizer.trim(data, s + 22, s + 26)); + + // 27 AChar Code for insertion of residues. + if (str.charCodeAt(s + 26) === 32) { // ' ' + TokenBuilder.add(sites.label_alt_id, 0, 0); + } else { + TokenBuilder.add(sites.label_alt_id, s + 26, s + 27); + } + + // 31 - 38 Real(8.3) Orthogonal coordinates for X in Angstroms. + TokenBuilder.addToken(sites.Cartn_x, Tokenizer.trim(data, s + 30, s + 38)); + + // 39 - 46 Real(8.3) Orthogonal coordinates for Y in Angstroms. + TokenBuilder.addToken(sites.Cartn_y, Tokenizer.trim(data, s + 38, s + 46)); + + // 47 - 54 Real(8.3) Orthogonal coordinates for Z in Angstroms. + TokenBuilder.addToken(sites.Cartn_z, Tokenizer.trim(data, s + 46, s + 54)); + + // 55 - 60 Real(6.2) Occupancy. + TokenBuilder.addToken(sites.occupancy, Tokenizer.trim(data, s + 54, s + 60)); + + // 61 - 66 Real(6.2) Temperature factor (Default = 0.0). + if (length >= 66) { + TokenBuilder.addToken(sites.B_iso_or_equiv, Tokenizer.trim(data, s + 60, s + 66)); + } else { + TokenBuilder.add(sites.label_alt_id, 0, 0); + } + + // 73 - 76 LString(4) Segment identifier, left-justified. + // ignored + + // 77 - 78 LString(2) Element symbol, right-justified. + if (length >= 78) { + Tokenizer.trim(data, s + 76, s + 78); + + if (data.tokenStart < data.tokenEnd) { + TokenBuilder.addToken(sites.type_symbol, data); + } else { + // "guess" the symbol + TokenBuilder.add(sites.type_symbol, s + 12, s + 13); + } + } else { + TokenBuilder.add(sites.type_symbol, s + 12, s + 13); + } + + sites.label_entity_id[sites.index] = getEntityId(residueName, isHet); + sites.pdbx_PDB_model_num[sites.index] = model; + + sites.index++; +} + +export async function pdbToMmCif(pdb: PdbFile): Promise<CifFrame> { + const { lines } = pdb; + const { data, indices } = lines; + const tokenizer = Tokenizer(data); + + // Count the atoms + let atomCount = 0; + for (let i = 0, _i = lines.count; i < _i; i++) { + const s = indices[2 * i], e = indices[2 * i + 1]; + switch (data[s]) { + case 'A': + if (substringStartsWith(data, s, e, 'ATOM ')) atomCount++; + break; + case 'H': + if (substringStartsWith(data, s, e, 'HETATM')) atomCount++; + break; + } + } + + const atom_site = atom_site_template(data, atomCount); + + const helperCategories: CifCategory[] = []; + + let modelNum = 0, modelStr = ''; + + for (let i = 0, _i = lines.count; i < _i; i++) { + let s = indices[2 * i], e = indices[2 * i + 1]; + switch (data[s]) { + case 'A': + if (!substringStartsWith(data, s, e, 'ATOM ')) continue; + if (!modelNum) { modelNum++; modelStr = '' + modelNum; } + addAtom(atom_site, modelStr, tokenizer, s, e, false); + break; + case 'C': + if (substringStartsWith(data, s, e, 'CRYST1')) { + helperCategories.push(...parseCryst1(pdb.id || '?', data.substring(s, e))); + } + // TODO CONNECT records => struct_conn + // TODO COMPND records => entity + break; + case 'H': + if (substringStartsWith(data, s, e, 'HETATM')) { + if (!modelNum) { modelNum++; modelStr = '' + modelNum; } + addAtom(atom_site, modelStr, tokenizer, s, e, true); + } else if (substringStartsWith(data, s, e, 'HELIX')) { + let j = i + 1; + while (true) { + s = indices[2 * j]; e = indices[2 * j + 1]; + if (!substringStartsWith(data, s, e, 'HELIX')) break; + j++; + } + helperCategories.push(parseHelix(lines, i, j)); + i = j - 1; + } + // TODO HETNAM records => chem_comp (at least partially, needs to be completed with common bases and amino acids) + break; + case 'M': + if (substringStartsWith(data, s, e, 'MODEL ')) { + modelNum++; + modelStr = '' + modelNum; + } + if (substringStartsWith(data, s, e, 'MTRIX')) { + let j = i + 1; + while (true) { + s = indices[2 * j]; e = indices[2 * j + 1]; + if (!substringStartsWith(data, s, e, 'MTRIX')) break; + j++; + } + helperCategories.push(...parseMtrix(lines, i, j)); + i = j - 1; + } + // TODO MODRES records => pdbx_struct_mod_residue + break; + case 'O': + // TODO ORIGX record => cif.database_PDB_matrix.origx, cif.database_PDB_matrix.origx_vector + break; + case 'R': + if (substringStartsWith(data, s, e, 'REMARK 350')) { + let j = i + 1; + while (true) { + s = indices[2 * j]; e = indices[2 * j + 1]; + if (!substringStartsWith(data, s, e, 'REMARK 350')) break; + j++; + } + helperCategories.push(...parseRemark350(lines, i, j)); + i = j - 1; + } + break; + case 'S': + if (substringStartsWith(data, s, e, 'SHEET')) { + let j = i + 1; + while (true) { + s = indices[2 * j]; e = indices[2 * j + 1]; + if (!substringStartsWith(data, s, e, 'SHEET')) break; + j++; + } + helperCategories.push(parseSheet(lines, i, j)); + i = j - 1; + } + // TODO SCALE record => cif.atom_sites.fract_transf_matrix, cif.atom_sites.fract_transf_vector + break; + } + } + + const categories = { + entity: CifCategory.ofFields('entity', _entity()), + atom_site: CifCategory.ofFields('atom_site', _atom_site(atom_site)) + } as any; + + for (const c of helperCategories) { + categories[c.name] = c; + } + + return { + header: pdb.id || 'PDB', + categoryNames: Object.keys(categories), + categories + }; +} \ No newline at end of file diff --git a/src/mol-model/volume/formats/ccp4.ts b/src/mol-model-formats/volume/ccp4.ts similarity index 98% rename from src/mol-model/volume/formats/ccp4.ts rename to src/mol-model-formats/volume/ccp4.ts index 33b8087c80d2e4d9f7153d9800972a889161fd0f..5a6d9cfac971938ed881439ac32d9c1df00111e9 100644 --- a/src/mol-model/volume/formats/ccp4.ts +++ b/src/mol-model-formats/volume/ccp4.ts @@ -4,7 +4,7 @@ * @author Alexander Rose <alexander.rose@weirdbyte.de> */ -import { VolumeData } from '../data' +import { VolumeData } from 'mol-model/volume/data' import { Task } from 'mol-task'; import { SpacegroupCell, Box3D } from 'mol-math/geometry'; import { Tensor, Vec3 } from 'mol-math/linear-algebra'; diff --git a/src/mol-model/volume/formats/density-server.ts b/src/mol-model-formats/volume/density-server.ts similarity index 97% rename from src/mol-model/volume/formats/density-server.ts rename to src/mol-model-formats/volume/density-server.ts index 2f539d379377eb2cff132f132ca9a1cd7bb6be42..315bc33a6edbed6f54057da8595ac31e45cd7b54 100644 --- a/src/mol-model/volume/formats/density-server.ts +++ b/src/mol-model-formats/volume/density-server.ts @@ -5,7 +5,7 @@ */ import { DensityServer_Data_Database } from 'mol-io/reader/cif/schema/density-server' -import { VolumeData } from '../data' +import { VolumeData } from 'mol-model/volume/data' import { Task } from 'mol-task'; import { SpacegroupCell, Box3D } from 'mol-math/geometry'; import { Tensor, Vec3 } from 'mol-math/linear-algebra'; diff --git a/src/mol-model/volume/formats/dsn6.ts b/src/mol-model-formats/volume/dsn6.ts similarity index 97% rename from src/mol-model/volume/formats/dsn6.ts rename to src/mol-model-formats/volume/dsn6.ts index d945c19cfb03a6458fa57acd78138369c772c236..b256fe599afc421722d8943defc96fccc4fe4f4f 100644 --- a/src/mol-model/volume/formats/dsn6.ts +++ b/src/mol-model-formats/volume/dsn6.ts @@ -4,7 +4,7 @@ * @author Alexander Rose <alexander.rose@weirdbyte.de> */ -import { VolumeData } from '../data' +import { VolumeData } from 'mol-model/volume/data' import { Task } from 'mol-task'; import { SpacegroupCell, Box3D } from 'mol-math/geometry'; import { Tensor, Vec3 } from 'mol-math/linear-algebra'; diff --git a/src/mol-model-props/pdbe/themes/structure-quality-report.ts b/src/mol-model-props/pdbe/themes/structure-quality-report.ts index a527e58f7797addf3ad4b2a3de9109c5179f330b..928298bd293b60de2d5673ae174134a9dca1d75a 100644 --- a/src/mol-model-props/pdbe/themes/structure-quality-report.ts +++ b/src/mol-model-props/pdbe/themes/structure-quality-report.ts @@ -31,7 +31,7 @@ const ValidationColorTable: [string, Color][] = [ export function StructureQualityReportColorTheme(ctx: ThemeDataContext, props: {}): ColorTheme<{}> { let color: LocationColor - if (ctx.structure && ctx.structure.models[0].customProperties.has(StructureQualityReport.Descriptor)) { + if (ctx.structure && !ctx.structure.isEmpty && ctx.structure.models[0].customProperties.has(StructureQualityReport.Descriptor)) { const getIssues = StructureQualityReport.getIssues; color = (location: Location) => { if (StructureElement.isLocation(location)) { diff --git a/src/mol-model-props/rcsb/themes/assembly-symmetry-cluster.ts b/src/mol-model-props/rcsb/themes/assembly-symmetry-cluster.ts index efe1bb6fba7c3b5ab1453c7080ec3cb4c2f036b7..434187445d789c4f3419c363e4e936b849a2b2b1 100644 --- a/src/mol-model-props/rcsb/themes/assembly-symmetry-cluster.ts +++ b/src/mol-model-props/rcsb/themes/assembly-symmetry-cluster.ts @@ -47,7 +47,7 @@ export function AssemblySymmetryClusterColorTheme(ctx: ThemeDataContext, props: const { symmetryId } = props - if (ctx.structure && ctx.structure.models[0].customProperties.has(AssemblySymmetry.Descriptor)) { + if (ctx.structure && !ctx.structure.isEmpty && ctx.structure.models[0].customProperties.has(AssemblySymmetry.Descriptor)) { const assemblySymmetry = AssemblySymmetry.get(ctx.structure.models[0])! const s = assemblySymmetry.db.rcsb_assembly_symmetry @@ -99,5 +99,5 @@ export const AssemblySymmetryClusterColorThemeProvider: ColorTheme.Provider<Asse factory: AssemblySymmetryClusterColorTheme, getParams: getAssemblySymmetryClusterColorThemeParams, defaultValues: PD.getDefaultValues(AssemblySymmetryClusterColorThemeParams), - isApplicable: (ctx: ThemeDataContext) => !!ctx.structure && ctx.structure.models[0].customProperties.has(AssemblySymmetry.Descriptor) + isApplicable: (ctx: ThemeDataContext) => !!ctx.structure && !ctx.structure.isEmpty && ctx.structure.models[0].customProperties.has(AssemblySymmetry.Descriptor) } \ No newline at end of file diff --git a/src/mol-model/structure/export/categories/utils.ts b/src/mol-model/structure/export/categories/utils.ts index 17869c5f15547791e4fc967ef6aff51877382b17..4b8acb1e8882de25959bf6410ecb85f8e2df67d5 100644 --- a/src/mol-model/structure/export/categories/utils.ts +++ b/src/mol-model/structure/export/categories/utils.ts @@ -20,7 +20,7 @@ export function getModelMmCifCategory<K extends keyof mmCIF_Schema>(model: Model } export function getUniqueResidueNamesFromStructures(structures: Structure[]) { - return SetUtils.unionMany(structures.map(s => s.uniqueResidueNames)); + return SetUtils.unionMany(...structures.map(s => s.uniqueResidueNames)); } export function getUniqueEntityIdsFromStructures(structures: Structure[]): Set<string> { diff --git a/src/mol-model/structure/model.ts b/src/mol-model/structure/model.ts index 05dec32e216ca63c83ad5f5e80767f027fef847b..df9e98d18fa289780ace42f1326250fd232ce92e 100644 --- a/src/mol-model/structure/model.ts +++ b/src/mol-model/structure/model.ts @@ -6,10 +6,9 @@ import { Model } from './model/model' import * as Types from './model/types' -import Format from './model/format' import { ModelSymmetry } from './model/properties/symmetry' import StructureSequence from './model/properties/sequence' export * from './model/properties/custom' export * from './model/indexing' -export { Model, Types, Format, ModelSymmetry, StructureSequence } \ No newline at end of file +export { Model, Types, ModelSymmetry, StructureSequence } \ No newline at end of file diff --git a/src/mol-model/structure/model/format.ts b/src/mol-model/structure/model/format.ts deleted file mode 100644 index d2053f170306e4e704fc6399dda1f0914a276b8e..0000000000000000000000000000000000000000 --- a/src/mol-model/structure/model/format.ts +++ /dev/null @@ -1,24 +0,0 @@ -/** - * Copyright (c) 2017 mol* contributors, licensed under MIT, See LICENSE file for more info. - * - * @author David Sehnal <david.sehnal@gmail.com> - */ - -// import { File as GroFile } from 'mol-io/reader/gro/schema' -import { mmCIF_Database } from 'mol-io/reader/cif/schema/mmcif' -import CIF, { CifFrame } from 'mol-io/reader/cif'; - -type Format = - // | Format.gro - | Format.mmCIF - -namespace Format { - // export interface gro { kind: 'gro', data: GroFile } - export interface mmCIF { kind: 'mmCIF', data: mmCIF_Database, frame: CifFrame } - - export function mmCIF(frame: CifFrame, data?: mmCIF_Database): mmCIF { - return { kind: 'mmCIF', data: data || CIF.schema.mmCIF(frame), frame }; - } -} - -export default Format \ No newline at end of file diff --git a/src/mol-model/structure/model/model.ts b/src/mol-model/structure/model/model.ts index a9442fc948c6ffe8e1de0f19236ec750760e8a68..c777d8d55fe61d75afc5b24f07e58dbfe0b4748e 100644 --- a/src/mol-model/structure/model/model.ts +++ b/src/mol-model/structure/model/model.ts @@ -4,19 +4,17 @@ * @author David Sehnal <david.sehnal@gmail.com> */ -import UUID from 'mol-util/uuid' -import Format from './format' -import StructureSequence from './properties/sequence' -import { AtomicHierarchy, AtomicConformation } from './properties/atomic' -import { ModelSymmetry } from './properties/symmetry' -import { CoarseHierarchy, CoarseConformation } from './properties/coarse' +import UUID from 'mol-util/uuid'; +import StructureSequence from './properties/sequence'; +import { AtomicHierarchy, AtomicConformation } from './properties/atomic'; +import { ModelSymmetry } from './properties/symmetry'; +import { CoarseHierarchy, CoarseConformation } from './properties/coarse'; import { Entities } from './properties/common'; import { CustomProperties } from './properties/custom'; import { SecondaryStructure } from './properties/seconday-structure'; - -import from_mmCIF from './formats/mmcif' -import { ChemicalComponentMap } from './properties/chemical-component'; import { SaccharideComponentMap } from '../structure/carbohydrates/constants'; +import { ModelFormat } from 'mol-model-formats/structure/format'; +import { ChemicalComponentMap } from './properties/chemical-component'; /** * Interface to the "source data" of the molecule. @@ -30,7 +28,7 @@ export interface Model extends Readonly<{ // for IHM, corresponds to ihm_model_list.model_id modelNum: number, - sourceData: Format, + sourceData: ModelFormat, symmetry: ModelSymmetry, entities: Entities, @@ -69,10 +67,6 @@ export interface Model extends Readonly<{ } { } export namespace Model { - export function create(format: Format) { - switch (format.kind) { - // case 'gro': return from_gro(format); - case 'mmCIF': return from_mmCIF(format); - } - } + // TODO: is this enough? + export type Trajectory = ReadonlyArray<Model> } \ No newline at end of file diff --git a/src/mol-model/structure/model/properties/chemical-component.ts b/src/mol-model/structure/model/properties/chemical-component.ts index 35d34e571790872c8e1a110bc125705d7a4b49c4..15efa28fed123964a6f47efc6555f8343f6514c8 100644 --- a/src/mol-model/structure/model/properties/chemical-component.ts +++ b/src/mol-model/structure/model/properties/chemical-component.ts @@ -4,16 +4,10 @@ * @author Alexander Rose <alexander.rose@weirdbyte.de> */ -import { MoleculeType, ComponentType } from '../types' +import { mmCIF_Schema } from 'mol-io/reader/cif/schema/mmcif'; +import { Table } from 'mol-data/db'; -export interface ChemicalComponent { - id: string - type: ComponentType - moleculeType: MoleculeType - name: string - synonyms: string[] - formula: string - formulaWeight: number -} +export type ChemicalComponent = Table.Row<mmCIF_Schema['chem_comp']> +export type ChemicalComponentMap = ReadonlyMap<string, ChemicalComponent> -export type ChemicalComponentMap = ReadonlyMap<string, ChemicalComponent> \ No newline at end of file +// TODO add data for common chemical components \ No newline at end of file diff --git a/src/mol-model/structure/model/properties/utils/atomic-derived.ts b/src/mol-model/structure/model/properties/utils/atomic-derived.ts index c998a2513010e4ea9315d3202f148d33b982dfce..f2e1833042eff059d102d7d25dd1d860eac8bce7 100644 --- a/src/mol-model/structure/model/properties/utils/atomic-derived.ts +++ b/src/mol-model/structure/model/properties/utils/atomic-derived.ts @@ -1,5 +1,5 @@ /** - * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info. + * Copyright (c) 2018-2019 mol* contributors, licensed under MIT, See LICENSE file for more info. * * @author Alexander Rose <alexander.rose@weirdbyte.de> */ @@ -8,22 +8,32 @@ import { AtomicData } from '../atomic'; import { ChemicalComponentMap } from '../chemical-component'; import { AtomicIndex, AtomicDerivedData } from '../atomic/hierarchy'; import { ElementIndex, ResidueIndex } from '../../indexing'; -import { MoleculeType } from '../../types'; +import { MoleculeType, getMoleculeType, getComponentType } from '../../types'; import { getAtomIdForAtomRole } from 'mol-model/structure/util'; export function getAtomicDerivedData(data: AtomicData, index: AtomicIndex, chemicalComponentMap: ChemicalComponentMap): AtomicDerivedData { - const { label_comp_id, _rowCount: n } = data.residues const traceElementIndex = new Uint32Array(n) const directionElementIndex = new Uint32Array(n) const moleculeType = new Uint8Array(n) + const moleculeTypeMap = new Map<string, MoleculeType>() + for (let i = 0; i < n; ++i) { const compId = label_comp_id.value(i) const chemCompMap = chemicalComponentMap - const cc = chemCompMap.get(compId) - const molType = cc ? cc.moleculeType : MoleculeType.unknown + let molType: MoleculeType + if (moleculeTypeMap.has(compId)){ + molType = moleculeTypeMap.get(compId)! + } else if (chemCompMap.has(compId)) { + molType = getMoleculeType(chemCompMap.get(compId)!.type, compId) + moleculeTypeMap.set(compId, molType) + } else { + molType = getMoleculeType(getComponentType(compId), compId) + // TODO if unknown molecule type, use atom names to guess molecule type + moleculeTypeMap.set(compId, molType) + } moleculeType[i] = molType const traceAtomId = getAtomIdForAtomRole(molType, 'trace') @@ -33,7 +43,6 @@ export function getAtomicDerivedData(data: AtomicData, index: AtomicIndex, chemi directionElementIndex[i] = index.findAtomOnResidue(i as ResidueIndex, directionAtomId) } - return { residue: { traceElementIndex: traceElementIndex as unknown as ArrayLike<ElementIndex>, diff --git a/src/mol-model/structure/model/properties/utils/atomic-ranges.ts b/src/mol-model/structure/model/properties/utils/atomic-ranges.ts index 7853fb37776f4f9abc625c332e08b55883357a20..ed6816364115f26aa64dbf6a981bf35b3a8c751d 100644 --- a/src/mol-model/structure/model/properties/utils/atomic-ranges.ts +++ b/src/mol-model/structure/model/properties/utils/atomic-ranges.ts @@ -8,7 +8,6 @@ import { AtomicSegments } from '../atomic'; import { AtomicData, AtomicRanges } from '../atomic/hierarchy'; import { Segmentation, Interval } from 'mol-data/int'; import SortedRanges from 'mol-data/int/sorted-ranges'; -import { ChemicalComponentMap } from '../chemical-component'; import { MoleculeType, isPolymer } from '../../types'; import { ElementIndex, ResidueIndex } from '../../indexing'; import { getAtomIdForAtomRole } from '../../../util'; @@ -17,11 +16,6 @@ import { Vec3 } from 'mol-math/linear-algebra'; // TODO add gaps at the ends of the chains by comparing to the polymer sequence data -function getMoleculeType(compId: string, chemicalComponentMap: ChemicalComponentMap) { - const cc = chemicalComponentMap.get(compId) - return cc ? cc.moleculeType : MoleculeType.unknown -} - function getElementIndexForAtomId(rI: ResidueIndex, atomId: string, data: AtomicData, segments: AtomicSegments): ElementIndex { const { offsets } = segments.residueAtomSegments const { label_atom_id } = data.atoms @@ -31,10 +25,9 @@ function getElementIndexForAtomId(rI: ResidueIndex, atomId: string, data: Atomic return offsets[rI] } -function areBackboneConnected(riStart: ResidueIndex, riEnd: ResidueIndex, data: AtomicData, segments: AtomicSegments, conformation: AtomicConformation, chemicalComponentMap: ChemicalComponentMap) { - const { label_comp_id } = data.residues - const mtStart = getMoleculeType(label_comp_id.value(riStart), chemicalComponentMap) - const mtEnd = getMoleculeType(label_comp_id.value(riEnd), chemicalComponentMap) +function areBackboneConnected(riStart: ResidueIndex, riEnd: ResidueIndex, data: AtomicData, segments: AtomicSegments, conformation: AtomicConformation, moleculeType: ArrayLike<MoleculeType>) { + const mtStart = moleculeType[riStart] + const mtEnd = moleculeType[riEnd] if (!isPolymer(mtStart) || !isPolymer(mtEnd)) return false const startId = getAtomIdForAtomRole(mtStart, 'backboneStart') @@ -49,13 +42,13 @@ function areBackboneConnected(riStart: ResidueIndex, riEnd: ResidueIndex, data: return Vec3.distance(pStart, pEnd) < 10 } -export function getAtomicRanges(data: AtomicData, segments: AtomicSegments, conformation: AtomicConformation, chemicalComponentMap: ChemicalComponentMap): AtomicRanges { +export function getAtomicRanges(data: AtomicData, segments: AtomicSegments, conformation: AtomicConformation, moleculeType: ArrayLike<MoleculeType>): AtomicRanges { const polymerRanges: number[] = [] const gapRanges: number[] = [] const cyclicPolymerMap = new Map<ResidueIndex, ResidueIndex>() const chainIt = Segmentation.transientSegments(segments.chainAtomSegments, Interval.ofBounds(0, data.atoms._rowCount)) const residueIt = Segmentation.transientSegments(segments.residueAtomSegments, Interval.ofBounds(0, data.atoms._rowCount)) - const { label_seq_id, label_comp_id } = data.residues + const { label_seq_id } = data.residues let prevSeqId: number let prevStart: number @@ -72,7 +65,7 @@ export function getAtomicRanges(data: AtomicData, segments: AtomicSegments, conf const riStart = segments.residueAtomSegments.index[chainSegment.start] const riEnd = segments.residueAtomSegments.index[chainSegment.end - 1] - if (areBackboneConnected(riStart, riEnd, data, segments, conformation, chemicalComponentMap)) { + if (areBackboneConnected(riStart, riEnd, data, segments, conformation, moleculeType)) { cyclicPolymerMap.set(riStart, riEnd) cyclicPolymerMap.set(riEnd, riStart) } @@ -80,9 +73,8 @@ export function getAtomicRanges(data: AtomicData, segments: AtomicSegments, conf while (residueIt.hasNext) { const residueSegment = residueIt.move(); const residueIndex = residueSegment.index - const moleculeType = getMoleculeType(label_comp_id.value(residueIndex), chemicalComponentMap) const seqId = label_seq_id.value(residueIndex) - if (isPolymer(moleculeType)) { + if (isPolymer(moleculeType[residueIndex])) { if (startIndex !== -1) { if (seqId !== prevSeqId + 1) { polymerRanges.push(startIndex, prevEnd - 1) @@ -93,7 +85,7 @@ export function getAtomicRanges(data: AtomicData, segments: AtomicSegments, conf } else { const riStart = segments.residueAtomSegments.index[residueSegment.start] const riEnd = segments.residueAtomSegments.index[prevEnd - 1] - if (!areBackboneConnected(riStart, riEnd, data, segments, conformation, chemicalComponentMap)) { + if (!areBackboneConnected(riStart, riEnd, data, segments, conformation, moleculeType)) { polymerRanges.push(startIndex, prevEnd - 1) startIndex = residueSegment.start } diff --git a/src/mol-model/structure/model/types.ts b/src/mol-model/structure/model/types.ts index 4f2b7d525341c3162d4ae59d58e6a2d1964604e2..8d8be296dbf079b0a1e8aa3333d793afc7797940 100644 --- a/src/mol-model/structure/model/types.ts +++ b/src/mol-model/structure/model/types.ts @@ -1,11 +1,14 @@ /** - * Copyright (c) 2017-2018 mol* contributors, licensed under MIT, See LICENSE file for more info. + * Copyright (c) 2017-2019 mol* contributors, licensed under MIT, See LICENSE file for more info. * * @author Alexander Rose <alexander.rose@weirdbyte.de> * @author David Sehnal <david.sehnal@gmail.com> */ import BitFlags from 'mol-util/bit-flags' +import { SaccharideCompIdMap } from '../structure/carbohydrates/constants'; +import { mmCIF_Schema } from 'mol-io/reader/cif/schema/mmcif'; +import { SetUtils } from 'mol-util/set'; const _esCache = (function () { const cache = Object.create(null); @@ -85,51 +88,28 @@ export const MoleculeTypeAtomRoleId: { [k: number]: { [k in AtomRole]: string } } } -export const ProteinBackboneAtoms = [ +export const ProteinBackboneAtoms = new Set([ 'CA', 'C', 'N', 'O', 'O1', 'O2', 'OC1', 'OC2', 'OX1', 'OXT', 'H', 'H1', 'H2', 'H3', 'HA', 'HN', 'BB' -] +]) -export const NucleicBackboneAtoms = [ +export const NucleicBackboneAtoms = new Set([ 'P', 'OP1', 'OP2', 'HOP2', 'HOP3', 'O2\'', 'O3\'', 'O4\'', 'O5\'', 'C1\'', 'C2\'', 'C3\'', 'C4\'', 'C5\'', 'H1\'', 'H2\'', 'H2\'\'', 'HO2\'', 'H3\'', 'H4\'', 'H5\'', 'H5\'\'', 'HO3\'', 'HO5\'', 'O2*', 'O3*', 'O4*', 'O5*', 'C1*', 'C2*', 'C3*', 'C4*', 'C5*' -] - -/** Chemical component types as defined in the mmCIF CCD */ -export enum ComponentType { - // protein - 'D-peptide linking', 'L-peptide linking', 'D-peptide NH3 amino terminus', - 'L-peptide NH3 amino terminus', 'D-peptide COOH carboxy terminus', - 'L-peptide COOH carboxy terminus', 'peptide linking', 'peptide-like', - 'L-gamma-peptide, C-delta linking', 'D-gamma-peptide, C-delta linking', - 'L-beta-peptide, C-gamma linking', 'D-beta-peptide, C-gamma linking', - - // DNA - 'DNA linking', 'L-DNA linking', 'DNA OH 5 prime terminus', 'DNA OH 3 prime terminus', - - // RNA - 'RNA linking', 'L-RNA linking', 'RNA OH 5 prime terminus', 'RNA OH 3 prime terminus', - - // sacharide - 'D-saccharide 1,4 and 1,4 linking', 'L-saccharide 1,4 and 1,4 linking', - 'D-saccharide 1,4 and 1,6 linking', 'L-saccharide 1,4 and 1,6 linking', 'L-saccharide', - 'D-saccharide', 'saccharide', - - 'non-polymer', 'other' -} +]) /** Chemical component type names for protein */ -export const ProteinComponentTypeNames = [ +export const ProteinComponentTypeNames = new Set([ 'D-PEPTIDE LINKING', 'L-PEPTIDE LINKING', 'D-PEPTIDE NH3 AMINO TERMINUS', 'L-PEPTIDE NH3 AMINO TERMINUS', 'D-PEPTIDE COOH CARBOXY TERMINUS', 'L-PEPTIDE COOH CARBOXY TERMINUS', 'PEPTIDE LINKING', 'PEPTIDE-LIKE', 'L-GAMMA-PEPTIDE, C-DELTA LINKING', 'D-GAMMA-PEPTIDE, C-DELTA LINKING', 'L-BETA-PEPTIDE, C-GAMMA LINKING', 'D-BETA-PEPTIDE, C-GAMMA LINKING', -] +]) /** Chemical component type names for DNA */ export const DNAComponentTypeNames = [ @@ -137,66 +117,104 @@ export const DNAComponentTypeNames = [ ] /** Chemical component type names for RNA */ -export const RNAComponentTypeNames = [ +export const RNAComponentTypeNames = new Set([ 'RNA LINKING', 'L-RNA LINKING', 'RNA OH 5 PRIME TERMINUS', 'RNA OH 3 PRIME TERMINUS', -] +]) /** Chemical component type names for saccharide */ -export const SaccharideComponentTypeNames = [ +export const SaccharideComponentTypeNames = new Set([ 'D-SACCHARIDE 1,4 AND 1,4 LINKING', 'L-SACCHARIDE 1,4 AND 1,4 LINKING', 'D-SACCHARIDE 1,4 AND 1,6 LINKING', 'L-SACCHARIDE 1,4 AND 1,6 LINKING', 'L-SACCHARIDE', 'D-SACCHARIDE', 'SACCHARIDE', -] +]) /** Chemical component type names for other */ -export const OtherComponentTypeNames = [ +export const OtherComponentTypeNames = new Set([ 'NON-POLYMER', 'OTHER' -] +]) /** Common names for water molecules */ -export const WaterNames = [ +export const WaterNames = new Set([ 'SOL', 'WAT', 'HOH', 'H2O', 'W', 'DOD', 'D3O', 'TIP3', 'TIP4', 'SPC' -] - -export const ExtraSaccharideNames = [ - 'MLR' -] - -export const RnaBaseNames = [ 'A', 'C', 'T', 'G', 'I', 'U' ] -export const DnaBaseNames = [ 'DA', 'DC', 'DT', 'DG', 'DI', 'DU' ] -export const PeptideBaseNames = [ 'APN', 'CPN', 'TPN', 'GPN' ] -export const PurinBaseNames = [ 'A', 'G', 'DA', 'DG', 'DI', 'APN', 'GPN' ] -export const PyrimidineBaseNames = [ 'C', 'T', 'U', 'DC', 'DT', 'DU', 'CPN', 'TPN' ] -export const BaseNames = RnaBaseNames.concat(DnaBaseNames, PeptideBaseNames) - -export const isPurinBase = (compId: string) => PurinBaseNames.includes(compId.toUpperCase()) -export const isPyrimidineBase = (compId: string) => PyrimidineBaseNames.includes(compId.toUpperCase()) +]) + +export const AminoAcidNames = new Set([ + 'HIS', 'ARG', 'LYS', 'ILE', 'PHE', 'LEU', 'TRP', 'ALA', 'MET', 'PRO', 'CYS', + 'ASN', 'VAL', 'GLY', 'SER', 'GLN', 'TYR', 'ASP', 'GLU', 'THR', 'SEC', 'PYL', + + 'DAL', // D-ALANINE + 'DAR', // D-ARGININE + 'DSG', // D-ASPARAGINE + 'DAS', // D-ASPARTIC ACID + 'DCY', // D-CYSTEINE + 'DGL', // D-GLUTAMIC ACID + 'DGN', // D-GLUTAMINE + 'DHI', // D-HISTIDINE + 'DIL', // D-ISOLEUCINE + 'DLE', // D-LEUCINE + 'DLY', // D-LYSINE + 'MED', // D-METHIONINE + 'DPN', // D-PHENYLALANINE + 'DPR', // D-PROLINE + 'DSN', // D-SERINE + 'DTH', // D-THREONINE + 'DTR', // D-TRYPTOPHAN + 'DTY', // D-TYROSINE + 'DVA', // D-VALINE + 'DNE' // D-NORLEUCINE + // ??? // D-SELENOCYSTEINE +]) + +export const RnaBaseNames = new Set([ 'A', 'C', 'T', 'G', 'I', 'U' ]) +export const DnaBaseNames = new Set([ 'DA', 'DC', 'DT', 'DG', 'DI', 'DU' ]) +export const PeptideBaseNames = new Set([ 'APN', 'CPN', 'TPN', 'GPN' ]) +export const PurinBaseNames = new Set([ 'A', 'G', 'DA', 'DG', 'DI', 'APN', 'GPN' ]) +export const PyrimidineBaseNames = new Set([ 'C', 'T', 'U', 'DC', 'DT', 'DU', 'CPN', 'TPN' ]) +export const BaseNames = SetUtils.unionMany(RnaBaseNames, DnaBaseNames, PeptideBaseNames) + +export const isPurinBase = (compId: string) => PurinBaseNames.has(compId.toUpperCase()) +export const isPyrimidineBase = (compId: string) => PyrimidineBaseNames.has(compId.toUpperCase()) /** get the molecule type from component type and id */ export function getMoleculeType(compType: string, compId: string) { compType = compType.toUpperCase() compId = compId.toUpperCase() - if (PeptideBaseNames.includes(compId)) { + if (PeptideBaseNames.has(compId)) { return MoleculeType.PNA - } else if (ProteinComponentTypeNames.includes(compType)) { + } else if (ProteinComponentTypeNames.has(compType)) { return MoleculeType.protein - } else if (RNAComponentTypeNames.includes(compType)) { + } else if (RNAComponentTypeNames.has(compType)) { return MoleculeType.RNA } else if (DNAComponentTypeNames.includes(compType)) { return MoleculeType.DNA - } else if (SaccharideComponentTypeNames.includes(compType) || ExtraSaccharideNames.includes(compId)) { + } else if (SaccharideComponentTypeNames.has(compType)) { return MoleculeType.saccharide - } else if (WaterNames.includes(compId)) { + } else if (WaterNames.has(compId)) { return MoleculeType.water - } else if (IonNames.includes(compId)) { + } else if (IonNames.has(compId)) { return MoleculeType.ion - } else if (OtherComponentTypeNames.includes(compType)) { + } else if (OtherComponentTypeNames.has(compType)) { return MoleculeType.other } else { return MoleculeType.unknown } } +export function getComponentType(compId: string): mmCIF_Schema['chem_comp']['type']['T'] { + compId = compId.toUpperCase() + if (AminoAcidNames.has(compId)) { + return 'peptide linking' + } else if (RnaBaseNames.has(compId)) { + return 'RNA linking' + } else if (DnaBaseNames.has(compId)) { + return 'DNA linking' + } else if (SaccharideCompIdMap.has(compId)) { + return 'saccharide' + } else { + return 'other' + } +} + export function isPolymer(moleculeType: MoleculeType) { return moleculeType === MoleculeType.protein || moleculeType === MoleculeType.DNA || moleculeType === MoleculeType.RNA || moleculeType === MoleculeType.PNA } @@ -206,6 +224,8 @@ export function isNucleic(moleculeType: MoleculeType) { } /** + * TODO write script that read CCD and outputs list of ion names + * * all chemical components with the word "ion" in their name, Sep 2016 * * SET SESSION group_concat_max_len = 1000000; @@ -216,7 +236,7 @@ export function isNucleic(moleculeType: MoleculeType) { * GROUP BY id_ * ) AS t1; */ -export const IonNames = [ +export const IonNames = new Set([ '118', '119', '1AL', '1CU', '2FK', '2HP', '2OF', '3CO', '3MT', '3NI', '3OF', '3P8', '4MO', '4PU', '543', '6MO', 'ACT', 'AG', 'AL', 'ALF', 'AM', 'ATH', 'AU', 'AU3', 'AUC', 'AZI', 'BA', 'BCT', 'BEF', 'BF4', 'BO4', @@ -237,7 +257,7 @@ export const IonNames = [ 'YB2', 'YH', 'YT3', 'ZCM', 'ZN', 'ZN2', 'ZN3', 'ZNO', 'ZO3', // additional ion names 'OHX' -] +]) export interface SecondaryStructureType extends BitFlags<SecondaryStructureType.Flag> { } export namespace SecondaryStructureType { diff --git a/src/mol-model/structure/structure/structure.ts b/src/mol-model/structure/structure/structure.ts index d7b99e9ff018db30162d37aae9a615c2892c2e20..b1c520601104772ffeba27c674deae65e2325d88 100644 --- a/src/mol-model/structure/structure/structure.ts +++ b/src/mol-model/structure/structure/structure.ts @@ -72,6 +72,10 @@ class Structure { return prc && ec ? ec / prc < 2 : false } + get isEmpty() { + return this.units.length === 0; + } + get hashCode() { if (this._props.hashCode !== -1) return this._props.hashCode; return this.computeHash(); diff --git a/src/mol-model/structure/structure/symmetry.ts b/src/mol-model/structure/structure/symmetry.ts index 5af14696a9e1b5017299ace7cbe5643956a2bc1f..162d3151cb070c1fd49261410f31827fe8bf4025 100644 --- a/src/mol-model/structure/structure/symmetry.ts +++ b/src/mol-model/structure/structure/symmetry.ts @@ -1,19 +1,19 @@ /** - * Copyright (c) 2017-2018 mol* contributors, licensed under MIT, See LICENSE file for more info. + * Copyright (c) 2017-2019 mol* contributors, licensed under MIT, See LICENSE file for more info. * * @author David Sehnal <david.sehnal@gmail.com> * @author Alexander Rose <alexander.rose@weirdbyte.de> */ -import Structure from './structure' -import { StructureSelection, QueryContext } from '../query' -import { ModelSymmetry } from '../model' -import { Task, RuntimeContext } from 'mol-task'; import { SortedArray } from 'mol-data/int'; -import Unit from './unit'; import { EquivalenceClasses } from 'mol-data/util'; +import { Spacegroup, SpacegroupCell, SymmetryOperator } from 'mol-math/geometry'; import { Vec3 } from 'mol-math/linear-algebra'; -import { SymmetryOperator, Spacegroup, SpacegroupCell } from 'mol-math/geometry'; +import { RuntimeContext, Task } from 'mol-task'; +import { ModelSymmetry } from '../model'; +import { QueryContext, StructureSelection } from '../query'; +import Structure from './structure'; +import Unit from './unit'; namespace StructureSymmetry { export function buildAssembly(structure: Structure, asmName: string) { @@ -90,26 +90,34 @@ namespace StructureSymmetry { } function getOperators(symmetry: ModelSymmetry, ijkMin: Vec3, ijkMax: Vec3) { - const operators: SymmetryOperator[] = symmetry._operators_333 || []; + const operators: SymmetryOperator[] = []; const { spacegroup } = symmetry; - if (operators.length === 0) { + if (ijkMin[0] <= 0 && ijkMax[0] >= 0 && + ijkMin[1] <= 0 && ijkMax[1] >= 0 && + ijkMin[2] <= 0 && ijkMax[2] >= 0) { operators[0] = Spacegroup.getSymmetryOperator(spacegroup, 0, 0, 0, 0) - for (let op = 0; op < spacegroup.operators.length; op++) { - for (let i = ijkMin[0]; i < ijkMax[0]; i++) { - for (let j = ijkMin[1]; j < ijkMax[1]; j++) { - for (let k = ijkMin[2]; k < ijkMax[2]; k++) { - // we have added identity as the 1st operator. - if (op === 0 && i === 0 && j === 0 && k === 0) continue; - operators[operators.length] = Spacegroup.getSymmetryOperator(spacegroup, op, i, j, k); - } + } + + for (let op = 0; op < spacegroup.operators.length; op++) { + for (let i = ijkMin[0]; i <= ijkMax[0]; i++) { + for (let j = ijkMin[1]; j <= ijkMax[1]; j++) { + for (let k = ijkMin[2]; k <= ijkMax[2]; k++) { + // we have added identity as the 1st operator. + if (op === 0 && i === 0 && j === 0 && k === 0) continue; + operators[operators.length] = Spacegroup.getSymmetryOperator(spacegroup, op, i, j, k); } } } - symmetry._operators_333 = operators; } return operators; } +function getOperatorsCached333(symmetry: ModelSymmetry) { + if (typeof symmetry._operators_333 !== 'undefined') return symmetry._operators_333; + symmetry._operators_333 = getOperators(symmetry, Vec3.create(-3, -3, -3), Vec3.create(3, 3, 3)); + return symmetry._operators_333; +} + function assembleOperators(structure: Structure, operators: ReadonlyArray<SymmetryOperator>) { const assembler = Structure.Builder(); const { units } = structure; @@ -150,7 +158,7 @@ async function findMatesRadius(ctx: RuntimeContext, structure: Structure, radius if (SpacegroupCell.isZero(spacegroup.cell)) return structure; if (ctx.shouldUpdate) await ctx.update('Initialing...'); - const operators = getOperators(symmetry, Vec3.create(-3, -3, -3), Vec3.create(3, 3, 3)); + const operators = getOperatorsCached333(symmetry); const lookup = structure.lookup3d; const assembler = Structure.Builder(); diff --git a/src/mol-model/structure/structure/unit/links/data.ts b/src/mol-model/structure/structure/unit/links/data.ts index 00559aec5a5cb63269a7ef784ccc5b7bb8d145d0..d8f40a7ec87e3328bb1adc1d763928b08c79bba2 100644 --- a/src/mol-model/structure/structure/unit/links/data.ts +++ b/src/mol-model/structure/structure/unit/links/data.ts @@ -66,10 +66,10 @@ class InterUnitBonds { pairBonds.linkedElementIndices.forEach(indexA => { pairBonds.getBonds(indexA).forEach(bondInfo => { const { unitA, unitB } = pairBonds - + const bondKey = InterUnitBonds.getBondKey(indexA, unitA, bondInfo.indexB, unitB) bondKeyIndex.set(bondKey, bonds.length) - + const elementKey = InterUnitBonds.getElementKey(indexA, unitA) const e = elementKeyIndex.get(elementKey) if (e === undefined) elementKeyIndex.set(elementKey, [bonds.length]) diff --git a/src/mol-model/structure/structure/unit/links/inter-compute.ts b/src/mol-model/structure/structure/unit/links/inter-compute.ts index f4f0143449d11a83c936185027e17e9b068ad0f5..ac094daa8c3f143658e3783378f50e1ab2be1487 100644 --- a/src/mol-model/structure/structure/unit/links/inter-compute.ts +++ b/src/mol-model/structure/structure/unit/links/inter-compute.ts @@ -4,7 +4,6 @@ * @author David Sehnal <david.sehnal@gmail.com> */ -import { StructConn } from '../../../model/formats/mmcif/bonds'; import { LinkType } from '../../../model/types'; import Structure from '../../structure'; import Unit from '../../unit'; @@ -14,6 +13,7 @@ import { UniqueArray } from 'mol-data/generic'; import { SortedArray } from 'mol-data/int'; import { Vec3, Mat4 } from 'mol-math/linear-algebra'; import StructureElement from '../../element'; +import { StructConn } from 'mol-model-formats/structure/mmcif/bonds'; const MAX_RADIUS = 4; diff --git a/src/mol-model/structure/structure/unit/links/intra-compute.ts b/src/mol-model/structure/structure/unit/links/intra-compute.ts index 5ba8eb95b351a01f472f10132a16f190af4ece99..04b7497de8988bcda8b034a76fff8971c41e6834 100644 --- a/src/mol-model/structure/structure/unit/links/intra-compute.ts +++ b/src/mol-model/structure/structure/unit/links/intra-compute.ts @@ -6,11 +6,11 @@ import { LinkType } from '../../../model/types' import { IntraUnitLinks } from './data' -import { StructConn, ComponentBond } from '../../../model/formats/mmcif/bonds' import Unit from '../../unit' import { IntAdjacencyGraph } from 'mol-math/graph'; import { LinkComputationParameters, getElementIdx, MetalsSet, getElementThreshold, isHydrogen, getElementPairThreshold } from './common'; import { SortedArray } from 'mol-data/int'; +import { StructConn, ComponentBond } from 'mol-model-formats/structure/mmcif/bonds'; function getGraph(atomA: number[], atomB: number[], _order: number[], _flags: number[], atomCount: number): IntraUnitLinks { const builder = new IntAdjacencyGraph.EdgeBuilder(atomCount, atomA, atomB); diff --git a/src/mol-model/structure/structure/unit/pair-restraints/extract-cross-links.ts b/src/mol-model/structure/structure/unit/pair-restraints/extract-cross-links.ts index 28ae9d1be1bb2d38ac20cd7a50206415f6acfd88..a6e027e9cc5937eecdcd7e39b129c186cbbb6871 100644 --- a/src/mol-model/structure/structure/unit/pair-restraints/extract-cross-links.ts +++ b/src/mol-model/structure/structure/unit/pair-restraints/extract-cross-links.ts @@ -6,9 +6,9 @@ import Unit from '../../unit'; import Structure from '../../structure'; -import { IHMCrossLinkRestraint } from '../../../model/formats/mmcif/pair-restraint'; import { PairRestraints, CrossLinkRestraint } from './data'; import { StructureElement } from '../../../structure'; +import { IHMCrossLinkRestraint } from 'mol-model-formats/structure/mmcif/pair-restraint'; function _addRestraints(map: Map<number, number>, unit: Unit, restraints: IHMCrossLinkRestraint) { const { elements } = unit; diff --git a/src/mol-model/structure/util.ts b/src/mol-model/structure/util.ts index e688b99c2c88325f541e5184020b98dd5648bcd8..40b5c13dac895a5a43e62a51aee243f932886054 100644 --- a/src/mol-model/structure/util.ts +++ b/src/mol-model/structure/util.ts @@ -1,11 +1,11 @@ /** - * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info. + * Copyright (c) 2018-2019 mol* contributors, licensed under MIT, See LICENSE file for more info. * * @author Alexander Rose <alexander.rose@weirdbyte.de> */ import { Model, ResidueIndex, ElementIndex } from './model'; -import { MoleculeType, AtomRole, MoleculeTypeAtomRoleId } from './model/types'; +import { MoleculeType, AtomRole, MoleculeTypeAtomRoleId, getMoleculeType } from './model/types'; import { Vec3 } from 'mol-math/linear-algebra'; import { Unit } from './structure'; import Matrix from 'mol-math/linear-algebra/matrix/matrix'; @@ -17,27 +17,22 @@ export function getCoarseBegCompId(unit: Unit.Spheres | Unit.Gaussians, element: return seq.compId.value(seq_id_begin - 1) // 1-indexed } -export function getElementMoleculeType(unit: Unit, element: ElementIndex) { - let compId = '' +export function getElementMoleculeType(unit: Unit, element: ElementIndex): MoleculeType { switch (unit.kind) { case Unit.Kind.Atomic: - compId = unit.model.atomicHierarchy.residues.label_comp_id.value(unit.residueIndex[element]) - break + return unit.model.atomicHierarchy.derived.residue.moleculeType[unit.residueIndex[element]] case Unit.Kind.Spheres: case Unit.Kind.Gaussians: - compId = getCoarseBegCompId(unit, element) - break + // TODO add unit.model.coarseHierarchy.derived.residue.moleculeType + const compId = getCoarseBegCompId(unit, element) + const cc = unit.model.properties.chemicalComponentMap.get(compId) + if (cc) return getMoleculeType(cc.type, compId) } - const chemCompMap = unit.model.properties.chemicalComponentMap - const cc = chemCompMap.get(compId) - return cc ? cc.moleculeType : MoleculeType.unknown + return MoleculeType.unknown } -export function getAtomicMoleculeType(model: Model, rI: ResidueIndex) { - const compId = model.atomicHierarchy.residues.label_comp_id.value(rI) - const chemCompMap = model.properties.chemicalComponentMap - const cc = chemCompMap.get(compId) - return cc ? cc.moleculeType : MoleculeType.unknown +export function getAtomicMoleculeType(model: Model, rI: ResidueIndex): MoleculeType { + return model.atomicHierarchy.derived.residue.moleculeType[rI] } export function getAtomIdForAtomRole(moleculeType: MoleculeType, atomRole: AtomRole) { diff --git a/src/mol-model/volume.ts b/src/mol-model/volume.ts index aa8cf1ce46056532d4723f8c3584de3842c4e484..c622e171bf076b1d77cca1d2f027464ac44734ee 100644 --- a/src/mol-model/volume.ts +++ b/src/mol-model/volume.ts @@ -4,5 +4,4 @@ * @author David Sehnal <david.sehnal@gmail.com> */ -export * from './volume/data' -export * from './volume/formats/density-server' \ No newline at end of file +export * from './volume/data' \ No newline at end of file diff --git a/src/mol-plugin/behavior/dynamic/animation.ts b/src/mol-plugin/behavior/dynamic/animation.ts index d54e53db816499d74d497d28afd9e842eb6a386f..2b3a1cee14b172862acd3a79390ce0b660ef8c41 100644 --- a/src/mol-plugin/behavior/dynamic/animation.ts +++ b/src/mol-plugin/behavior/dynamic/animation.ts @@ -64,7 +64,7 @@ export const StructureAnimation = PluginBehavior.create<StructureAnimationProps> rotate(rad: number) { this.updatedUnitTransforms.clear() const state = this.ctx.state.dataState - const reprs = state.select(q => q.rootsOfType(PluginStateObject.Molecule.Representation3D)) + const reprs = state.selectQ(q => q.rootsOfType(PluginStateObject.Molecule.Representation3D)) Mat4.rotate(this.rotMat, this.tmpMat, rad, this.rotVec) for (const r of reprs) { if (!SO.isRepresentation3D(r.obj)) return @@ -112,7 +112,7 @@ export const StructureAnimation = PluginBehavior.create<StructureAnimationProps> explode(p: number) { this.updatedUnitTransforms.clear() const state = this.ctx.state.dataState - const reprs = state.select(q => q.rootsOfType(PluginStateObject.Molecule.Representation3D)); + const reprs = state.selectQ(q => q.rootsOfType(PluginStateObject.Molecule.Representation3D)); for (const r of reprs) { if (!SO.isRepresentation3D(r.obj)) return const structure = getRootStructure(r, state) @@ -187,5 +187,5 @@ export const StructureAnimation = PluginBehavior.create<StructureAnimationProps> // function getRootStructure(root: StateObjectCell, state: State) { - return state.query(StateSelection.Generators.byValue(root).rootOfType([PluginStateObject.Molecule.Structure]))[0]; + return state.select(StateSelection.Generators.byValue(root).rootOfType([PluginStateObject.Molecule.Structure]))[0]; } \ No newline at end of file diff --git a/src/mol-plugin/behavior/dynamic/custom-props/pdbe/structure-quality-report.ts b/src/mol-plugin/behavior/dynamic/custom-props/pdbe/structure-quality-report.ts index 61a09e969723d1c0d28ece8064cee69b88f11b95..6c09982015da5a9d71bba13f2351d6dd8d40df83 100644 --- a/src/mol-plugin/behavior/dynamic/custom-props/pdbe/structure-quality-report.ts +++ b/src/mol-plugin/behavior/dynamic/custom-props/pdbe/structure-quality-report.ts @@ -40,7 +40,7 @@ export const PDBeStructureQualityReport = PluginBehavior.create<{ autoAttach: bo factory: StructureQualityReportColorTheme, getParams: () => ({}), defaultValues: {}, - isApplicable: (ctx: ThemeDataContext) => !!ctx.structure && ctx.structure.models[0].customProperties.has(StructureQualityReport.Descriptor) + isApplicable: (ctx: ThemeDataContext) => !!ctx.structure && !ctx.structure.isEmpty && ctx.structure.models[0].customProperties.has(StructureQualityReport.Descriptor) }) } diff --git a/src/mol-plugin/behavior/dynamic/labels.ts b/src/mol-plugin/behavior/dynamic/labels.ts index f30f4464e52091c277f7cdcf9531faf4f5d5a5c5..7b8ccd1ba92cb413457de3fe1e6ae15b85944084 100644 --- a/src/mol-plugin/behavior/dynamic/labels.ts +++ b/src/mol-plugin/behavior/dynamic/labels.ts @@ -21,6 +21,7 @@ import { Unit, StructureElement, StructureProperties } from 'mol-model/structure import { SetUtils } from 'mol-util/set'; import { arrayEqual } from 'mol-util'; import { MoleculeType } from 'mol-model/structure/model/types'; +import { getElementMoleculeType } from 'mol-model/structure/util'; // TODO // - support more object types than structures @@ -112,7 +113,7 @@ export const SceneLabels = PluginBehavior.create<SceneLabelsProps>({ /** Update structures to be labeled, returns true if changed */ private updateStructures(p: SceneLabelsProps) { const state = this.ctx.state.dataState - const structures = state.select(q => q.rootsOfType(PluginStateObject.Molecule.Structure)); + const structures = state.selectQ(q => q.rootsOfType(PluginStateObject.Molecule.Structure)); const rootStructures = new Set<SO.Molecule.Structure>() for (const s of structures) { const rootStructure = getRootStructure(s, state) @@ -157,9 +158,7 @@ export const SceneLabels = PluginBehavior.create<SceneLabelsProps>({ } if (p.levels.includes('ligand') && !u.polymerElements.length) { - const compId = StructureProperties.residue.label_comp_id(l) - const chemComp = u.model.properties.chemicalComponentMap.get(compId) - const moleculeType = chemComp ? chemComp.moleculeType : MoleculeType.unknown + const moleculeType = getElementMoleculeType(u, u.elements[0]) if (moleculeType === MoleculeType.other || moleculeType === MoleculeType.saccharide) { label = `${StructureProperties.entity.pdbx_description(l).join(', ')} (${getAsymId(u)(l)})` } diff --git a/src/mol-plugin/behavior/static/state.ts b/src/mol-plugin/behavior/static/state.ts index f3d852d82e218b3cc1455ea579b70bd88032dae4..2e53ede8c6ae73956b50ce2fc5061749bfd8a3a1 100644 --- a/src/mol-plugin/behavior/static/state.ts +++ b/src/mol-plugin/behavior/static/state.ts @@ -51,17 +51,17 @@ export function SetCurrentObject(ctx: PluginContext) { } export function Update(ctx: PluginContext) { - PluginCommands.State.Update.subscribe(ctx, ({ state, tree }) => ctx.runTask(state.update(tree))); + PluginCommands.State.Update.subscribe(ctx, ({ state, tree }) => ctx.runTask(state.updateTree(tree))); } export function ApplyAction(ctx: PluginContext) { - PluginCommands.State.ApplyAction.subscribe(ctx, ({ state, action, ref }) => ctx.runTask(state.apply(action.action, action.params, ref))); + PluginCommands.State.ApplyAction.subscribe(ctx, ({ state, action, ref }) => ctx.runTask(state.applyAction(action.action, action.params, ref))); } export function RemoveObject(ctx: PluginContext) { PluginCommands.State.RemoveObject.subscribe(ctx, ({ state, ref }) => { const tree = state.tree.build().delete(ref).getTree(); - return ctx.runTask(state.update(tree)); + return ctx.runTask(state.updateTree(tree)); }); } diff --git a/src/mol-plugin/command/base.ts b/src/mol-plugin/command/base.ts index fcf1980a4e158b1dc29f27a7c085b5c187327095..d58e5e1b709d80b1e28a530f9e681d0b28d2af24 100644 --- a/src/mol-plugin/command/base.ts +++ b/src/mol-plugin/command/base.ts @@ -42,7 +42,7 @@ namespace PluginCommand { unsubscribe(): void } - export type Action<T> = (params: T) => void | Promise<void> + export type Action<T> = (params: T) => unknown | Promise<unknown> type Instance = { cmd: PluginCommand<any>, params: any, resolve: () => void, reject: (e: any) => void } export class Manager { diff --git a/src/mol-plugin/context.ts b/src/mol-plugin/context.ts index 2749c5eee811344b9646c7a403a3cbb11eed5b72..13f4d4480d7ee680983295cdb2b06d562c25d4ec 100644 --- a/src/mol-plugin/context.ts +++ b/src/mol-plugin/context.ts @@ -29,6 +29,7 @@ import { VolumeRepresentationRegistry } from 'mol-repr/volume/registry'; import { PLUGIN_VERSION, PLUGIN_VERSION_DATE } from './version'; import { PluginLayout } from './layout'; import { List } from 'immutable'; +import { StateTransformParameters } from './ui/state/common'; export class PluginContext { private disposed = false; @@ -87,6 +88,7 @@ export class PluginContext { } readonly customModelProperties = new CustomPropertyRegistry(); + readonly customParamEditors = new Map<string, StateTransformParameters.Class>(); initViewer(canvas: HTMLCanvasElement, container: HTMLDivElement) { try { @@ -136,6 +138,16 @@ export class PluginContext { this.disposed = true; } + applyTransform(state: State, a: Transform.Ref, transformer: Transformer, params: any) { + const tree = state.tree.build().to(a).apply(transformer, params); + return PluginCommands.State.Update.dispatch(this, { state, tree }); + } + + updateTransform(state: State, a: Transform.Ref, params: any) { + const tree = state.build().to(a).update(params); + return PluginCommands.State.Update.dispatch(this, { state, tree }); + } + private initBuiltInBehavior() { BuiltInPluginBehaviors.State.registerDefault(this); BuiltInPluginBehaviors.Representation.registerDefault(this); @@ -145,30 +157,28 @@ export class PluginContext { merge(this.state.dataState.events.log, this.state.behaviorState.events.log).subscribe(e => this.events.log.next(e)); } - async initBehaviors() { + private async initBehaviors() { const tree = this.state.behaviorState.tree.build(); for (const b of this.spec.behaviors) { tree.toRoot().apply(b.transformer, b.defaultParams, { ref: b.transformer.id }); } - await this.runTask(this.state.behaviorState.update(tree, true)); + await this.runTask(this.state.behaviorState.updateTree(tree, true)); } - initDataActions() { + private initDataActions() { for (const a of this.spec.actions) { this.state.dataState.actions.add(a.action); } } - applyTransform(state: State, a: Transform.Ref, transformer: Transformer, params: any) { - const tree = state.tree.build().to(a).apply(transformer, params); - return PluginCommands.State.Update.dispatch(this, { state, tree }); - } + private initCustomParamEditors() { + if (!this.spec.customParamEditors) return; - updateTransform(state: State, a: Transform.Ref, params: any) { - const tree = state.build().to(a).update(params); - return PluginCommands.State.Update.dispatch(this, { state, tree }); + for (const [t, e] of this.spec.customParamEditors) { + this.customParamEditors.set(t.id, e); + } } constructor(public spec: PluginSpec) { @@ -178,12 +188,10 @@ export class PluginContext { this.initBehaviors(); this.initDataActions(); + this.initCustomParamEditors(); this.lociLabels = new LociLabelManager(this); - // TODO: find a better solution for this. - setTimeout(() => this.log.message(`Mol* Plugin ${PLUGIN_VERSION} [${PLUGIN_VERSION_DATE.toLocaleString()}]`), 500); + this.log.message(`Mol* Plugin ${PLUGIN_VERSION} [${PLUGIN_VERSION_DATE.toLocaleString()}]`); } - - // settings = ; } \ No newline at end of file diff --git a/src/mol-plugin/index.ts b/src/mol-plugin/index.ts index ba07c78ce7fa16e0dfb516fed52ddc39cef2d549..54e7e1590e56e0970754f3e5083f11a53a6f8bb6 100644 --- a/src/mol-plugin/index.ts +++ b/src/mol-plugin/index.ts @@ -31,6 +31,7 @@ export const DefaultPluginSpec: PluginSpec = { PluginSpec.Action(StateTransforms.Data.ParseCif), PluginSpec.Action(StateTransforms.Data.ParseCcp4), PluginSpec.Action(StateTransforms.Model.StructureAssemblyFromModel), + PluginSpec.Action(StateTransforms.Model.StructureSymmetryFromModel), PluginSpec.Action(StateTransforms.Model.StructureFromModel), PluginSpec.Action(StateTransforms.Model.ModelFromTrajectory), PluginSpec.Action(StateTransforms.Model.VolumeFromCcp4), diff --git a/src/mol-plugin/spec.ts b/src/mol-plugin/spec.ts index 86b95500864782a8d6d3767fe9643b8194616ece..7475b6ebd0446cd623ad7de994031eb321530218 100644 --- a/src/mol-plugin/spec.ts +++ b/src/mol-plugin/spec.ts @@ -14,6 +14,7 @@ export { PluginSpec } interface PluginSpec { actions: PluginSpec.Action[], behaviors: PluginSpec.Behavior[], + customParamEditors?: [StateAction | Transformer, StateTransformParameters.Class][] initialLayout?: PluginLayoutStateProps } diff --git a/src/mol-plugin/state/actions/basic.ts b/src/mol-plugin/state/actions/basic.ts index b2c372fa2665d6cac0bf5f1ff57edaf269440260..d06655c3da87f40e51b6a8c33cd6829db2734f5a 100644 --- a/src/mol-plugin/state/actions/basic.ts +++ b/src/mol-plugin/state/actions/basic.ts @@ -14,8 +14,9 @@ import { ParamDefinition as PD } from 'mol-util/param-definition'; import { PluginStateObject } from '../objects'; import { StateTransforms } from '../transforms'; import { Download } from '../transforms/data'; -import { StructureRepresentation3DHelpers, VolumeRepresentation3DHelpers } from '../transforms/representation'; +import { StructureRepresentation3DHelpers } from '../transforms/representation'; import { getFileInfo, FileInput } from 'mol-util/file-info'; +import { Task } from 'mol-task'; // TODO: "structure/volume parser provider" @@ -40,6 +41,7 @@ const DownloadStructure = StateAction.build({ }, { isFlat: true }), 'url': PD.Group({ url: PD.Text(''), + format: PD.Select('cif', [['cif', 'CIF'], ['pdb', 'PDB']]), isBinary: PD.Boolean(false), supportProps: PD.Boolean(false) }, { isFlat: true }) @@ -59,7 +61,7 @@ const DownloadStructure = StateAction.build({ switch (src.name) { case 'url': - downloadParams = src.params; + downloadParams = { url: src.params.url, isBinary: src.params.isBinary }; break; case 'pdbe-updated': downloadParams = { url: `https://www.ebi.ac.uk/pdbe/static/entry/${src.params.id.toLowerCase()}_updated.cif`, isBinary: false, label: `PDBe: ${src.params.id}` }; @@ -74,7 +76,8 @@ const DownloadStructure = StateAction.build({ } const data = b.toRoot().apply(StateTransforms.Data.Download, downloadParams); - return state.update(createStructureTree(ctx, data, params.source.params.supportProps)); + const traj = createModelTree(data, src.name === 'url' ? src.params.format : 'cif'); + return state.updateTree(createStructureTree(ctx, traj, params.source.params.supportProps)); }); export const OpenStructure = StateAction.build({ @@ -84,15 +87,20 @@ export const OpenStructure = StateAction.build({ })(({ 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) }); - return state.update(createStructureTree(ctx, data, false)); + const traj = createModelTree(data, 'cif'); + return state.updateTree(createStructureTree(ctx, traj, false)); }); -function createStructureTree(ctx: PluginContext, b: StateTreeBuilder.To<PluginStateObject.Data.Binary | PluginStateObject.Data.String>, supportProps: boolean): StateTree { - let root = b - .apply(StateTransforms.Data.ParseCif) - .apply(StateTransforms.Model.TrajectoryFromMmCif) - .apply(StateTransforms.Model.ModelFromTrajectory, { modelIndex: 0 }); +function createModelTree(b: StateTreeBuilder.To<PluginStateObject.Data.Binary | PluginStateObject.Data.String>, format: 'pdb' | 'cif' = 'cif') { + const parsed = format === 'cif' + ? b.apply(StateTransforms.Data.ParseCif).apply(StateTransforms.Model.TrajectoryFromMmCif) + : b.apply(StateTransforms.Model.TrajectoryFromPDB); + + return parsed.apply(StateTransforms.Model.ModelFromTrajectory, { modelIndex: 0 }); +} +function createStructureTree(ctx: PluginContext, b: StateTreeBuilder.To<PluginStateObject.Molecule.Model>, supportProps: boolean): StateTree { + let root = b; if (supportProps) { root = root.apply(StateTransforms.Model.CustomModelProperties); } @@ -123,7 +131,7 @@ export const CreateComplexRepresentation = StateAction.build({ })(({ ref, state }, ctx: PluginContext) => { const root = state.build().to(ref); complexRepresentation(ctx, root); - return state.update(root.getTree()); + return state.updateTree(root.getTree()); }); export const UpdateTrajectory = StateAction.build({ @@ -133,7 +141,7 @@ export const UpdateTrajectory = StateAction.build({ by: PD.makeOptional(PD.Numeric(1, { min: -1, max: 1, step: 1 })) } })(({ params, state }) => { - const models = state.select(q => q.rootsOfType(PluginStateObject.Molecule.Model) + const models = state.selectQ(q => q.rootsOfType(PluginStateObject.Molecule.Model) .filter(c => c.transform.transformer === StateTransforms.Model.ModelFromTrajectory)); const update = state.build(); @@ -157,7 +165,7 @@ export const UpdateTrajectory = StateAction.build({ } } - return state.update(update); + return state.updateTree(update); }); // @@ -177,15 +185,15 @@ function getVolumeData(format: VolumeFormat, b: StateTreeBuilder.To<PluginStateO } function createVolumeTree(format: VolumeFormat, ctx: PluginContext, b: StateTreeBuilder.To<PluginStateObject.Data.Binary | PluginStateObject.Data.String>): StateTree { - - const root = getVolumeData(format, b) - .apply(StateTransforms.Representation.VolumeRepresentation3D, - VolumeRepresentation3DHelpers.getDefaultParamsStatic(ctx, 'isosurface')); - - return root.getTree(); + return getVolumeData(format, b) + .apply(StateTransforms.Representation.VolumeRepresentation3D) + // the parameters will be used automatically by the reconciler and the IsoValue object + // will get the correct Stats object instead of the empty one + // VolumeRepresentation3DHelpers.getDefaultParamsStatic(ctx, 'isosurface')) + .getTree(); } -function getFileFormat(format: VolumeFormat | 'auto', file: FileInput): VolumeFormat { +function getFileFormat(format: VolumeFormat | 'auto', file: FileInput, data?: Uint8Array): VolumeFormat { if (format === 'auto') { const fileFormat = getFileInfo(file).ext if (fileFormat in VolumeFormats) { @@ -208,12 +216,22 @@ export const OpenVolume = StateAction.build({ ['auto', 'Automatic'], ['ccp4', 'CCP4'], ['mrc', 'MRC'], ['map', 'MAP'], ['dsn6', 'DSN6'], ['brix', 'BRIX'], ['dscif', 'densityServerCIF'] ]), } -})(({ params, state }, ctx: PluginContext) => { - const b = state.build(); - const data = b.toRoot().apply(StateTransforms.Data.ReadFile, { file: params.file, isBinary: params.isBinary }); - const format = getFileFormat(params.format, params.file) - return state.update(createVolumeTree(format, ctx, data)); -}); +})(({ params, state }, ctx: PluginContext) => Task.create('Open Volume', async taskCtx => { + const dataTree = state.build().toRoot().apply(StateTransforms.Data.ReadFile, { file: params.file, isBinary: true }); + const volumeData = await state.updateTree(dataTree).runInContext(taskCtx); + + // Alternative for more complex states where the builder is not a simple StateTreeBuilder.To<>: + /* + const dataRef = dataTree.ref; + await state.updateTree(dataTree).runInContext(taskCtx); + const dataCell = state.select(dataRef)[0]; + */ + + const format = getFileFormat(params.format, params.file, volumeData.data as Uint8Array); + const volumeTree = state.build().to(dataTree.ref); + // need to await the 2nd update the so that the enclosing Task finishes after the update is done. + await state.updateTree(createVolumeTree(format, ctx, volumeTree)).runInContext(taskCtx); +})); export { DownloadDensity }; type DownloadDensity = typeof DownloadDensity @@ -280,5 +298,5 @@ const DownloadDensity = StateAction.build({ } const data = b.toRoot().apply(StateTransforms.Data.Download, downloadParams); - return state.update(createVolumeTree(format, ctx, data)); + return state.updateTree(createVolumeTree(format, ctx, data)); }); \ No newline at end of file diff --git a/src/mol-plugin/state/transforms/model.ts b/src/mol-plugin/state/transforms/model.ts index 9e697bfc24dbc9eda2f54a7950a9f2e231a6cb19..a28e72ada1d3fc3a30cddb12a12b8d493ca15bfd 100644 --- a/src/mol-plugin/state/transforms/model.ts +++ b/src/mol-plugin/state/transforms/model.ts @@ -8,7 +8,7 @@ import { PluginStateTransform } from '../objects'; import { PluginStateObject as SO } from '../objects'; import { Task, RuntimeContext } from 'mol-task'; -import { Model, Format, Structure, ModelSymmetry, StructureSymmetry, QueryContext, StructureSelection as Sel, StructureQuery, Queries } from 'mol-model/structure'; +import { Model, Structure, ModelSymmetry, StructureSymmetry, QueryContext, StructureSelection as Sel, StructureQuery, Queries } from 'mol-model/structure'; import { ParamDefinition as PD } from 'mol-util/param-definition'; import Expression from 'mol-script/language/expression'; import { compile } from 'mol-script/runtime/query/compiler'; @@ -16,11 +16,14 @@ import { MolScriptBuilder } from 'mol-script/language/builder'; import { StateObject } from 'mol-state'; import { PluginContext } from 'mol-plugin/context'; import { stringToWords } from 'mol-util/string'; -import { volumeFromCcp4 } from 'mol-model/volume/formats/ccp4'; +import { volumeFromCcp4 } from 'mol-model-formats/volume/ccp4'; import { Vec3 } from 'mol-math/linear-algebra'; -import { volumeFromDsn6 } from 'mol-model/volume/formats/dsn6'; -import { volumeFromDensityServerData } from 'mol-model/volume'; import CIF from 'mol-io/reader/cif'; +import { volumeFromDsn6 } from 'mol-model-formats/volume/dsn6'; +import { volumeFromDensityServerData } from 'mol-model-formats/volume/density-server'; +import { trajectoryFromMmCIF } from 'mol-model-formats/structure/mmcif'; +import { parsePDB } from 'mol-io/reader/pdb/parser'; +import { trajectoryFromPDB } from 'mol-model-formats/structure/pdb'; export { TrajectoryFromMmCif } type TrajectoryFromMmCif = typeof TrajectoryFromMmCif @@ -47,7 +50,7 @@ const TrajectoryFromMmCif = PluginStateTransform.BuiltIn({ const header = params.blockHeader || a.data.blocks[0].header; const block = a.data.blocks.find(b => b.header === header); if (!block) throw new Error(`Data block '${[header]}' not found.`); - const models = await Model.create(Format.mmCIF(block)).runInContext(ctx); + const models = await trajectoryFromMmCIF(block).runInContext(ctx); if (models.length === 0) throw new Error('No models found.'); const props = { label: models[0].label, description: `${models.length} model${models.length === 1 ? '' : 's'}` }; return new SO.Molecule.Trajectory(models, props); @@ -55,6 +58,27 @@ const TrajectoryFromMmCif = PluginStateTransform.BuiltIn({ } }); + +export { TrajectoryFromPDB } +type TrajectoryFromPDB = typeof TrajectoryFromPDB +const TrajectoryFromPDB = PluginStateTransform.BuiltIn({ + name: 'trajectory-from-pdb', + display: { name: 'Parse PDB string and create trajectory' }, + from: [SO.Data.String], + to: SO.Molecule.Trajectory +})({ + apply({ a }) { + return Task.create('Parse PDB', async ctx => { + const parsed = await parsePDB(a.data).runInContext(ctx); + if (parsed.isError) throw new Error(parsed.message); + const models = await trajectoryFromPDB(parsed.result).runInContext(ctx); + const props = { label: models[0].label, description: `${models.length} model${models.length === 1 ? '' : 's'}` }; + return new SO.Molecule.Trajectory(models, props); + }); + } +}); + + export { ModelFromTrajectory } const plus1 = (v: number) => v + 1, minus1 = (v: number) => v - 1; type ModelFromTrajectory = typeof ModelFromTrajectory @@ -107,28 +131,30 @@ const StructureAssemblyFromModel = PluginStateTransform.BuiltIn({ to: SO.Molecule.Structure, params(a) { if (!a) { - return { id: PD.makeOptional(PD.Text('', { label: 'Assembly Id', description: 'Assembly Id. If none specified (undefined or empty string), the asymmetric unit is used.' })) }; + return { id: PD.makeOptional(PD.Text('', { label: 'Assembly Id', description: 'Assembly Id. Value \'deposited\' can be used to specify deposited asymmetric unit.' })) }; } const model = a.data; const ids = model.symmetry.assemblies.map(a => [a.id, `${a.id}: ${stringToWords(a.details)}`] as [string, string]); - if (!ids.length) ids.push(['deposited', 'Deposited']) + ids.push(['deposited', 'Deposited']); return { id: PD.makeOptional(PD.Select(ids[0][0], ids, { label: 'Asm Id', description: 'Assembly Id' })) }; } })({ apply({ a, params }, plugin: PluginContext) { return Task.create('Build Assembly', async ctx => { const model = a.data; - const id = params.id; - const asm = ModelSymmetry.findAssembly(model, id || ''); + let id = params.id; + let asm = ModelSymmetry.findAssembly(model, id || ''); if (!!id && id !== 'deposited' && !asm) throw new Error(`Assembly '${id}' not found`); const base = Structure.ofModel(model); - if (!asm) { - plugin.log.warn(`Model '${a.label}' has no assembly, returning deposited structure.`); + if ((id && !asm) || model.symmetry.assemblies.length === 0) { + if (!!id && id !== 'deposited') plugin.log.warn(`Model '${a.label}' has no assembly, returning deposited structure.`); const label = { label: a.data.label, description: structureDesc(base) }; return new SO.Molecule.Structure(base, label); } + asm = model.symmetry.assemblies[0]; + id = asm.id; const s = await StructureSymmetry.buildAssembly(base, id!).runInContext(ctx); const props = { label: `Assembly ${id}`, description: structureDesc(s) }; return new SO.Molecule.Structure(s, props); @@ -136,6 +162,32 @@ const StructureAssemblyFromModel = PluginStateTransform.BuiltIn({ } }); +export { StructureSymmetryFromModel } +type StructureSymmetryFromModel = typeof StructureSymmetryFromModel +const StructureSymmetryFromModel = PluginStateTransform.BuiltIn({ + name: 'structure-symmetry-from-model', + display: { name: 'Structure Symmetry', description: 'Create a molecular structure symmetry.' }, + from: SO.Molecule.Model, + to: SO.Molecule.Structure, + params(a) { + return { + ijkMin: PD.Vec3(Vec3.create(-1, -1, -1), { label: 'Min IJK', fieldLabels: { x: 'I', y: 'J', z: 'K' } }), + ijkMax: PD.Vec3(Vec3.create(1, 1, 1), { label: 'Max IJK', fieldLabels: { x: 'I', y: 'J', z: 'K' } }) + } + } +})({ + apply({ a, params }, plugin: PluginContext) { + return Task.create('Build Symmetry', async ctx => { + const { ijkMin, ijkMax } = params + const model = a.data; + const base = Structure.ofModel(model); + const s = await StructureSymmetry.buildSymmetryRange(base, ijkMin, ijkMax).runInContext(ctx); + const props = { label: `Symmetry [${ijkMin}] to [${ijkMax}]`, description: structureDesc(s) }; + return new SO.Molecule.Structure(s, props); + }) + } +}); + export { StructureSelection } type StructureSelection = typeof StructureSelection const StructureSelection = PluginStateTransform.BuiltIn({ diff --git a/src/mol-plugin/state/transforms/representation.ts b/src/mol-plugin/state/transforms/representation.ts index 4ffdfa8face12d2066213a6cd09a32e9c4a339a1..31fbb134ff2df51dfc639187c3c87edefb255302 100644 --- a/src/mol-plugin/state/transforms/representation.ts +++ b/src/mol-plugin/state/transforms/representation.ts @@ -233,6 +233,8 @@ const VolumeRepresentation3D = PluginStateTransform.BuiltIn({ const repr = provider.factory({ webgl: plugin.canvas3d.webgl, ...plugin.volumeRepresentation.themeCtx }, provider.getParams) repr.setTheme(createTheme(plugin.volumeRepresentation.themeCtx, { volume: a.data }, params)) // TODO set initial state, repr.setState({}) + // TODO include isoValue in the label where available + console.log(params.type.params); await repr.createOrUpdate(props, a.data).runInContext(ctx); return new SO.Volume.Representation3D(repr, { label: provider.label }); }); diff --git a/src/mol-plugin/ui/controls/parameters.tsx b/src/mol-plugin/ui/controls/parameters.tsx index a6bff120b3f6d20392921d50ec8e6f35603a7697..caa63c87a96e8ed267212070ff1285fd947c3161 100644 --- a/src/mol-plugin/ui/controls/parameters.tsx +++ b/src/mol-plugin/ui/controls/parameters.tsx @@ -5,7 +5,7 @@ * @author Alexander Rose <alexander.rose@weirdbyte.de> */ -import { Vec2 } from 'mol-math/linear-algebra'; +import { Vec2, Vec3 } from 'mol-math/linear-algebra'; import { Color } from 'mol-util/color'; import { ColorListName, getColorListFromName } from 'mol-util/color/scale'; import { ColorNames, ColorNamesValueMap } from 'mol-util/color/tables'; @@ -109,20 +109,20 @@ export class LineGraphControl extends React.PureComponent<ParamProps<PD.LineGrap } onHover = (point?: Vec2) => { - this.setState({isOverPoint: !this.state.isOverPoint}); + this.setState({ isOverPoint: !this.state.isOverPoint }); if (point) { - this.setState({message: `(${point[0].toFixed(2)}, ${point[1].toFixed(2)})`}); + this.setState({ message: `(${point[0].toFixed(2)}, ${point[1].toFixed(2)})` }); return; } - this.setState({message: `${this.props.value.length} points`}); + this.setState({ message: `${this.props.value.length} points` }); } onDrag = (point: Vec2) => { - this.setState({message: `(${point[0].toFixed(2)}, ${point[1].toFixed(2)})`}); + this.setState({ message: `(${point[0].toFixed(2)}, ${point[1].toFixed(2)})` }); } - onChange = (value: PD.LineGraph['defaultValue'] ) => { - this.props.onChange({ name: this.props.name, param: this.props.param, value: value}); + onChange = (value: PD.LineGraph['defaultValue']) => { + this.props.onChange({ name: this.props.name, param: this.props.param, value: value }); } toggleExpanded = (e: React.MouseEvent<HTMLButtonElement>) => { @@ -146,18 +146,61 @@ export class LineGraphControl extends React.PureComponent<ParamProps<PD.LineGrap data={this.props.param.defaultValue} onChange={this.onChange} onHover={this.onHover} - onDrag={this.onDrag}/> + onDrag={this.onDrag} /> </div> </>; } } -export class NumberInputControl extends SimpleParam<PD.Numeric> { - onChange = (e: React.ChangeEvent<HTMLInputElement>) => { this.update(+e.target.value); } - renderControl() { - return <span> - number input TODO - </span> +export class NumberInputControl extends React.PureComponent<ParamProps<PD.Numeric>, { value: string }> { + state = { value: '0' }; + + protected update(value: any) { + this.props.onChange({ param: this.props.param, name: this.props.name, value }); + } + + onChange = (e: React.ChangeEvent<HTMLInputElement>) => { + const value = +e.target.value; + this.setState({ value: e.target.value }, () => { + if (!Number.isNaN(value) && value !== this.props.value) { + this.update(value); + } + }); + } + + onKeyPress = (e: React.KeyboardEvent<HTMLInputElement>) => { + if (!this.props.onEnter) return; + if ((e.keyCode === 13 || e.charCode === 13)) { + this.props.onEnter(); + } + } + + onBlur = () => { + this.setState({ value: '' + this.props.value }); + } + + static getDerivedStateFromProps(props: { value: number }, state: { value: string }) { + const value = +state.value; + if (Number.isNaN(value) || value === props.value) return null; + return { value: '' + props.value }; + } + + render() { + const placeholder = this.props.param.label || camelCaseToWords(this.props.name); + const label = this.props.param.label || camelCaseToWords(this.props.name); + return <div className='msp-control-row'> + <span title={this.props.param.description}>{label}</span> + <div> + <input type='text' + onBlur={this.onBlur} + value={this.state.value} + placeholder={placeholder} + onChange={this.onChange} + onKeyPress={this.props.onEnter ? this.onKeyPress : void 0} + disabled={this.props.isDisabled} + /> + </div> + </div>; } } @@ -205,8 +248,9 @@ export class SelectControl extends SimpleParam<PD.Select<string | number>> { } } renderControl() { - return <select value={this.props.value || ''} onChange={this.onChange} disabled={this.props.isDisabled}> - {!this.props.param.options.some(e => e[0] === this.props.value) && <option key={this.props.value} value={this.props.value}>{`[Invalid] ${this.props.value}`}</option>} + const isInvalid = this.props.value !== void 0 && !this.props.param.options.some(e => e[0] === this.props.value); + return <select value={this.props.value || this.props.param.defaultValue} onChange={this.onChange} disabled={this.props.isDisabled}> + {isInvalid && <option key={this.props.value} value={this.props.value}>{`[Invalid] ${this.props.value}`}</option>} {this.props.param.options.map(([value, label]) => <option key={value} value={value}>{label}</option>)} </select>; } @@ -231,7 +275,7 @@ let _colors: React.ReactFragment | undefined = void 0; function ColorOptions() { if (_colors) return _colors; _colors = <>{Object.keys(ColorNames).map(name => - <option key={name} value={(ColorNames as { [k: string]: Color})[name]} style={{ background: `${Color.toStyle((ColorNames as { [k: string]: Color})[name])}` }} > + <option key={name} value={(ColorNames as { [k: string]: Color })[name]} style={{ background: `${Color.toStyle((ColorNames as { [k: string]: Color })[name])}` }} > {name} </option> )}</>; @@ -297,17 +341,49 @@ export class ColorScaleControl extends SimpleParam<PD.ColorScale<any>> { } } -export class Vec3Control extends SimpleParam<PD.Vec3> { - // onChange = (e: React.ChangeEvent<HTMLSelectElement>) => { - // this.setState({ value: e.target.value }); - // this.props.onChange(e.target.value); - // } +export class Vec3Control extends React.PureComponent<ParamProps<PD.Vec3>, { isExpanded: boolean }> { + state = { isExpanded: false } - renderControl() { - return <span>vec3 TODO</span>; + components = { + 0: PD.Numeric(0, void 0, { label: (this.props.param.fieldLabels && this.props.param.fieldLabels.x) || 'X' }), + 1: PD.Numeric(0, void 0, { label: (this.props.param.fieldLabels && this.props.param.fieldLabels.y) || 'Y' }), + 2: PD.Numeric(0, void 0, { label: (this.props.param.fieldLabels && this.props.param.fieldLabels.z) || 'Z' }) + } + + change(value: PD.MultiSelect<any>['defaultValue']) { + this.props.onChange({ name: this.props.name, param: this.props.param, value }); + } + + componentChange: ParamOnChange = ({ name, value }) => { + const v = Vec3.copy(Vec3.zero(), this.props.value); + v[+name] = value; + this.change(v); + } + + toggleExpanded = (e: React.MouseEvent<HTMLButtonElement>) => { + this.setState({ isExpanded: !this.state.isExpanded }); + e.currentTarget.blur(); + } + + render() { + const v = this.props.value; + const label = this.props.param.label || camelCaseToWords(this.props.name); + const value = `[${v[0].toFixed(2)}, ${v[1].toFixed(2)}, ${v[2].toFixed(2)}]`; + return <> + <div className='msp-control-row'> + <span>{label}</span> + <div> + <button onClick={this.toggleExpanded}>{value}</button> + </div> + </div> + <div className='msp-control-offset' style={{ display: this.state.isExpanded ? 'block' : 'none' }}> + <ParameterControls params={this.components} values={v} onChange={this.componentChange} onEnter={this.props.onEnter} /> + </div> + </>; } } + export class FileControl extends React.PureComponent<ParamProps<PD.FileParam>> { change(value: File) { this.props.onChange({ name: this.props.name, param: this.props.param, value }); @@ -330,7 +406,7 @@ export class FileControl extends React.PureComponent<ParamProps<PD.FileParam>> { export class MultiSelectControl extends React.PureComponent<ParamProps<PD.MultiSelect<any>>, { isExpanded: boolean }> { state = { isExpanded: false } - change(value: PD.MultiSelect<any>['defaultValue'] ) { + change(value: PD.MultiSelect<any>['defaultValue']) { this.props.onChange({ name: this.props.name, param: this.props.param, value }); } @@ -366,7 +442,8 @@ export class MultiSelectControl extends React.PureComponent<ParamProps<PD.MultiS <button onClick={this.toggle(value)} disabled={this.props.isDisabled}> <span style={{ float: sel ? 'left' : 'right' }}>{sel ? `✓ ${label}` : `${label} ✗`}</span> </button> - </div> })} + </div> + })} </div> </>; } @@ -375,7 +452,7 @@ export class MultiSelectControl extends React.PureComponent<ParamProps<PD.MultiS export class GroupControl extends React.PureComponent<ParamProps<PD.Group<any>>, { isExpanded: boolean }> { state = { isExpanded: !!this.props.param.isExpanded } - change(value: any ) { + change(value: any) { this.props.onChange({ name: this.props.name, param: this.props.param, value }); } @@ -423,7 +500,7 @@ export class MappedControl extends React.PureComponent<ParamProps<PD.Mapped<any> } } - change(value: PD.Mapped<any>['defaultValue'] ) { + change(value: PD.Mapped<any>['defaultValue']) { this.props.onChange({ name: this.props.name, param: this.props.param, value }); } @@ -458,7 +535,7 @@ export class MappedControl extends React.PureComponent<ParamProps<PD.Mapped<any> } export class ConditionedControl extends React.PureComponent<ParamProps<PD.Conditioned<any, any, any>>> { - change(value: PD.Conditioned<any, any, any>['defaultValue'] ) { + change(value: PD.Conditioned<any, any, any>['defaultValue']) { this.props.onChange({ name: this.props.name, param: this.props.param, value }); } diff --git a/src/mol-plugin/ui/state/apply-action.tsx b/src/mol-plugin/ui/state/apply-action.tsx index 17482d70072e34909f0e0545b9c6e8986366a6e0..c82faeee8d7ce946b1dff0ddb087fb05382520f8 100644 --- a/src/mol-plugin/ui/state/apply-action.tsx +++ b/src/mol-plugin/ui/state/apply-action.tsx @@ -41,6 +41,7 @@ class ApplyActionContol extends TransformContolBase<ApplyActionContol.Props, App }); } getInfo() { return this._getInfo(this.props.nodeRef, this.props.state.transforms.get(this.props.nodeRef).version); } + getTransformerId() { return this.props.state.transforms.get(this.props.nodeRef).transformer.id; } getHeader() { return this.props.action.definition.display; } canApply() { return !this.state.error && !this.state.busy; } canAutoApply() { return false; } diff --git a/src/mol-plugin/ui/state/common.tsx b/src/mol-plugin/ui/state/common.tsx index a56615a85ba7e4579d5eb6626b204189726539f5..daf5f2568aeca146e4cbf638d614a67ddc2906cf 100644 --- a/src/mol-plugin/ui/state/common.tsx +++ b/src/mol-plugin/ui/state/common.tsx @@ -102,6 +102,7 @@ abstract class TransformContolBase<P, S extends TransformContolBase.ControlState abstract getInfo(): StateTransformParameters.Props['info']; abstract getHeader(): Transformer.Definition['display']; abstract canApply(): boolean; + abstract getTransformerId(): string; abstract canAutoApply(newParams: any): boolean; abstract applyText(): string; abstract isUpdate(): boolean; @@ -170,13 +171,18 @@ abstract class TransformContolBase<P, S extends TransformContolBase.ControlState const display = this.getHeader(); + const tId = this.getTransformerId(); + const ParamEditor: StateTransformParameters.Class = this.plugin.customParamEditors.has(tId) + ? this.plugin.customParamEditors.get(tId)! + : StateTransformParameters; + return <div className='msp-transform-wrapper'> <div className='msp-transform-header'> <button className='msp-btn msp-btn-block' onClick={this.toggleExpanded}>{display.name}</button> {!this.state.isCollapsed && <button className='msp-btn msp-btn-link msp-transform-default-params' onClick={this.setDefault} disabled={this.state.busy} style={{ float: 'right'}} title='Set default params'>↻</button>} </div> {!this.state.isCollapsed && <> - <StateTransformParameters info={info} events={this.events} params={this.state.params} isDisabled={this.state.busy} /> + <ParamEditor info={info} events={this.events} params={this.state.params} isDisabled={this.state.busy} /> <div className='msp-transform-apply-wrap'> <button className='msp-btn msp-btn-block msp-transform-refresh msp-form-control' title='Refresh params' onClick={this.refresh} disabled={this.state.busy || this.state.isInitial}> diff --git a/src/mol-plugin/ui/state/update-transform.tsx b/src/mol-plugin/ui/state/update-transform.tsx index c7e89a1f43d4ba812913cecfc5572aa82787d822..ceff41e16f33a7debcd7edb87bb8693c79a58ff6 100644 --- a/src/mol-plugin/ui/state/update-transform.tsx +++ b/src/mol-plugin/ui/state/update-transform.tsx @@ -28,6 +28,7 @@ namespace UpdateTransformContol { class UpdateTransformContol extends TransformContolBase<UpdateTransformContol.Props, UpdateTransformContol.ComponentState> { applyAction() { return this.plugin.updateTransform(this.props.state, this.props.transform.ref, this.state.params); } getInfo() { return this._getInfo(this.props.transform); } + getTransformerId() { return this.props.transform.transformer.id; } getHeader() { return this.props.transform.transformer.definition.display; } canApply() { return !this.state.error && !this.state.busy && !this.state.isInitial; } applyText() { return this.canApply() ? 'Update' : 'Nothing to Update'; } diff --git a/src/mol-state/action.ts b/src/mol-state/action.ts index 1b178246fbc2fe31d421b4c116db744492117813..53b41b53b66b1727944f135ebdb4baf9176350be 100644 --- a/src/mol-state/action.ts +++ b/src/mol-state/action.ts @@ -71,7 +71,7 @@ namespace StateAction { params: def.params as Transformer.Definition<Transformer.From<T>, any, Transformer.Params<T>>['params'], run({ cell, state, params }) { const tree = state.build().to(cell.transform.ref).apply(transformer, params); - return state.update(tree); + return state.updateTree(tree) as Task<void>; } }) } diff --git a/src/mol-state/object.ts b/src/mol-state/object.ts index 495206df1f0a9ae667baf3b5e10c2eb463b78a97..3e2df3343df0b95b85698bbbbdabcfbda6dbbbd6 100644 --- a/src/mol-state/object.ts +++ b/src/mol-state/object.ts @@ -105,7 +105,7 @@ export class StateObjectTracker<T extends StateObject> { } update() { - const cell = this.state.query(this.query)[0]; + const cell = this.state.select(this.query)[0]; const version = cell ? cell.transform.version : void 0; const changed = this.cell !== cell || this.version !== version; this.cell = cell; diff --git a/src/mol-state/state.ts b/src/mol-state/state.ts index 9733c06add1823d77dcef5785eb3dfe878783532..42030da32278c8e08da5aefae600c17bf1d455ea 100644 --- a/src/mol-state/state.ts +++ b/src/mol-state/state.ts @@ -33,7 +33,7 @@ class State { readonly globalContext: unknown = void 0; readonly events = { cell: { - stateUpdated: this.ev<State.ObjectEvent & { cellState: StateObjectCell.State}>(), + stateUpdated: this.ev<State.ObjectEvent & { cellState: StateObjectCell.State }>(), created: this.ev<State.ObjectEvent & { cell: StateObjectCell }>(), removed: this.ev<State.ObjectEvent & { parent: Transform.Ref }>(), }, @@ -67,7 +67,7 @@ class State { setSnapshot(snapshot: State.Snapshot) { const tree = StateTree.fromJSON(snapshot.tree); - return this.update(tree); + return this.updateTree(tree); } setCurrent(ref: Transform.Ref) { @@ -89,26 +89,28 @@ class State { } /** - * Select Cells by ref or a query generated on the fly. - * @example state.select('test') - * @example state.select(q => q.byRef('test').subtree()) + * Select Cells using the provided selector. + * @example state.query(StateSelection.Generators.byRef('test').ancestorOfType([type])) + * @example state.query('test') */ - select(selector: Transform.Ref | ((q: typeof StateSelection.Generators) => StateSelection.Selector)) { - if (typeof selector === 'string') return StateSelection.select(selector, this); - return StateSelection.select(selector(StateSelection.Generators), this) + select(selector: StateSelection.Selector) { + return StateSelection.select(selector, this) } /** - * Select Cells using the provided selector. - * @example state.select('test') + * Select Cells by building a query generated on the fly. * @example state.select(q => q.byRef('test').subtree()) */ - query(selector: StateSelection.Selector) { - return StateSelection.select(selector, this) + selectQ(selector: (q: typeof StateSelection.Generators) => StateSelection.Selector) { + if (typeof selector === 'string') return StateSelection.select(selector, this); + return StateSelection.select(selector(StateSelection.Generators), this) } - /** If no ref is specified, apply to root */ - apply<A extends StateAction>(action: A, params: StateAction.Params<A>, ref: Transform.Ref = Transform.RootRef): Task<void> { + /** + * Creates a Task that applies the specified StateAction (i.e. must use run* on the result) + * If no ref is specified, apply to root. + */ + applyAction<A extends StateAction>(action: A, params: StateAction.Params<A>, ref: Transform.Ref = Transform.RootRef): Task<void> { return Task.create('Apply Action', ctx => { const cell = this.cells.get(ref); if (!cell) throw new Error(`'${ref}' does not exist.`); @@ -118,41 +120,59 @@ class State { }); } - update(tree: StateTree | StateTreeBuilder, silent: boolean = false): Task<void> { - const _tree = (StateTreeBuilder.is(tree) ? tree.getTree() : tree).asTransient(); + /** + * Reconcialites the existing state tree with the new version. + * + * If the tree is StateTreeBuilder.To<T>, the corresponding StateObject is returned by the task. + * @param tree Tree instance or a tree builder instance + * @param doNotReportTiming Indicates whether to log timing of the individual transforms + */ + updateTree<T extends StateObject>(tree: StateTree | StateTreeBuilder | StateTreeBuilder.To<T>, doNotLogTiming?: boolean): Task<T> + updateTree(tree: StateTree | StateTreeBuilder, doNotLogTiming?: boolean): Task<void> + updateTree(tree: StateTree | StateTreeBuilder, doNotLogTiming: boolean = false): Task<any> { return Task.create('Update Tree', async taskCtx => { let updated = false; try { - const oldTree = this._tree; - this._tree = _tree; + const ctx = this.updateTreeAndCreateCtx(tree, taskCtx, doNotLogTiming); + updated = await update(ctx); + if (StateTreeBuilder.isTo(tree)) { + const cell = this.select(tree.ref)[0]; + return cell && cell.obj; + } + } finally { + if (updated) this.events.changed.next(); + } + }); + } + + private updateTreeAndCreateCtx(tree: StateTree | StateTreeBuilder, taskCtx: RuntimeContext, doNotLogTiming: boolean) { + const _tree = (StateTreeBuilder.is(tree) ? tree.getTree() : tree).asTransient(); + const oldTree = this._tree; + this._tree = _tree; - const ctx: UpdateContext = { - parent: this, - editInfo: StateTreeBuilder.is(tree) ? tree.editInfo : void 0, + const ctx: UpdateContext = { + parent: this, + editInfo: StateTreeBuilder.is(tree) ? tree.editInfo : void 0, - errorFree: this.errorFree, - taskCtx, - oldTree, - tree: _tree, - cells: this.cells as Map<Transform.Ref, StateObjectCell>, - transformCache: this.transformCache, + errorFree: this.errorFree, + taskCtx, + oldTree, + tree: _tree, + cells: this.cells as Map<Transform.Ref, StateObjectCell>, + transformCache: this.transformCache, - results: [], + results: [], - silent, + silent: doNotLogTiming, - changed: false, - hadError: false, - newCurrent: void 0 - }; + changed: false, + hadError: false, + newCurrent: void 0 + }; - this.errorFree = true; - // TODO: handle "cancelled" error? Or would this be handled automatically? - updated = await update(ctx); - } finally { - if (updated) this.events.changed.next(); - } - }); + this.errorFree = true; + + return ctx; } constructor(rootObject: StateObject, params?: { globalContext?: unknown }) { @@ -167,8 +187,8 @@ class State { version: root.version, errorText: void 0, params: { - definition: { }, - values: { } + definition: {}, + values: {} } }); @@ -311,7 +331,7 @@ async function update(ctx: UpdateContext) { const current = ctx.parent.current; const currentCell = ctx.cells.get(current); if (currentCell && ( - currentCell.obj === StateObject.Null + currentCell.obj === StateObject.Null || (currentCell.status === 'error' && currentCell.errorText === ParentNullErrorText))) { newCurrent = findNewCurrent(ctx.oldTree, current, [], ctx.cells); ctx.parent.setCurrent(newCurrent); @@ -509,6 +529,7 @@ async function updateSubtree(ctx: UpdateContext, root: Ref) { ctx.changed = true; if (!ctx.hadError) ctx.newCurrent = root; doError(ctx, root, '' + e, false); + console.error(e); return; } @@ -523,7 +544,7 @@ async function updateSubtree(ctx: UpdateContext, root: Ref) { function resolveParams(ctx: UpdateContext, transform: Transform, src: StateObject) { const prms = transform.transformer.definition.params; - const definition = prms ? prms(src, ctx.parent.globalContext) : { }; + const definition = prms ? prms(src, ctx.parent.globalContext) : {}; const values = transform.params ? transform.params : ParamDefinition.getDefaultValues(definition); return { definition, values }; } diff --git a/src/mol-state/tree/builder.ts b/src/mol-state/tree/builder.ts index 2eb22efd86b5797c1845f75603646ca94af8ff79..0cd0faa0b36088c79de318bd233bdba057a07c59 100644 --- a/src/mol-state/tree/builder.ts +++ b/src/mol-state/tree/builder.ts @@ -33,6 +33,10 @@ namespace StateTreeBuilder { return !!obj && typeof (obj as StateTreeBuilder).getTree === 'function'; } + export function isTo(obj: any): obj is StateTreeBuilder.To<any> { + return !!obj && typeof (obj as StateTreeBuilder).getTree === 'function' && typeof (obj as StateTreeBuilder.To<any>).ref === 'string'; + } + export class Root implements StateTreeBuilder { private state: State; get editInfo() { return this.state.editInfo; } diff --git a/src/mol-util/param-definition.ts b/src/mol-util/param-definition.ts index 4fb2e52bfd49a283ab63ca5cc73574f6cb9ea956..46d8abbfeb457cfbb5ae83f320374cb77cd266e2 100644 --- a/src/mol-util/param-definition.ts +++ b/src/mol-util/param-definition.ts @@ -14,13 +14,15 @@ export namespace ParamDefinition { export interface Info { label?: string, description?: string, + fieldLabels?: { [name: string]: string }, isHidden?: boolean, } function setInfo<T extends Info>(param: T, info?: Info): T { if (!info) return param; - if (info.description) param.description = info.description; if (info.label) param.label = info.label; + if (info.description) param.description = info.description; + if (info.fieldLabels) param.fieldLabels = info.fieldLabels; if (info.isHidden) param.isHidden = info.isHidden; return param; } diff --git a/src/mol-util/set.ts b/src/mol-util/set.ts index 34a79112f02b0d3afd5f86a440fd0ade65fa1709..3d5f648d9a58f12450353da24cd3f5cd9ade0dc7 100644 --- a/src/mol-util/set.ts +++ b/src/mol-util/set.ts @@ -22,7 +22,7 @@ export namespace SetUtils { return union; } - export function unionMany<T>(sets: Set<T>[]) { + export function unionMany<T>(...sets: Set<T>[]) { if (sets.length === 0) return new Set<T>(); if (sets.length === 1) return sets[0]; const union = new Set(sets[0]); diff --git a/src/mol-util/string.ts b/src/mol-util/string.ts index f2e8f5958ba30fd56124d1844cb478f45e1caa14..9e33986c6bf8f4c11fc773cb6e068df977b9dbaf 100644 --- a/src/mol-util/string.ts +++ b/src/mol-util/string.ts @@ -37,4 +37,13 @@ export function snakeCaseToWords(str: string) { export function stringToWords(str: string) { return capitalize(splitCamelCase(splitSnakeCase(str))) +} + +export function substringStartsWith(str: string, start: number, end: number, target: string) { + let len = target.length; + if (len > end - start) return false; + for (let i = 0; i < len; i++) { + if (str.charCodeAt(start + i) !== target.charCodeAt(i)) return false; + } + return true; } \ No newline at end of file diff --git a/src/perf-tests/lookup3d.ts b/src/perf-tests/lookup3d.ts index 1a18900644164459d29349e5c24156d364281e74..a625a6cd16187429723d8b36b6fbb0e29bc52d7d 100644 --- a/src/perf-tests/lookup3d.ts +++ b/src/perf-tests/lookup3d.ts @@ -2,11 +2,12 @@ import * as util from 'util' import * as fs from 'fs' import CIF from 'mol-io/reader/cif' -import { Structure, Model, Format } from 'mol-model/structure' +import { Structure } from 'mol-model/structure' import { GridLookup3D } from 'mol-math/geometry'; // import { sortArray } from 'mol-data/util'; import { OrderedSet } from 'mol-data/int'; +import { trajectoryFromMmCIF } from 'mol-model-formats/structure/mmcif'; require('util.promisify').shim(); const readFileAsync = util.promisify(fs.readFile); @@ -31,11 +32,10 @@ export async function readCIF(path: string) { throw parsed; } - const mmcif = Format.mmCIF(parsed.result.blocks[0]); - const models = await Model.create(mmcif).run(); + const models = await trajectoryFromMmCIF(parsed.result.blocks[0]).run(); const structures = models.map(Structure.ofModel); - return { mmcif: mmcif.data, models, structures }; + return { mmcif: models[0].sourceData.data, models, structures }; } export async function test() { diff --git a/src/perf-tests/structure.ts b/src/perf-tests/structure.ts index 7616ed526dd7b49747d35fdf445acd95830a80f3..48f9e54d5823f01af4f6d529a278513827004bab 100644 --- a/src/perf-tests/structure.ts +++ b/src/perf-tests/structure.ts @@ -11,11 +11,12 @@ import * as fs from 'fs' import fetch from 'node-fetch' import CIF from 'mol-io/reader/cif' -import { Structure, Model, Queries as Q, StructureElement, StructureSelection, StructureSymmetry, StructureQuery, Format, StructureProperties as SP } from 'mol-model/structure' +import { Structure, Model, Queries as Q, StructureElement, StructureSelection, StructureSymmetry, StructureQuery, StructureProperties as SP } from 'mol-model/structure' // import { Segmentation, OrderedSet } from 'mol-data/int' import to_mmCIF from 'mol-model/structure/export/mmcif' import { Vec3 } from 'mol-math/linear-algebra'; +import { trajectoryFromMmCIF } from 'mol-model-formats/structure/mmcif'; // import { printUnits } from 'apps/structure-info/model'; // import { EquivalenceClasses } from 'mol-data/util'; @@ -69,16 +70,12 @@ export async function readCIF(path: string) { } const data = parsed.result.blocks[0]; - console.time('schema') - const mmcif = Format.mmCIF(data); - - console.timeEnd('schema') console.time('buildModels') - const models = await Model.create(mmcif).run(); + const models = await trajectoryFromMmCIF(data).run(); console.timeEnd('buildModels') const structures = models.map(Structure.ofModel); - return { mmcif, models, structures }; + return { mmcif: models[0].sourceData.data, models, structures }; } const DATA_DIR = './build/data'; diff --git a/src/servers/model/server/structure-wrapper.ts b/src/servers/model/server/structure-wrapper.ts index 7c8bb1b255ddf845a0c86ac861332e39bd681755..615e57e758653a9aa27fd43ef896be644b3ed91b 100644 --- a/src/servers/model/server/structure-wrapper.ts +++ b/src/servers/model/server/structure-wrapper.ts @@ -4,7 +4,7 @@ * @author David Sehnal <david.sehnal@gmail.com> */ -import { Structure, Model, Format } from 'mol-model/structure'; +import { Structure, Model } from 'mol-model/structure'; import { PerformanceMonitor } from 'mol-util/performance-monitor'; import { Cache } from './cache'; import Config from '../config'; @@ -15,6 +15,7 @@ import * as zlib from 'zlib' import { Job } from './jobs'; import { ConsoleLogger } from 'mol-util/console-logger'; import { ModelPropertiesProvider } from '../property-provider'; +import { trajectoryFromMmCIF } from 'mol-model-formats/structure/mmcif'; require('util.promisify').shim(); @@ -108,7 +109,7 @@ export async function readStructureWrapper(key: string, sourceId: string | '_loc const frame = (await parseCif(data)).blocks[0]; perf.end('parse'); perf.start('createModel'); - const models = await Model.create(Format.mmCIF(frame)).run(); + const models = await trajectoryFromMmCIF(frame).run(); perf.end('createModel'); const modelMap = new Map<number, Model>(); diff --git a/src/servers/volume/pack.ts b/src/servers/volume/pack.ts index 47fb39c988ec8488df1ff0fa606924a9becf57ab..8be8f9afaa777466561df4ed1b27b509d951ca4f 100644 --- a/src/servers/volume/pack.ts +++ b/src/servers/volume/pack.ts @@ -32,7 +32,7 @@ function printHelp() { ` Optionally specify maximum block size.`, ``, ` node pack -em density.map output.mdb [-blockSize 96]`, - ` Pack single density into a block file.`, + ` Pack single density into a block file.`, ` Optionally specify maximum block size.` ]; console.log(help.join('\n')); diff --git a/tsconfig.json b/tsconfig.json index 70aa52c9b25f08ccd1f961c4cf42d4dc0138d4aa..0a845d1985969692496c7afa252226647952ee33 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -23,6 +23,7 @@ "mol-math": ["./mol-math"], "mol-model": ["./mol-model"], "mol-model-props": ["./mol-model-props", "./mol-model-props/index.ts"], + "mol-model-formats": ["./mol-model-formats"], "mol-ql": ["./mol-ql"], "mol-repr": ["./mol-repr"], "mol-script": ["./mol-script"],