diff --git a/.eslintrc.json b/.eslintrc.json index b9185e66b77456dec5f90b5a0ba8c4217ceb6766..28e80614867167fe9c80bb988dc5fa9114552072 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -14,8 +14,9 @@ "rules": { "@typescript-eslint/ban-types": "warn", "@typescript-eslint/class-name-casing": "off", + "indent": "off", "@typescript-eslint/indent": [ - "warn", + "error", 4 ], "@typescript-eslint/member-delimiter-style": [ @@ -33,7 +34,7 @@ ], "@typescript-eslint/prefer-namespace-keyword": "warn", "@typescript-eslint/quotes": [ - "warn", + "error", "single", { "avoidEscape": true, @@ -44,22 +45,27 @@ "off", null ], - "@typescript-eslint/type-annotation-spacing": "warn", + "@typescript-eslint/type-annotation-spacing": "error", "arrow-parens": [ "off", "as-needed" ], + "brace-style": "off", + "@typescript-eslint/brace-style": [ + "error", + "1tbs", { "allowSingleLine": true } + ], "comma-dangle": "off", "eqeqeq": [ - "warn", + "error", "smart" ], "import/order": "off", "no-eval": "warn", "no-new-wrappers": "warn", - "no-trailing-spaces": "warn", + "no-trailing-spaces": "error", "no-unsafe-finally": "warn", - "no-var": "warn", - "spaced-comment": "warn" + "no-var": "error", + "spaced-comment": "error" } } \ No newline at end of file diff --git a/data/cif-field-names/cif-core-field-names.csv b/data/cif-field-names/cif-core-field-names.csv index 25ce0320c9e0e7c60a833309094d6a27814b7a4c..f935944d7166915cf4c16bd567919d42fe85e6d2 100644 --- a/data/cif-field-names/cif-core-field-names.csv +++ b/data/cif-field-names/cif-core-field-names.csv @@ -1,6 +1,12 @@ audit.block_doi database_code.depnum_ccdc_archive +database_code.depnum_ccdc_fiz +database_code.ICSD +database_code.MDF +database_code.NBS +database_code.CSD +database_code.COD chemical.name_systematic chemical.name_common @@ -19,6 +25,7 @@ atom_type_scat.source space_group.crystal_system space_group.name_H-M_full +space_group.IT_number space_group_symop.operation_xyz cell.length_a @@ -42,19 +49,28 @@ atom_site.calc_flag atom_site.refinement_flags atom_site.disorder_assembly atom_site.disorder_group - atom_site.site_symmetry_multiplicity atom_site_aniso.label +atom_site_aniso.U atom_site_aniso.U_11 atom_site_aniso.U_22 atom_site_aniso.U_33 atom_site_aniso.U_23 atom_site_aniso.U_13 atom_site_aniso.U_12 +atom_site_aniso.U_su +atom_site_aniso.U_11_su +atom_site_aniso.U_22_su +atom_site_aniso.U_33_su +atom_site_aniso.U_23_su +atom_site_aniso.U_13_su +atom_site_aniso.U_12_su geom_bond.atom_site_label_1 geom_bond.atom_site_label_2 geom_bond.distance +geom_bond.site_symmetry_1 geom_bond.site_symmetry_2 -geom_bond.publ_flag \ No newline at end of file +geom_bond.publ_flag +geom_bond.valence \ No newline at end of file diff --git a/package.json b/package.json index 34f59326c9d625d3b5eb83ea8e5da411d337b021..895748255601adfe3995d9b74f0c552b4eab1356 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,7 @@ "url": "https://github.com/molstar/molstar/issues" }, "scripts": { - "lint": "eslint src/**/*.ts", + "lint": "eslint src/**/*.{ts,tsx}", "test": "npm run lint && jest", "build": "npm run build-tsc && npm run build-extra && npm run build-webpack", "build-tsc": "tsc --incremental", diff --git a/src/apps/cifschema/index.ts b/src/apps/cifschema/index.ts index 38602d919e917f21b660a37f6171afc65008f6e4..479192198c741b8d009b7b39c3fa8a6a1a54b290 100644 --- a/src/apps/cifschema/index.ts +++ b/src/apps/cifschema/index.ts @@ -159,7 +159,7 @@ async function ensureDicAvailable(dicPath: string, dicUrl: string) { } } -const DIC_DIR = path.resolve(__dirname, '../dics/') +const DIC_DIR = path.resolve(__dirname, '../../../build/dics/') const MMCIF_DIC_PATH = `${DIC_DIR}/mmcif_pdbx_v50.dic` const MMCIF_DIC_URL = 'http://mmcif.wwpdb.org/dictionaries/ascii/mmcif_pdbx_v50.dic' const IHM_DIC_PATH = `${DIC_DIR}/ihm-extension.dic` diff --git a/src/apps/cifschema/util/cif-dic.ts b/src/apps/cifschema/util/cif-dic.ts index 2f43e75a00009ae4a2ab604c52c1fd401e6112db..0fd9318731f554c1978a440c3ba39227c4f44885 100644 --- a/src/apps/cifschema/util/cif-dic.ts +++ b/src/apps/cifschema/util/cif-dic.ts @@ -232,6 +232,32 @@ const FORCE_INT_FIELDS = [ '_struct_sheet_range.end_auth_seq_id', ]; +const FORCE_MATRIX_FIELDS_MAP: { [k: string]: string } = { + 'atom_site_aniso.U_11': 'U', + 'atom_site_aniso.U_22': 'U', + 'atom_site_aniso.U_33': 'U', + 'atom_site_aniso.U_23': 'U', + 'atom_site_aniso.U_13': 'U', + 'atom_site_aniso.U_12': 'U', + 'atom_site_aniso.U_11_su': 'U_su', + 'atom_site_aniso.U_22_su': 'U_su', + 'atom_site_aniso.U_33_su': 'U_su', + 'atom_site_aniso.U_23_su': 'U_su', + 'atom_site_aniso.U_13_su': 'U_su', + 'atom_site_aniso.U_12_su': 'U_su', +} +const FORCE_MATRIX_FIELDS = Object.keys(FORCE_MATRIX_FIELDS_MAP) + +const EXTRA_ALIASES: Database['aliases'] = { + 'atom_site_aniso.U': [ + 'atom_site_anisotrop_U' + ], + 'atom_site_aniso.U_su': [ + 'atom_site_aniso_U_esd', + 'atom_site_anisotrop_U_esd', + ], +} + const COMMA_SEPARATED_LIST_FIELDS = [ '_atom_site.pdbx_struct_group_id', '_chem_comp.mon_nstd_parent_comp_id', @@ -280,9 +306,8 @@ const EXTRA_ENUM_VALUES: { [k: string]: string[] } = { } export function generateSchema(frames: CifFrame[], imports: Imports = new Map()): Database { - const tables: Database['tables'] = {} - const aliases: Database['aliases'] = {} + const aliases: Database['aliases'] = { ...EXTRA_ALIASES } const categories: FrameCategories = {} const links: FrameLinks = {} @@ -291,7 +316,7 @@ export function generateSchema(frames: CifFrame[], imports: Imports = new Map()) // get category metadata frames.forEach(d => { // category definitions in mmCIF start with '_' and don't include a '.' - // category definitions in cif don't include a '.' + // category definitions in cifCore don't include a '.' if (d.header[0] === '_' || d.header.includes('.')) return const categoryName = d.header.toLowerCase() // console.log(d.header, d.categoryNames, d.categories) @@ -399,6 +424,10 @@ export function generateSchema(frames: CifFrame[], imports: Imports = new Map()) } else if (FORCE_INT_FIELDS.includes(d.header)) { fields[itemName] = IntCol(description) console.log(`forcing int: ${d.header}`) + } else if (FORCE_MATRIX_FIELDS.includes(d.header)) { + fields[itemName] = FloatCol(description) + fields[FORCE_MATRIX_FIELDS_MAP[d.header]] = MatrixCol(3, 3, description) + console.log(`forcing matrix: ${d.header}`) } else if (subCategory === 'matrix') { fields[itemName.replace(reMatrixField, '')] = MatrixCol(3, 3, description) } else if (subCategory === 'vector') { diff --git a/src/apps/viewer/extensions/cellpack/model.ts b/src/apps/viewer/extensions/cellpack/model.ts index 0c985c078f74bf9d493b8182ac30839aca3ce79e..e43e7e5144c8472d01c49cef7f6ef8b7d86d647a 100644 --- a/src/apps/viewer/extensions/cellpack/model.ts +++ b/src/apps/viewer/extensions/cellpack/model.ts @@ -321,20 +321,19 @@ async function handleHivRna(ctx: { runtime: RuntimeContext, fetch: AjaxTask }, p async function loadHivMembrane(plugin: PluginContext, runtime: RuntimeContext, state: State, params: LoadCellPackModelParams) { const url = `${params.baseUrl}/membranes/hiv_lipids.bcif` - const membraneBuilder = state.build().toRoot() + const membrane = await state.build().toRoot() .apply(StateTransforms.Data.Download, { label: 'hiv_lipids', url, isBinary: true }, { state: { isGhost: true } }) .apply(StateTransforms.Data.ParseCif, undefined, { state: { isGhost: true } }) .apply(StateTransforms.Model.TrajectoryFromMmCif, undefined, { state: { isGhost: true } }) .apply(StateTransforms.Model.ModelFromTrajectory, undefined, { state: { isGhost: true } }) .apply(StateTransforms.Model.StructureFromModel) - await state.updateTree(membraneBuilder).runInContext(runtime) + .commit() const membraneParams = { representation: params.preset.representation, } - const membrane = state.build().to(membraneBuilder.ref) - await plugin.updateDataState(membrane, { revertOnError: true }); - await CellpackMembranePreset.apply(membrane.selector, membraneParams, plugin) + + await CellpackMembranePreset.apply(membrane, membraneParams, plugin) } async function loadPackings(plugin: PluginContext, runtime: RuntimeContext, state: State, params: LoadCellPackModelParams) { @@ -345,6 +344,10 @@ async function loadPackings(plugin: PluginContext, runtime: RuntimeContext, stat .apply(StateTransforms.Data.Download, { url, isBinary: false, label: params.source.params }, { state: { isGhost: true } }) } else { const file = params.source.params + if (file === null) { + plugin.log.error('No file selected') + return + } cellPackJson = state.build().toRoot() .apply(StateTransforms.Data.ReadFile, { file, isBinary: false, label: file.name }, { state: { isGhost: true } }) } @@ -354,17 +357,19 @@ async function loadPackings(plugin: PluginContext, runtime: RuntimeContext, stat .apply(ParseCellPack) const cellPackObject = await state.updateTree(cellPackBuilder).runInContext(runtime) - const { packings } = cellPackObject.data + const { packings } = cellPackObject.obj!.data await handleHivRna({ runtime, fetch: plugin.fetch }, packings, params.baseUrl) for (let i = 0, il = packings.length; i < il; ++i) { const p = { packing: i, baseUrl: params.baseUrl, ingredientFiles: params.ingredients.files } - const packing = state.build().to(cellPackBuilder.ref).apply(StructureFromCellpack, p) - await plugin.updateDataState(packing, { revertOnError: true }); + const packing = await state.build() + .to(cellPackBuilder.ref) + .apply(StructureFromCellpack, p) + .commit({ revertOnError: true }) - const structure = packing.selector.obj?.data + const structure = packing.obj?.data if (structure) { await CellPackInfoProvider.attach({ fetch: plugin.fetch, runtime }, structure, { info: { packingsCount: packings.length, packingIndex: i } @@ -375,7 +380,7 @@ async function loadPackings(plugin: PluginContext, runtime: RuntimeContext, stat traceOnly: params.preset.traceOnly, representation: params.preset.representation, } - await CellpackPackingPreset.apply(packing.selector, packingParams, plugin) + await CellpackPackingPreset.apply(packing, packingParams, plugin) } } diff --git a/src/apps/viewer/extensions/cellpack/preset.ts b/src/apps/viewer/extensions/cellpack/preset.ts index fdb94b64f8dd8d31e7431ff557e11b9b48c37ecf..fd6a2361da6931152d0ea1d692906d1a454e9f1e 100644 --- a/src/apps/viewer/extensions/cellpack/preset.ts +++ b/src/apps/viewer/extensions/cellpack/preset.ts @@ -46,7 +46,7 @@ export const CellpackPackingPreset = StructureRepresentationPresetProvider({ polymer: builder.buildRepresentation<any>(update, components.polymer, { type: params.representation, typeParams: { ...typeParams, ...reprProps }, color }, { tag: 'polymer' }) }; - await plugin.updateDataState(update, { revertOnError: true }); + await update.commit({ revertOnError: true }) return { components, representations }; } }); @@ -84,7 +84,7 @@ export const CellpackMembranePreset = StructureRepresentationPresetProvider({ membrane: builder.buildRepresentation(update, components.membrane, { type: 'gaussian-surface', typeParams: { ...typeParams, ...reprProps }, color: 'uniform', colorParams: { value: ColorNames.lightgrey } }, { tag: 'all' }) }; - await plugin.updateDataState(update, { revertOnError: true }); + await update.commit({ revertOnError: true }) return { components, representations }; } }); \ No newline at end of file diff --git a/src/apps/viewer/extensions/cellpack/state.ts b/src/apps/viewer/extensions/cellpack/state.ts index abd983c26808377cda7878eaa28a4c12a889c802..9908ede2657560f99f05847ea0f042e4453fc9bf 100644 --- a/src/apps/viewer/extensions/cellpack/state.ts +++ b/src/apps/viewer/extensions/cellpack/state.ts @@ -70,9 +70,11 @@ const StructureFromCellpack = PluginStateTransform.BuiltIn({ return Task.create('Structure from CellPack', async ctx => { const packing = a.data.packings[params.packing] const ingredientFiles: IngredientFiles = {} - for (let i = 0, il = params.ingredientFiles.length; i < il; ++i) { - const file = params.ingredientFiles.item(i) - if (file) ingredientFiles[file.name] = file + if (params.ingredientFiles !== null) { + for (let i = 0, il = params.ingredientFiles.length; i < il; ++i) { + const file = params.ingredientFiles.item(i) + if (file) ingredientFiles[file.name] = file + } } const structure = await createStructureFromCellPack(packing, params.baseUrl, ingredientFiles).runInContext(ctx) return new PSO.Molecule.Structure(structure, { label: packing.name }) diff --git a/src/examples/proteopedia-wrapper/ui/controls.tsx b/src/examples/proteopedia-wrapper/ui/controls.tsx index ed402e5a13b47e441936f98f68dc8b5e058851c4..df970158b11b8c3b91a51caa96a110a265bb4303 100644 --- a/src/examples/proteopedia-wrapper/ui/controls.tsx +++ b/src/examples/proteopedia-wrapper/ui/controls.tsx @@ -13,7 +13,6 @@ import { StateElements } from '../helpers'; export function volumeStreamingControls(plugin: PluginContext, parent: Element) { ReactDOM.render(<PluginContextContainer plugin={plugin}> - <TransformUpdaterControl nodeRef={StateElements.VolumeStreaming} /> - </PluginContextContainer>, - parent); + <TransformUpdaterControl nodeRef={StateElements.VolumeStreaming} /> + </PluginContextContainer>, parent); } \ No newline at end of file diff --git a/src/mol-data/generic/linked-list.ts b/src/mol-data/generic/linked-list.ts index fbea58bd8d032c0b3aa6526df4ed8af6ba74e9a7..efd64e347c07ed662cf70248ebb73619c60c19fc 100644 --- a/src/mol-data/generic/linked-list.ts +++ b/src/mol-data/generic/linked-list.ts @@ -89,15 +89,13 @@ class LinkedListImpl<T> implements LinkedList<T> { if (node.previous !== null) { node.previous.next = node.next; - } - else if (/* first == item*/ node.previous === null) { + } else if (/* first == item*/ node.previous === null) { this.first = node.next; } if (node.next !== null) { node.next.previous = node.previous; - } - else if (/* last == item*/ node.next === null) { + } else if (/* last == item*/ node.next === null) { this.last = node.previous; } diff --git a/src/mol-data/int/impl/ordered-set.ts b/src/mol-data/int/impl/ordered-set.ts index 858d4abf7c15e29c1c81986db8ca8f97f2e2aac2..47a91eed2de4d0f8e2ffd07a6f7e29cab24e25a7 100644 --- a/src/mol-data/int/impl/ordered-set.ts +++ b/src/mol-data/int/impl/ordered-set.ts @@ -150,8 +150,11 @@ function unionII(a: I, b: I) { const minA = I.min(a), minB = I.min(b); if (areRangesIntersecting(a, b)) return I.ofRange(Math.min(minA, minB), Math.max(I.max(a), I.max(b))); let lSize, lMin, rSize, rMin; - if (minA < minB) { lSize = sizeA; lMin = minA; rSize = sizeB; rMin = minB; } - else { lSize = sizeB; lMin = minB; rSize = sizeA; rMin = minA; } + if (minA < minB) { + lSize = sizeA; lMin = minA; rSize = sizeB; rMin = minB; + } else { + lSize = sizeB; lMin = minB; rSize = sizeA; rMin = minA; + } const arr = new Int32Array(sizeA + sizeB); for (let i = 0; i < lSize; i++) arr[i] = i + lMin; for (let i = 0; i < rSize; i++) arr[i + lSize] = i + rMin; @@ -328,9 +331,13 @@ export function indexedIntersect(idxA: OrderedSetImpl, a: S, b: S): OrderedSetIm let j = startJ; while (O < lenI && j < endJ) { const x = a[getAt(idxA, O)], y = b[j]; - if (x < y) { O++; } - else if (x > y) { j++; } - else { commonCount++; O++; j++; } + if (x < y) { + O++; + } else if (x > y) { + j++; + } else { + commonCount++; O++; j++; + } } // no common elements @@ -345,9 +352,13 @@ export function indexedIntersect(idxA: OrderedSetImpl, a: S, b: S): OrderedSetIm j = startJ; while (O < lenI && j < endJ) { const x = a[getAt(idxA, O)], y = b[j]; - if (x < y) { O++; } - else if (x > y) { j++; } - else { indices[offset++] = j; O++; j++; } + if (x < y) { + O++; + } else if (x > y) { + j++; + } else { + indices[offset++] = j; O++; j++; + } } return ofSortedArray(indices); diff --git a/src/mol-data/int/impl/sorted-array.ts b/src/mol-data/int/impl/sorted-array.ts index 35e83338e1469e4ec62720d081bba2c4a68851ad..87eacf6b0b8c91243f00447a118b3b69cb801e67 100644 --- a/src/mol-data/int/impl/sorted-array.ts +++ b/src/mol-data/int/impl/sorted-array.ts @@ -146,8 +146,8 @@ export function areIntersecting(a: Nums, b: Nums) { let { startI: i, startJ: j, endI, endJ } = getSuitableIntersectionRange(a, b); while (i < endI && j < endJ) { const x = a[i], y = b[j]; - if (x < y) { i++; } - else if (x > y) { j++; } + if (x < y) i++; + else if (x > y) j++; else return true; } return false; @@ -164,9 +164,13 @@ export function isSubset(a: Nums, b: Nums) { let equal = 0; while (i < endI && j < endJ) { const x = a[i], y = b[j]; - if (x < y) { i++; } - else if (x > y) { j++; } - else { i++; j++; equal++; } + if (x < y) { + i++; + } else if (x > y) { + j++; + } else { + i++; j++; equal++; + } } return equal === lenB; } @@ -197,9 +201,13 @@ export function union(a: Nums, b: Nums): Nums { // insert the common part while (i < endI && j < endJ) { const x = a[i], y = b[j]; - if (x < y) { indices[offset++] = x; i++; } - else if (x > y) { indices[offset++] = y; j++; } - else { indices[offset++] = x; i++; j++; } + if (x < y) { + indices[offset++] = x; i++; + } else if (x > y) { + indices[offset++] = y; j++; + } else { + indices[offset++] = x; i++; j++; + } } // insert the remaining common part @@ -224,9 +232,13 @@ function getCommonCount(a: Nums, b: Nums, startI: number, startJ: number, endI: let commonCount = 0; while (i < endI && j < endJ) { const x = a[i], y = b[j]; - if (x < y) { i++; } - else if (x > y) { j++; } - else { i++; j++; commonCount++; } + if (x < y) { + i++; + } else if (x > y) { + j++; + } else { + i++; j++; commonCount++; + } } return commonCount; } @@ -251,9 +263,13 @@ export function intersect(a: Nums, b: Nums) { let j = startJ; while (i < endI && j < endJ) { const x = a[i], y = b[j]; - if (x < y) { i++; } - else if (x > y) { j++; } - else { indices[offset++] = x; i++; j++; } + if (x < y) { + i++; + } else if (x > y) { + j++; + } else { + indices[offset++] = x; i++; j++; + } } return ofSortedArray(indices); @@ -268,9 +284,13 @@ export function subtract(a: Nums, b: Nums) { let commonCount = 0; while (i < endI && j < endJ) { const x = a[i], y = b[j]; - if (x < y) { i++; } - else if (x > y) { j++; } - else { i++; j++; commonCount++; } + if (x < y) { + i++; + } else if (x > y) { + j++; + } else { + i++; j++; commonCount++; + } } // A isnt intersecting B ===> A @@ -288,9 +308,13 @@ export function subtract(a: Nums, b: Nums) { j = sJ; while (i < endI && j < endJ) { const x = a[i], y = b[j]; - if (x < y) { indices[offset++] = x; i++; } - else if (x > y) { j++; } - else { i++; j++; } + if (x < y) { + indices[offset++] = x; i++; + } else if (x > y) { + j++; + } else { + i++; j++; + } } // insert the "tail" @@ -323,9 +347,13 @@ export function indicesOf(a: Nums, b: Nums): Nums { let commonCount = 0; while (i < endI && j < endJ) { const x = a[i], y = b[j]; - if (x < y) { i++; } - else if (x > y) { j++; } - else { i++; j++; commonCount++; } + if (x < y) { + i++; + } else if (x > y) { + j++; + } else { + i++; j++; commonCount++; + } } const lenA = a.length; @@ -340,9 +368,13 @@ export function indicesOf(a: Nums, b: Nums): Nums { j = sJ; while (i < endI && j < endJ) { const x = a[i], y = b[j]; - if (x < y) { i++; } - else if (x > y) { j++; } - else { indices[offset++] = i; i++; j++; } + if (x < y) { + i++; + } else if (x > y) { + j++; + } else { + indices[offset++] = i; i++; j++; + } } return ofSortedArray(indices); diff --git a/src/mol-io/common/msgpack/encode.ts b/src/mol-io/common/msgpack/encode.ts index 35231843a4eeb62d41f8a58d8242bc2eb42afd22..9fa997674258233b3d1b089c4469fcc179e8f6ef 100644 --- a/src/mol-io/common/msgpack/encode.ts +++ b/src/mol-io/common/msgpack/encode.ts @@ -88,8 +88,7 @@ function encodedSize(value: any) { for (let i = 0; i < length; i++) { size += encodedSize(value[i]); } - } - else { + } else { let keys = Object.keys(value); length = keys.length; for (let i = 0; i < length; i++) { @@ -257,8 +256,7 @@ function encodeInternal(value: any, view: DataView, bytes: Uint8Array, offset: n if (isArray) { length = value.length; - } - else { + } else { keys = Object.keys(value); length = keys.length; } @@ -266,13 +264,11 @@ function encodeInternal(value: any, view: DataView, bytes: Uint8Array, offset: n if (length < 0x10) { view.setUint8(offset, length | (isArray ? 0x90 : 0x80)); size = 1; - } - else if (length < 0x10000) { + } else if (length < 0x10000) { view.setUint8(offset, isArray ? 0xdc : 0xde); view.setUint16(offset + 1, length); size = 3; - } - else if (length < 0x100000000) { + } else if (length < 0x100000000) { view.setUint8(offset, isArray ? 0xdd : 0xdf); view.setUint32(offset + 1, length); size = 5; @@ -282,8 +278,7 @@ function encodeInternal(value: any, view: DataView, bytes: Uint8Array, offset: n for (let i = 0; i < length; i++) { size += encodeInternal(value[i], view, bytes, offset + size); } - } - else { + } else { for (let i = 0, _i = keys!.length; i < _i; i++) { const key = keys![i]; size += encodeInternal(key, view, bytes, offset + size); diff --git a/src/mol-io/common/utf8.ts b/src/mol-io/common/utf8.ts index 83cc3f0754f1e5a7b5d28303c697aa59903d52c0..706fe90db5492faf6f8980958504c5a5aa29ae15 100644 --- a/src/mol-io/common/utf8.ts +++ b/src/mol-io/common/utf8.ts @@ -59,24 +59,21 @@ function _utf8Read(data: Uint8Array, offset: number, length: number) { for (let i = offset, end = offset + length; i < end; i++) { let byte = data[i]; - // One byte character if ((byte & 0x80) === 0x00) { + // One byte character chunk[chunkOffset++] = chars[byte]; - } - // Two byte character - else if ((byte & 0xe0) === 0xc0) { + } else if ((byte & 0xe0) === 0xc0) { + // Two byte character chunk[chunkOffset++] = chars[((byte & 0x0f) << 6) | (data[++i] & 0x3f)]; - } - // Three byte character - else if ((byte & 0xf0) === 0xe0) { + } else if ((byte & 0xf0) === 0xe0) { + // Three byte character chunk[chunkOffset++] = String.fromCharCode( ((byte & 0x0f) << 12) | ((data[++i] & 0x3f) << 6) | ((data[++i] & 0x3f) << 0) ); - } - // Four byte character - else if ((byte & 0xf8) === 0xf0) { + } else if ((byte & 0xf8) === 0xf0) { + // Four byte character chunk[chunkOffset++] = String.fromCharCode( ((byte & 0x07) << 18) | ((data[++i] & 0x3f) << 12) | diff --git a/src/mol-io/reader/cif/data-model.ts b/src/mol-io/reader/cif/data-model.ts index d354d65aaae2f27b4013590f1c2e9a73cfe7eaf5..fa7223cf6ebb326a6e20a4f615cf263a4f4b99cb 100644 --- a/src/mol-io/reader/cif/data-model.ts +++ b/src/mol-io/reader/cif/data-model.ts @@ -267,21 +267,40 @@ export namespace CifField { } } -export function getTensor(category: CifCategory, field: string, space: Tensor.Space, row: number, zeroIndexed: boolean): Tensor.Data { - const ret = space.create(); +export function tensorFieldNameGetter(field: string, rank: number, zeroIndexed: boolean, namingVariant: 'brackets' | 'underscore') { const offset = zeroIndexed ? 0 : 1; + switch (rank) { + case 1: + return namingVariant === 'brackets' + ? (i: number) => `${field}[${i + offset}]` + : (i: number) => `${field}_${i + offset}` + case 2: + return namingVariant === 'brackets' + ? (i: number, j: number) => `${field}[${i + offset}][${j + offset}]` + : (i: number, j: number) => `${field}_${i + offset}${j + offset}` + case 3: + return namingVariant === 'brackets' + ? (i: number, j: number, k: number) => `${field}[${i + offset}][${j + offset}][${k + offset}]` + : (i: number, j: number, k: number) => `${field}_${i + offset}${j + offset}${k + offset}` + default: + throw new Error('Tensors with rank > 3 or rank 0 are currently not supported.'); + } +} + +export function getTensor(category: CifCategory, space: Tensor.Space, row: number, getName: (...args: number[]) => string): Tensor.Data { + const ret = space.create(); if (space.rank === 1) { const rows = space.dimensions[0]; for (let i = 0; i < rows; i++) { - const f = category.getField(`${field}[${i + offset}]`); + const f = category.getField(getName(i)); space.set(ret, i, !!f ? f.float(row) : 0.0); } } else if (space.rank === 2) { const rows = space.dimensions[0], cols = space.dimensions[1]; for (let i = 0; i < rows; i++) { for (let j = 0; j < cols; j++) { - const f = category.getField(`${field}[${i + offset}][${j + offset}]`); + const f = category.getField(getName(i, j)); space.set(ret, i, j, !!f ? f.float(row) : 0.0); } } @@ -290,12 +309,14 @@ export function getTensor(category: CifCategory, field: string, space: Tensor.Sp for (let i = 0; i < d0; i++) { for (let j = 0; j < d1; j++) { for (let k = 0; k < d2; k++) { - const f = category.getField(`${field}[${i + offset}][${j + offset}][${k + offset}]`); + const f = category.getField(getName(i, j, k)); space.set(ret, i, j, k, !!f ? f.float(row) : 0.0); } } } - } else throw new Error('Tensors with rank > 3 or rank 0 are currently not supported.'); + } else { + throw new Error('Tensors with rank > 3 or rank 0 are currently not supported.'); + } return ret; } diff --git a/src/mol-io/reader/cif/schema.ts b/src/mol-io/reader/cif/schema.ts index f465a8fd29828e99fe396cdaea1756325592afc8..1fba5439b4d564e612167f08d6b6f2ca86d8843c 100644 --- a/src/mol-io/reader/cif/schema.ts +++ b/src/mol-io/reader/cif/schema.ts @@ -88,18 +88,21 @@ function createListColumn<T extends number | string>(schema: Column.Schema.List< function createTensorColumn(schema: Column.Schema.Tensor, category: Data.CifCategory, key: string): Column<Tensor.Data> { const space = schema.space; - const zeroOffset = category.fieldNames.indexOf(`${key}[0]`) >= 0; + const zeroOffset = ( + category.fieldNames.includes(`${key}[0]`) || + category.fieldNames.includes(`${key}[0][0]`) || + category.fieldNames.includes(`${key}[0][0][0]`) + ); const fst = zeroOffset ? 0 : 1; - - let firstFieldName: string; - switch (space.rank) { - case 1: firstFieldName = `${key}[${fst}]`; break; - case 2: firstFieldName = `${key}[${fst}][${fst}]`; break; - case 3: firstFieldName = `${key}[${fst}][${fst}][${fst}]`; break; - default: throw new Error('Tensors with rank > 3 or rank 0 are currently not supported.'); - } - const first = category.getField(firstFieldName) || Column.Undefined(category.rowCount, schema); - const value = (row: number) => Data.getTensor(category, key, space, row, zeroOffset); + const namingVariant = ( + category.fieldNames.includes(`${key}_1`) || + category.fieldNames.includes(`${key}_11`) || + category.fieldNames.includes(`${key}_111`) + ) ? 'underscore' : 'brackets' + + const getName = Data.tensorFieldNameGetter(key, space.rank, zeroOffset, namingVariant) + const first = category.getField(getName(fst, fst, fst)) || Column.Undefined(category.rowCount, schema); + const value = (row: number) => Data.getTensor(category, space, row, getName); const toArray: Column<Tensor.Data>['toArray'] = params => ColumnHelpers.createAndFillArray(category.rowCount, value, params) return { diff --git a/src/mol-io/reader/cif/schema/cif-core.ts b/src/mol-io/reader/cif/schema/cif-core.ts index 6b6cfe4c72dfe0a3dba41ca013e32dc00aee3e3b..940ddc947e39b5933e1b8520c8224c27b98aed2e 100644 --- a/src/mol-io/reader/cif/schema/cif-core.ts +++ b/src/mol-io/reader/cif/schema/cif-core.ts @@ -13,6 +13,7 @@ import Schema = Column.Schema const int = Schema.int; const float = Schema.float; const str = Schema.str; +const Matrix = Schema.Matrix; export const CifCore_Schema = { /** @@ -181,6 +182,14 @@ export const CifCore_Schema = { * trigonal system. */ crystal_system: str, + /** + * The number as assigned in International Tables for Crystallography + * Vol A, specifying the proper affine class (i.e. the orientation + * preserving affine class) of space groups (crystallographic space + * group type) to which the space group belongs. This number defines + * the space group type but not the coordinate system expressed. + */ + IT_number: int, /** * The full international Hermann-Mauguin space-group symbol as * defined in Section 2.2.3 and given as the second item of the @@ -272,6 +281,29 @@ export const CifCore_Schema = { * publication or should be placed in a table of significant angles. */ publ_flag: str, + /** + * The set of data items which specify the symmetry operation codes + * which must be applied to the atom sites involved in the geometry angle. + * + * The symmetry code of each atom site as the symmetry-equivalent position + * number 'n' and the cell translation number 'pqr'. These numbers are + * combined to form the code 'n pqr' or n_pqr. + * + * The character string n_pqr is composed as follows: + * + * n refers to the symmetry operation that is applied to the + * coordinates stored in _atom_site.fract_xyz. It must match a + * number given in _symmetry_equiv.pos_site_id. + * + * p, q and r refer to the translations that are subsequently + * applied to the symmetry transformed coordinates to generate + * the atom used in calculating the angle. These translations + * (x,y,z) are related to (p,q,r) by the relations + * p = 5 + x + * q = 5 + y + * r = 5 + z + */ + site_symmetry_1: str, /** * The set of data items which specify the symmetry operation codes * which must be applied to the atom sites involved in the geometry angle. @@ -295,6 +327,10 @@ export const CifCore_Schema = { * r = 5 + z */ site_symmetry_2: str, + /** + * Bond valence calculated from the bond distance. + */ + valence: float, }, /** * The CATEGORY of data items used to record details about the @@ -329,12 +365,38 @@ export const CifCore_Schema = { * originate from that source. */ database_code: { + /** + * Code assigned by Crystallography Open Database (COD). + */ + COD: str, + /** + * Code assigned by the Cambridge Structural Database. + */ + CSD: str, /** * Deposition numbers assigned by the Cambridge Crystallographic * Data Centre (CCDC) to files containing structural information * archived by the CCDC. */ depnum_ccdc_archive: str, + /** + * Deposition numbers assigned by the Fachinformationszentrum + * Karlsruhe (FIZ) to files containing structural information + * archived by the Cambridge Crystallographic Data Centre (CCDC). + */ + depnum_ccdc_fiz: str, + /** + * Code assigned by the Inorganic Crystal Structure Database. + */ + ICSD: str, + /** + * Code assigned in the Metals Data File. + */ + MDF: str, + /** + * Code assigned by the NBS (NIST) Crystal Data Database. + */ + NBS: str, }, /** * The CATEGORY of data items used to describe atom site information @@ -476,6 +538,33 @@ export const CifCore_Schema = { * The unique elements of the real symmetric matrix are entered by row. */ U_11: float, + /** + * These are the standard anisotropic atomic displacement + * components in angstroms squared which appear in the + * structure factor term: + * + * T = exp{-2pi^2^ sum~i~ [sum~j~ (U^ij^ h~i~ h~j~ a*~i~ a*~j~) ] } + * + * h = the Miller indices + * a* = the reciprocal-space cell lengths + * + * The unique elements of the real symmetric matrix are entered by row. + */ + U: Matrix(3, 3), + /** + * These are the standard uncertainty values (SU) for the standard + * form of the Uij anisotropic atomic displacement components (see + * _aniso_UIJ. Because these values are TYPE measurand, the su values + * may in practice be auto generated as part of the Uij calculation. + */ + U_11_su: float, + /** + * These are the standard uncertainty values (SU) for the standard + * form of the Uij anisotropic atomic displacement components (see + * _aniso_UIJ. Because these values are TYPE measurand, the su values + * may in practice be auto generated as part of the Uij calculation. + */ + U_su: Matrix(3, 3), /** * These are the standard anisotropic atomic displacement * components in angstroms squared which appear in the @@ -489,6 +578,13 @@ export const CifCore_Schema = { * The unique elements of the real symmetric matrix are entered by row. */ U_12: float, + /** + * These are the standard uncertainty values (SU) for the standard + * form of the Uij anisotropic atomic displacement components (see + * _aniso_UIJ. Because these values are TYPE measurand, the su values + * may in practice be auto generated as part of the Uij calculation. + */ + U_12_su: float, /** * These are the standard anisotropic atomic displacement * components in angstroms squared which appear in the @@ -502,6 +598,13 @@ export const CifCore_Schema = { * The unique elements of the real symmetric matrix are entered by row. */ U_13: float, + /** + * These are the standard uncertainty values (SU) for the standard + * form of the Uij anisotropic atomic displacement components (see + * _aniso_UIJ. Because these values are TYPE measurand, the su values + * may in practice be auto generated as part of the Uij calculation. + */ + U_13_su: float, /** * These are the standard anisotropic atomic displacement * components in angstroms squared which appear in the @@ -515,6 +618,13 @@ export const CifCore_Schema = { * The unique elements of the real symmetric matrix are entered by row. */ U_22: float, + /** + * These are the standard uncertainty values (SU) for the standard + * form of the Uij anisotropic atomic displacement components (see + * _aniso_UIJ. Because these values are TYPE measurand, the su values + * may in practice be auto generated as part of the Uij calculation. + */ + U_22_su: float, /** * These are the standard anisotropic atomic displacement * components in angstroms squared which appear in the @@ -528,6 +638,13 @@ export const CifCore_Schema = { * The unique elements of the real symmetric matrix are entered by row. */ U_23: float, + /** + * These are the standard uncertainty values (SU) for the standard + * form of the Uij anisotropic atomic displacement components (see + * _aniso_UIJ. Because these values are TYPE measurand, the su values + * may in practice be auto generated as part of the Uij calculation. + */ + U_23_su: float, /** * These are the standard anisotropic atomic displacement * components in angstroms squared which appear in the @@ -541,6 +658,13 @@ export const CifCore_Schema = { * The unique elements of the real symmetric matrix are entered by row. */ U_33: float, + /** + * These are the standard uncertainty values (SU) for the standard + * form of the Uij anisotropic atomic displacement components (see + * _aniso_UIJ. Because these values are TYPE measurand, the su values + * may in practice be auto generated as part of the Uij calculation. + */ + U_33_su: float, }, /** * The CATEGORY of data items used to describe atomic type information @@ -586,6 +710,16 @@ export const CifCore_Schema = { } export const CifCore_Aliases = { + 'atom_site_aniso.U': [ + 'atom_site_anisotrop_U', + ], + 'atom_site_aniso.U_su': [ + 'atom_site_aniso_U_esd', + 'atom_site_anisotrop_U_esd', + ], + 'space_group.IT_number': [ + 'symmetry_Int_Tables_number', + ], 'space_group.name_H-M_full': [ 'symmetry_space_group_name_H-M', ], @@ -616,21 +750,45 @@ export const CifCore_Aliases = { 'atom_site_aniso.U_11': [ 'atom_site_anisotrop_U_11', ], + 'atom_site_aniso.U_11_su': [ + 'atom_site_aniso_U_11_esd', + 'atom_site_anisotrop_U_11_esd', + ], 'atom_site_aniso.U_12': [ 'atom_site_anisotrop_U_12', ], + 'atom_site_aniso.U_12_su': [ + 'atom_site_aniso_U_12_esd', + 'atom_site_anisotrop_U_12_esd', + ], 'atom_site_aniso.U_13': [ 'atom_site_anisotrop_U_13', ], + 'atom_site_aniso.U_13_su': [ + 'atom_site_aniso_U_13_esd', + 'atom_site_anisotrop_U_13_esd', + ], 'atom_site_aniso.U_22': [ 'atom_site_anisotrop_U_22', ], + 'atom_site_aniso.U_22_su': [ + 'atom_site_aniso_U_22_esd', + 'atom_site_anisotrop_U_22_esd', + ], 'atom_site_aniso.U_23': [ 'atom_site_anisotrop_U_23', ], + 'atom_site_aniso.U_23_su': [ + 'atom_site_aniso_U_23_esd', + 'atom_site_anisotrop_U_23_esd', + ], 'atom_site_aniso.U_33': [ 'atom_site_anisotrop_U_33', ], + 'atom_site_aniso.U_33_su': [ + 'atom_site_aniso_U_33_esd', + 'atom_site_anisotrop_U_33_esd', + ], } export type CifCore_Schema = typeof CifCore_Schema; diff --git a/src/mol-io/reader/common/text/number-parser.ts b/src/mol-io/reader/common/text/number-parser.ts index 27adc16cc1396ac0defc6da5ceaae63991db7677..4f609cec74eaf9779186f09704a50875c1baf658 100644 --- a/src/mol-io/reader/common/text/number-parser.ts +++ b/src/mol-io/reader/common/text/number-parser.ts @@ -71,8 +71,9 @@ export function parseFloat(str: string, start: number, end: number) { return neg * (ret + point / div); } else if (c === 53 || c === 21) { // 'e'/'E' return parseScientific(neg * ret, str, _start + 1, end); + } else { + break; } - else break; } return neg * ret; } @@ -137,8 +138,9 @@ export function getNumberType(str: string): NumberType { return NumberType.NaN; // string starts with e/E or -e/-E } return getNumberTypeScientific(str, start + 1, end); + } else { + break; } - else break; } return start === end ? NumberType.Int : NumberType.NaN; } diff --git a/src/mol-io/writer/cif/encoder/binary.ts b/src/mol-io/writer/cif/encoder/binary.ts index 9040dc7e67bbf5d6ad349edc298d9b9f4f7d7050..a80e1afeb8c9ccca75f3a6ffff755c80c6ae4b4d 100644 --- a/src/mol-io/writer/cif/encoder/binary.ts +++ b/src/mol-io/writer/cif/encoder/binary.ts @@ -188,8 +188,7 @@ function getFieldData(field: Field<any, any>, arrayCtor: ArrayCtor<string | numb if (isStr) array[offset] = ''; allPresent = false; - } - else { + } else { mask[offset] = Column.ValueKind.Present; array[offset] = getter(key, d, offset); } diff --git a/src/mol-math/graph/int-adjacency-graph.ts b/src/mol-math/graph/int-adjacency-graph.ts index 600ab01f07e60e8746d2dbe17d205a299857ac56..04c62438d8758476b989641e0a869115e8739ade 100644 --- a/src/mol-math/graph/int-adjacency-graph.ts +++ b/src/mol-math/graph/int-adjacency-graph.ts @@ -54,8 +54,11 @@ export namespace IntAdjacencyGraph { getEdgeIndex(i: VertexIndex, j: VertexIndex): number { let a, b; - if (i < j) { a = i; b = j; } - else { a = j; b = i; } + if (i < j) { + a = i; b = j; + } else { + a = j; b = i; + } for (let t = this.offset[a], _t = this.offset[a + 1]; t < _t; t++) { if (this.b[t] === b) return t; } diff --git a/src/mol-math/linear-algebra/3d/mat4.ts b/src/mol-math/linear-algebra/3d/mat4.ts index f2b9d7173bbddbbfda28bf11d4f5f4685aafbe1d..6fd4bd0316504952edb1f460cb45aa5a4fabd592 100644 --- a/src/mol-math/linear-algebra/3d/mat4.ts +++ b/src/mol-math/linear-algebra/3d/mat4.ts @@ -743,6 +743,8 @@ namespace Mat4 { * Check if the matrix has the form * [ Rotation Translation ] * [ 0 1 ] + * + * Allows for improper rotations */ export function isRotationAndTranslation(a: Mat4, eps?: number) { return _isRotationAndTranslation(a, typeof eps !== 'undefined' ? eps : EPSILON) @@ -752,12 +754,14 @@ namespace Mat4 { const a00 = a[0], a01 = a[1], a02 = a[2], a03 = a[3], a10 = a[4], a11 = a[5], a12 = a[6], a13 = a[7], a20 = a[8], a21 = a[9], a22 = a[10], a23 = a[11], - /* a30 = a[12], a31 = a[13], a32 = a[14],*/ a33 = a[15]; + a33 = a[15]; if (!equalEps(a33, 1, eps) || !equalEps(a03, 0, eps) || !equalEps(a13, 0, eps) || !equalEps(a23, 0, eps)) { return false; } - const det3x3 = a00 * (a11 * a22 - a12 * a21) - a01 * (a10 * a22 - a12 * a20) + a02 * (a10 * a21 - a11 * a20); + + // use `abs` to allow for improper rotations + const det3x3 = Math.abs(a00 * (a11 * a22 - a12 * a21) - a01 * (a10 * a22 - a12 * a20) + a02 * (a10 * a21 - a11 * a20)); if (!equalEps(det3x3, 1, eps)) { return false; } diff --git a/src/mol-math/linear-algebra/matrix/evd.ts b/src/mol-math/linear-algebra/matrix/evd.ts index 789d314e65b8076c3deb49b92d98cc090b32b0f1..7bd2056ae88331e749a83771f1981b249e937c84 100644 --- a/src/mol-math/linear-algebra/matrix/evd.ts +++ b/src/mol-math/linear-algebra/matrix/evd.ts @@ -77,8 +77,7 @@ function symmetricTridiagonalize(a: number[], d: number[], e: number[], order: n a[(j * order) + i] = 0.0; a[(i * order) + j] = 0.0; } - } - else { + } else { // Generate Householder vector. for (let k = 0; k < i; k++) { d[k] /= scale; diff --git a/src/mol-model-formats/structure/_spec/cif-core.spec.ts b/src/mol-model-formats/structure/_spec/cif-core.spec.ts index 5e73efda9e8e6ef5d64c39ccb4e10e585ae27c81..51c4b868da264e145e2e8e6da09437c2141a2fd8 100644 --- a/src/mol-model-formats/structure/_spec/cif-core.spec.ts +++ b/src/mol-model-formats/structure/_spec/cif-core.spec.ts @@ -44,6 +44,34 @@ _cell_measurement_temperature 100(2) _cell_measurement_reflns_used 5934 _cell_measurement_theta_min 2.86 _cell_measurement_theta_max 64.30 + +loop_ +_atom_site_aniso_label +_atom_site_aniso_U_11 +_atom_site_aniso_U_22 +_atom_site_aniso_U_33 +_atom_site_aniso_U_23 +_atom_site_aniso_U_13 +_atom_site_aniso_U_12 +Pt1 0.0425(2) 0.0423(2) 0.0375(2) 0.00066(13) 0.01515(13) 0.00089(12) +K1 0.0605(15) 0.0687(17) 0.0559(17) 0.000 0.0203(13) 0.000 +Cl2 0.0511(11) 0.0554(11) 0.0533(13) 0.0078(10) 0.0225(9) 0.0027(9) +Cl3 0.0708(13) 0.0484(11) 0.0605(13) -0.0053(10) 0.0276(10) 0.0026(10) +Cl1 0.0950(16) 0.0442(11) 0.0942(18) -0.0051(12) 0.0526(14) 0.0035(12) +N9 0.045(3) 0.047(4) 0.035(4) 0.004(3) 0.014(3) -0.003(3) +N7 0.040(3) 0.048(4) 0.036(3) 0.008(3) 0.004(3) -0.004(3) +O2 0.052(3) 0.098(4) 0.046(4) -0.012(4) 0.006(3) -0.016(3) +N3 0.041(3) 0.044(3) 0.044(4) 0.001(3) 0.008(3) -0.002(3) +O6 0.053(3) 0.093(4) 0.052(3) 0.008(3) 0.021(3) -0.019(3) +C4 0.044(4) 0.032(4) 0.050(5) 0.004(4) 0.011(4) 0.003(3) +N1 0.049(4) 0.049(4) 0.040(4) 0.004(3) 0.014(3) -0.005(3) +C8 0.050(4) 0.045(4) 0.033(4) -0.007(4) 0.000(3) -0.004(4) +C5 0.036(4) 0.039(4) 0.045(5) 0.003(4) 0.013(3) -0.001(3) +C2 0.047(4) 0.045(4) 0.039(5) -0.007(4) 0.011(4) -0.004(4) +C7 0.041(4) 0.072(5) 0.055(5) 0.013(5) 0.006(4) -0.015(4) +C1 0.061(5) 0.067(5) 0.043(5) -0.002(4) 0.017(4) -0.005(4) +C3 0.038(4) 0.090(6) 0.054(5) 0.003(5) 0.013(4) -0.018(4) +C6 0.045(4) 0.043(4) 0.038(4) 0.004(4) 0.008(3) -0.002(4) ` describe('cif-core read', () => { @@ -65,6 +93,7 @@ describe('cif-core read', () => { const cifCore = CIF.schema.cifCore(block) expect(cifCore.cell.length_a.value(0)).toBe(11.0829) - expect.assertions(1) + expect(cifCore.atom_site_aniso.U.value(0)).toEqual(new Float64Array([ 0.0425, 0, 0, 0.00089, 0.0423, 0, 0.01515, 0.00066, 0.0375 ])) + expect.assertions(2) }); }); \ No newline at end of file diff --git a/src/mol-model-formats/structure/cif-core.ts b/src/mol-model-formats/structure/cif-core.ts new file mode 100644 index 0000000000000000000000000000000000000000..539ff633834b16392be84f588de2f2f3ea8df563 --- /dev/null +++ b/src/mol-model-formats/structure/cif-core.ts @@ -0,0 +1,201 @@ +/** + * Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author Alexander Rose <alexander.rose@weirdbyte.de> + */ + +import { Column, Table } from '../../mol-data/db'; +import { Model, Symmetry } from '../../mol-model/structure/model'; +import { MoleculeType } from '../../mol-model/structure/model/types'; +import { RuntimeContext, Task } from '../../mol-task'; +import { createModels } from './basic/parser'; +import { BasicSchema, createBasic } from './basic/schema'; +import { ComponentBuilder } from './common/component'; +import { EntityBuilder } from './common/entity'; +import { ModelFormat } from './format'; +import { CifCore_Database } from '../../mol-io/reader/cif/schema/cif-core'; +import { CifFrame, CIF } from '../../mol-io/reader/cif'; +import { Spacegroup, SpacegroupCell } from '../../mol-math/geometry'; +import { Vec3 } from '../../mol-math/linear-algebra'; +import { ModelSymmetry } from './property/symmetry'; +import { IndexPairBonds } from './property/bonds/index-pair'; +import { AtomSiteAnisotrop } from './property/anisotropic'; + +function getSpacegroupNameOrNumber(space_group: CifCore_Database['space_group']) { + const groupNumber = space_group.IT_number.value(0) + const groupName = space_group['name_H-M_full'].value(0) + if (!space_group.IT_number.isDefined) return groupName + if (!space_group['name_H-M_full'].isDefined) return groupNumber + return groupNumber +} + +function getSymmetry(db: CifCore_Database): Symmetry { + const { cell, space_group } = db + const nameOrNumber = getSpacegroupNameOrNumber(space_group) + const spaceCell = SpacegroupCell.create(nameOrNumber, + Vec3.create(cell.length_a.value(0), cell.length_b.value(0), cell.length_c.value(0)), + Vec3.scale(Vec3.zero(), Vec3.create(cell.angle_alpha.value(0), cell.angle_beta.value(0), cell.angle_gamma.value(0)), Math.PI / 180)); + + return { + spacegroup: Spacegroup.create(spaceCell), + assemblies : [], + isNonStandardCrytalFrame: false, + ncsOperators: [] + } +} + +async function getModels(db: CifCore_Database, format: CifCoreFormat, ctx: RuntimeContext): Promise<Model[]> { + + const atomCount = db.atom_site._rowCount + const MOL = Column.ofConst('MOL', atomCount, Column.Schema.str); + const A = Column.ofConst('A', atomCount, Column.Schema.str); + const seq_id = Column.ofConst(1, atomCount, Column.Schema.int); + + const symmetry = getSymmetry(db) + const m = symmetry.spacegroup.cell.fromFractional + + const { fract_x, fract_y, fract_z } = db.atom_site + const x = new Float32Array(atomCount) + const y = new Float32Array(atomCount) + const z = new Float32Array(atomCount) + const v = Vec3() + for (let i = 0; i < atomCount; ++i) { + Vec3.set(v, fract_x.value(i), fract_y.value(i), fract_z.value(i)) + Vec3.transformMat4(v, v, m) + x[i] = v[0], y[i] = v[1], z[i] = v[2] + } + + const atom_site = Table.ofPartialColumns(BasicSchema.atom_site, { + auth_asym_id: A, + auth_atom_id: db.atom_site.label, + auth_comp_id: MOL, + auth_seq_id: seq_id, + Cartn_x: Column.ofFloatArray(x), + Cartn_y: Column.ofFloatArray(y), + Cartn_z: Column.ofFloatArray(z), + id: Column.range(0, atomCount - 1), + + label_asym_id: A, + label_atom_id: db.atom_site.label, + label_comp_id: MOL, + label_seq_id: seq_id, + label_entity_id: Column.ofConst('1', atomCount, Column.Schema.str), + + occupancy: db.atom_site.occupancy, + type_symbol: db.atom_site.type_symbol, + + pdbx_PDB_model_num: Column.ofConst(1, atomCount, Column.Schema.int), + }, atomCount); + + const name = ( + db.chemical.name_common.value(0) || + db.chemical.name_systematic.value(0) || + db.chemical_formula.sum.value(0) + ) + + const entityBuilder = new EntityBuilder() + entityBuilder.setNames([['MOL', name || 'Unknown Entity']]) + entityBuilder.getEntityId('MOL', MoleculeType.Unknown, 'A'); + + const componentBuilder = new ComponentBuilder(seq_id, db.atom_site.type_symbol); + componentBuilder.setNames([['MOL', name || 'Unknown Molecule']]) + componentBuilder.add('MOL', 0); + + const basics = createBasic({ + entity: entityBuilder.getEntityTable(), + chem_comp: componentBuilder.getChemCompTable(), + atom_site + }); + + const models = await createModels(basics, format, ctx); + + if (models.length > 0) { + ModelSymmetry.Provider.set(models[0], symmetry) + + const bondCount = db.geom_bond._rowCount + if(bondCount > 0) { + const labelIndexMap: { [label: string]: number } = {} + const { label } = db.atom_site + for (let i = 0, il = label.rowCount; i < il; ++i) { + labelIndexMap[label.value(i)] = i + } + + const indexA: number[] = [] + const indexB: number[] = [] + const order: number[] = [] + const symmetryA: string[] = [] + const symmetryB: string[] = [] + + const { atom_site_label_1, atom_site_label_2, valence, site_symmetry_1, site_symmetry_2 } = db.geom_bond + for (let i = 0; i < bondCount; ++i) { + indexA[i] = labelIndexMap[atom_site_label_1.value(i)] + indexB[i] = labelIndexMap[atom_site_label_2.value(i)] + // TODO derive order from bond length if undefined + order[i] = valence.isDefined ? valence.value(i) : 1 + symmetryA[i] = site_symmetry_1.value(i) || '1_555' + symmetryB[i] = site_symmetry_2.value(i) || '1_555' + } + + IndexPairBonds.Provider.set(models[0], IndexPairBonds.fromData({ pairs: { + indexA: Column.ofIntArray(indexA), + indexB: Column.ofIntArray(indexB), + order: Column.ofIntArray(order), + symmetryA: Column.ofStringArray(symmetryA), + symmetryB: Column.ofStringArray(symmetryB) + }, count: indexA.length })); + } + } + + return models; +} + +function atomSiteAnisotropFromCifCore(model: Model) { + if (!CifCoreFormat.is(model.sourceData)) return; + const { atom_site, atom_site_aniso } = model.sourceData.data.db + const data = Table.ofPartialColumns(AtomSiteAnisotrop.Schema, { + U: atom_site_aniso.U, + U_esd: atom_site_aniso.U_su + }, atom_site_aniso._rowCount); + const elementToAnsiotrop = AtomSiteAnisotrop.getElementToAnsiotropFromLabel(atom_site.label, atom_site_aniso.label) + return { data, elementToAnsiotrop } +} +function atomSiteAnisotropApplicableCifCore(model: Model) { + if (!CifCoreFormat.is(model.sourceData)) return false; + return model.sourceData.data.db.atom_site_aniso.U.isDefined +} +AtomSiteAnisotrop.Provider.formatRegistry.add('cifCore', atomSiteAnisotropFromCifCore, atomSiteAnisotropApplicableCifCore) + +// + +export { CifCoreFormat }; + +type CifCoreFormat = ModelFormat<CifCoreFormat.Data> + +namespace CifCoreFormat { + export type Data = { db: CifCore_Database, frame: CifFrame } + export function is(x: ModelFormat): x is CifCoreFormat { + return x.kind === 'cifCore' + } + + export function fromFrame(frame: CifFrame, db?: CifCore_Database): CifCoreFormat { + if (!db) db = CIF.schema.cifCore(frame) + + const name = ( + db.database_code.depnum_ccdc_archive.value(0) || + db.database_code.depnum_ccdc_fiz.value(0) || + db.database_code.ICSD.value(0) || + db.database_code.MDF.value(0) || + db.database_code.NBS.value(0) || + db.database_code.CSD.value(0) || + db.database_code.COD.value(0) || + db._name + ) + + return { kind: 'cifCore', name, data: { db, frame } }; + } +} + +export function trajectoryFromCifCore(frame: CifFrame): Task<Model.Trajectory> { + const format = CifCoreFormat.fromFrame(frame) + return Task.create('Parse CIF Core', ctx => getModels(format.data.db, format, ctx)) +} diff --git a/src/mol-model-formats/structure/common/property.ts b/src/mol-model-formats/structure/common/property.ts index 873e8bd5848a0bf1edd36a69f9001d4555c293fb..eeb7c1685087afda73ad67d57f309eab2329f20c 100644 --- a/src/mol-model-formats/structure/common/property.ts +++ b/src/mol-model-formats/structure/common/property.ts @@ -8,19 +8,27 @@ import { CustomPropertyDescriptor, Model } from '../../../mol-model/structure'; import { ModelFormat } from '../format'; class FormatRegistry<T> { - map = new Map<ModelFormat['kind'], (model: Model) => T | undefined>() + private map = new Map<ModelFormat['kind'], (model: Model) => T | undefined>() + private applicable = new Map<ModelFormat['kind'], (model: Model) => boolean>() - add(kind: ModelFormat['kind'], obtain: (model: Model) => T | undefined) { + add(kind: ModelFormat['kind'], obtain: (model: Model) => T | undefined, applicable?: (model: Model) => boolean) { this.map.set(kind, obtain) + if (applicable) this.applicable.set(kind, applicable) } remove(kind: ModelFormat['kind']) { this.map.delete(kind) + this.applicable.delete(kind) } get(kind: ModelFormat['kind']) { return this.map.get(kind) } + + isApplicable(model: Model) { + const isApplicable = this.applicable.get(model.sourceData.kind) + return isApplicable ? isApplicable(model) : true + } } export { FormatPropertyProvider as FormatPropertyProvider } @@ -28,6 +36,7 @@ export { FormatPropertyProvider as FormatPropertyProvider } interface FormatPropertyProvider<T> { readonly descriptor: CustomPropertyDescriptor readonly formatRegistry: FormatRegistry<T> + isApplicable(model: Model): boolean get(model: Model): T | undefined set(model: Model, value: T): void } @@ -40,6 +49,9 @@ namespace FormatPropertyProvider { return { descriptor, formatRegistry, + isApplicable(model: Model) { + return formatRegistry.isApplicable(model) + }, get(model: Model): T | undefined { if (model._staticPropertyData[name]) return model._staticPropertyData[name] if (model.customProperties.has(descriptor)) return diff --git a/src/mol-model-formats/structure/mmcif.ts b/src/mol-model-formats/structure/mmcif.ts index ae7c3deb8147f42d7a2f660493581d921cc68b31..70fc158950b14340b6819e826dd580de2584a2ed 100644 --- a/src/mol-model-formats/structure/mmcif.ts +++ b/src/mol-model-formats/structure/mmcif.ts @@ -9,7 +9,7 @@ import { Model } from '../../mol-model/structure/model/model'; import { Task } from '../../mol-task'; import { ModelFormat } from './format'; import { CifFrame, CIF } from '../../mol-io/reader/cif'; -import { mmCIF_Database, mmCIF_Schema } from '../../mol-io/reader/cif/schema/mmcif'; +import { mmCIF_Database } from '../../mol-io/reader/cif/schema/mmcif'; import { createModels } from './basic/parser'; import { ModelSymmetry } from './property/symmetry'; import { ModelSecondaryStructure } from './property/secondary-structure'; @@ -34,11 +34,15 @@ ModelSecondaryStructure.Provider.formatRegistry.add('mmCIF', secondaryStructureF function atomSiteAnisotropFromMmcif(model: Model) { if (!MmcifFormat.is(model.sourceData)) return; const { atom_site_anisotrop } = model.sourceData.data.db - const data = Table.ofColumns(mmCIF_Schema['atom_site_anisotrop'], atom_site_anisotrop); - const elementToAnsiotrop = AtomSiteAnisotrop.getElementToAnsiotrop(model, data) + const data = Table.ofColumns(AtomSiteAnisotrop.Schema, atom_site_anisotrop); + const elementToAnsiotrop = AtomSiteAnisotrop.getElementToAnsiotrop(model.atomicConformation.atomId, atom_site_anisotrop.id) return { data, elementToAnsiotrop } } -AtomSiteAnisotrop.Provider.formatRegistry.add('mmCIF', atomSiteAnisotropFromMmcif) +function atomSiteAnisotropApplicableMmcif(model: Model) { + if (!MmcifFormat.is(model.sourceData)) return false; + return model.sourceData.data.db.atom_site_anisotrop.U.isDefined +} +AtomSiteAnisotrop.Provider.formatRegistry.add('mmCIF', atomSiteAnisotropFromMmcif, atomSiteAnisotropApplicableMmcif) function componentBondFromMmcif(model: Model) { if (!MmcifFormat.is(model.sourceData)) return; diff --git a/src/mol-model-formats/structure/mol.ts b/src/mol-model-formats/structure/mol.ts index 2e9f43f315a63db09bece852ca917c955e79dbe1..ea99af2616ffe2cd8f627c025293b887b4fd01a8 100644 --- a/src/mol-model-formats/structure/mol.ts +++ b/src/mol-model-formats/structure/mol.ts @@ -1,6 +1,7 @@ /** * Copyright (c) 2019-2020 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> */ @@ -19,7 +20,7 @@ import { IndexPairBonds } from './property/bonds/index-pair'; async function getModels(mol: MolFile, ctx: RuntimeContext): Promise<Model[]> { const { atoms, bonds } = mol; - const UNK = Column.ofConst('UNK', mol.atoms.count, Column.Schema.str); + const MOL = Column.ofConst('MOL', mol.atoms.count, Column.Schema.str); const A = Column.ofConst('A', mol.atoms.count, Column.Schema.str); const type_symbol = Column.asArrayColumn(atoms.type_symbol); const seq_id = Column.ofConst(1, atoms.count, Column.Schema.int); @@ -27,7 +28,7 @@ async function getModels(mol: MolFile, ctx: RuntimeContext): Promise<Model[]> { const atom_site = Table.ofPartialColumns(BasicSchema.atom_site, { auth_asym_id: A, auth_atom_id: type_symbol, - auth_comp_id: UNK, + auth_comp_id: MOL, auth_seq_id: seq_id, Cartn_x: Column.asArrayColumn(atoms.x, Float32Array), Cartn_y: Column.asArrayColumn(atoms.y, Float32Array), @@ -36,7 +37,7 @@ async function getModels(mol: MolFile, ctx: RuntimeContext): Promise<Model[]> { label_asym_id: A, label_atom_id: type_symbol, - label_comp_id: UNK, + label_comp_id: MOL, label_seq_id: seq_id, label_entity_id: Column.ofConst('1', atoms.count, Column.Schema.str), @@ -47,12 +48,12 @@ async function getModels(mol: MolFile, ctx: RuntimeContext): Promise<Model[]> { }, atoms.count); const entityBuilder = new EntityBuilder() - entityBuilder.setNames([['UNK', 'Unknown Entity']]) - entityBuilder.getEntityId('UNK', MoleculeType.Unknown, 'A'); + entityBuilder.setNames([['MOL', 'Unknown Entity']]) + entityBuilder.getEntityId('MOL', MoleculeType.Unknown, 'A'); const componentBuilder = new ComponentBuilder(seq_id, type_symbol); - componentBuilder.setNames([['UNK', 'Unknown Residue']]) - componentBuilder.add('UNK', 0); + componentBuilder.setNames([['MOL', 'Unknown Molecule']]) + componentBuilder.add('MOL', 0); const basics = createBasic({ entity: entityBuilder.getEntityTable(), diff --git a/src/mol-model-formats/structure/property/anisotropic.ts b/src/mol-model-formats/structure/property/anisotropic.ts index 156fcb8926083d77d75dd277a22e48621530d972..86db66428977fba9e1c7910f4af8524fb9488911 100644 --- a/src/mol-model-formats/structure/property/anisotropic.ts +++ b/src/mol-model-formats/structure/property/anisotropic.ts @@ -4,15 +4,20 @@ * @author Alexander Rose <alexander.rose@weirdbyte.de> */ -import { Table } from '../../../mol-data/db'; -import { Model, CustomPropertyDescriptor } from '../../../mol-model/structure'; +import { Table, Column } from '../../../mol-data/db'; +import { CustomPropertyDescriptor } from '../../../mol-model/structure'; import { mmCIF_Schema } from '../../../mol-io/reader/cif/schema/mmcif'; import { CifWriter } from '../../../mol-io/writer/cif'; import { FormatPropertyProvider } from '../common/property'; +import { MmcifFormat } from '../mmcif'; export { AtomSiteAnisotrop } -type Anisotrop = Table<mmCIF_Schema['atom_site_anisotrop']> +const Anisotrop = { + U: mmCIF_Schema.atom_site_anisotrop.U, + U_esd: mmCIF_Schema.atom_site_anisotrop.U_esd +} +type Anisotrop = Table<typeof Anisotrop> interface AtomSiteAnisotrop { data: Anisotrop @@ -21,6 +26,8 @@ interface AtomSiteAnisotrop { } namespace AtomSiteAnisotrop { + export const Schema = Anisotrop + export const Descriptor: CustomPropertyDescriptor = { name: 'atom_site_anisotrop', cifExport: { @@ -30,8 +37,9 @@ namespace AtomSiteAnisotrop { instance(ctx) { const p = Provider.get(ctx.firstModel); if (!p) return CifWriter.Category.Empty; + if (!MmcifFormat.is(ctx.firstModel.sourceData)) return CifWriter.Category.Empty; // TODO filter to write only data for elements that exist in model - return CifWriter.Category.ofTable(p.data); + return CifWriter.Category.ofTable(ctx.firstModel.sourceData.data.db.atom_site_anisotrop); } }] } @@ -39,22 +47,36 @@ namespace AtomSiteAnisotrop { export const Provider = FormatPropertyProvider.create<AtomSiteAnisotrop>(Descriptor) - export function getElementToAnsiotrop(model: Model, data: Anisotrop) { - const { atomId } = model.atomicConformation + export function getElementToAnsiotrop(atomId: Column<number>, ansioId: Column<number>) { const atomIdToElement = new Int32Array(atomId.rowCount) atomIdToElement.fill(-1) for (let i = 0, il = atomId.rowCount; i < il; i++) { atomIdToElement[atomId.value(i)] = i } - const { id } = data const elementToAnsiotrop = new Int32Array(atomId.rowCount) elementToAnsiotrop.fill(-1) - for (let i = 0, il = id.rowCount; i < il; ++i) { - const ei = atomIdToElement[id.value(i)] + for (let i = 0, il = ansioId.rowCount; i < il; ++i) { + const ei = atomIdToElement[ansioId.value(i)] if (ei !== -1) elementToAnsiotrop[ei] = i } return elementToAnsiotrop } + + export function getElementToAnsiotropFromLabel(atomLabel: Column<string>, ansioLabel: Column<string>) { + const atomLabelToElement: { [k: string]: number | undefined } = {} + for (let i = 0, il = atomLabel.rowCount; i < il; i++) { + atomLabelToElement[atomLabel.value(i)] = i + } + + const elementToAnsiotrop = new Int32Array(atomLabel.rowCount) + elementToAnsiotrop.fill(-1) + for (let i = 0, il = ansioLabel.rowCount; i < il; ++i) { + const ei = atomLabelToElement[ansioLabel.value(i)] + if (ei !== undefined) elementToAnsiotrop[ei] = i + } + + return elementToAnsiotrop + } } \ No newline at end of file diff --git a/src/mol-model-formats/structure/property/bonds/index-pair.ts b/src/mol-model-formats/structure/property/bonds/index-pair.ts index 8cc29e892471b5d9847dffb075c82efbf1d52d9d..98d48fabedbe7b8f45d31142ea66c08c657a8acc 100644 --- a/src/mol-model-formats/structure/property/bonds/index-pair.ts +++ b/src/mol-model-formats/structure/property/bonds/index-pair.ts @@ -9,17 +9,26 @@ import { IntAdjacencyGraph } from '../../../../mol-math/graph'; import { Column } from '../../../../mol-data/db'; import { FormatPropertyProvider } from '../../common/property'; -export type IndexPairBonds = IntAdjacencyGraph<number, { readonly order: ArrayLike<number> }> +export type IndexPairBondsProps = { + readonly order: ArrayLike<number> + readonly symmetryA: ArrayLike<string> + readonly symmetryB: ArrayLike<string> +} +export type IndexPairBonds = IntAdjacencyGraph<number, IndexPairBondsProps> -function getGraph(indexA: ArrayLike<number>, indexB: ArrayLike<number>, _order: ArrayLike<number>, count: number): IndexPairBonds { +function getGraph(indexA: ArrayLike<number>, indexB: ArrayLike<number>, props: Partial<IndexPairBondsProps>, count: number): IndexPairBonds { const builder = new IntAdjacencyGraph.EdgeBuilder(count, indexA, indexB); const order = new Int8Array(builder.slotCount); + const symmetryA = new Array(builder.slotCount); + const symmetryB = new Array(builder.slotCount); for (let i = 0, _i = builder.edgeCount; i < _i; i++) { builder.addNextEdge(); - builder.assignProperty(order, _order[i]); + builder.assignProperty(order, props.order ? props.order[i] : 1); + builder.assignProperty(symmetryA, props.symmetryA ? props.symmetryA[i] : ''); + builder.assignProperty(symmetryB, props.symmetryB ? props.symmetryB[i] : ''); } - return builder.createGraph({ order }); + return builder.createGraph({ order, symmetryA, symmetryB }); } export namespace IndexPairBonds { @@ -33,7 +42,9 @@ export namespace IndexPairBonds { pairs: { indexA: Column<number>, indexB: Column<number> - order: Column<number> + order?: Column<number>, + symmetryA?: Column<string>, + symmetryB?: Column<string>, }, count: number } @@ -42,7 +53,9 @@ export namespace IndexPairBonds { const { pairs, count } = data const indexA = pairs.indexA.toArray() const indexB = pairs.indexB.toArray() - const order = pairs.order.toArray() - return getGraph(indexA, indexB, order, count); + const order = pairs.order && pairs.order.toArray() + const symmetryA = pairs.symmetryA && pairs.symmetryA.toArray() + const symmetryB = pairs.symmetryB && pairs.symmetryB.toArray() + return getGraph(indexA, indexB, { order, symmetryA, symmetryB }, count); } } \ No newline at end of file diff --git a/src/mol-model-formats/structure/property/symmetry.ts b/src/mol-model-formats/structure/property/symmetry.ts index 487509249aefa9567915ca4b994aa715cf4e7f6d..8e3615079eafff748a161e731837ab6c15e91033 100644 --- a/src/mol-model-formats/structure/property/symmetry.ts +++ b/src/mol-model-formats/structure/property/symmetry.ts @@ -8,7 +8,7 @@ import { mmCIF_Schema } from '../../../mol-io/reader/cif/schema/mmcif'; import { Spacegroup, SpacegroupCell, SymmetryOperator } from '../../../mol-math/geometry'; import { Tensor, Vec3, Mat3 } from '../../../mol-math/linear-algebra'; -import { Symmetry as _ModelSymmetry } from '../../../mol-model/structure/model/properties/symmetry'; +import { Symmetry } from '../../../mol-model/structure/model/properties/symmetry'; import { createAssemblies } from './assembly'; import { CustomPropertyDescriptor } from '../../../mol-model/structure'; import { FormatPropertyProvider } from '../common/property'; @@ -21,7 +21,7 @@ namespace ModelSymmetry { name: 'model_symmetry', }; - export const Provider = FormatPropertyProvider.create<_ModelSymmetry>(Descriptor) + export const Provider = FormatPropertyProvider.create<Symmetry>(Descriptor) type Data = { symmetry: Table<mmCIF_Schema['symmetry']> @@ -33,7 +33,7 @@ namespace ModelSymmetry { pdbx_struct_oper_list: Table<mmCIF_Schema['pdbx_struct_oper_list']> } - export function fromData(data: Data): _ModelSymmetry { + export function fromData(data: Data): Symmetry { const assemblies = createAssemblies(data.pdbx_struct_assembly, data.pdbx_struct_assembly_gen, data.pdbx_struct_oper_list); const spacegroup = getSpacegroup(data.symmetry, data.cell); const isNonStandardCrytalFrame = checkNonStandardCrystalFrame(data.atom_sites, spacegroup); diff --git a/src/mol-model-props/rcsb/validation-report.ts b/src/mol-model-props/rcsb/validation-report.ts index 1120193d7c07b537f7a26d31779f59c7403a22a4..1ed8f1c2b68ded2a22e26a9dc36618723b0bd648 100644 --- a/src/mol-model-props/rcsb/validation-report.ts +++ b/src/mol-model-props/rcsb/validation-report.ts @@ -111,6 +111,7 @@ namespace ValidationReport { } export async function open(ctx: CustomProperty.Context, model: Model, props: FileSourceProps): Promise<ValidationReport> { + if (props.input === null) throw new Error('No file given') const xml = await readFromFile(props.input, 'xml').runInContext(ctx.runtime) return fromXml(xml, model) } diff --git a/src/mol-model/structure/model/model.ts b/src/mol-model/structure/model/model.ts index ba6574fbf07b34096833bc668ae0c88074381673..f9c39208313a08e56afe38d33fefd07e9ba746e9 100644 --- a/src/mol-model/structure/model/model.ts +++ b/src/mol-model/structure/model/model.ts @@ -24,6 +24,7 @@ import { createModels } from '../../../mol-model-formats/structure/basic/parser' import { MmcifFormat } from '../../../mol-model-formats/structure/mmcif'; import { ChainIndex } from './indexing'; import { SymmetryOperator } from '../../../mol-math/geometry'; +import { ModelSymmetry } from '../../../mol-model-formats/structure/property/symmetry'; /** * Interface to the "source data" of the molecule. @@ -159,19 +160,14 @@ export namespace Model { ) } + const tmpAngles90 = Vec3.create(1.5708, 1.5708, 1.5708) // in radians + const tmpLengths1 = Vec3.create(1, 1, 1) export function hasCrystalSymmetry(model: Model) { - if (!MmcifFormat.is(model.sourceData)) return false - const { db } = model.sourceData.data - return ( - db.symmetry._rowCount === 1 && db.cell._rowCount === 1 && !( - db.symmetry.Int_Tables_number.value(0) === 1 && - db.cell.angle_alpha.value(0) === 90 && - db.cell.angle_beta.value(0) === 90 && - db.cell.angle_gamma.value(0) === 90 && - db.cell.length_a.value(0) === 1 && - db.cell.length_b.value(0) === 1 && - db.cell.length_c.value(0) === 1 - ) + const spacegroup = ModelSymmetry.Provider.get(model)?.spacegroup + return !!spacegroup && !( + spacegroup.num === 1 && + Vec3.equals(spacegroup.cell.anglesInRadians, tmpAngles90) && + Vec3.equals(spacegroup.cell.size, tmpLengths1) ) } diff --git a/src/mol-model/structure/query/utils/structure-set.ts b/src/mol-model/structure/query/utils/structure-set.ts index 2e1cf2d1aa17d8cf22bdad066779d310c173dc22..9741f3f3533c7c3a30792985ec03d1353f7dfbc3 100644 --- a/src/mol-model/structure/query/utils/structure-set.ts +++ b/src/mol-model/structure/query/utils/structure-set.ts @@ -44,8 +44,11 @@ export function structureAreIntersecting(sA: Structure, sB: Structure): boolean if (sA === sB) return true; let a, b; - if (sA.units.length < sB.units.length) { a = sA; b = sB; } - else { a = sB; b = sA; } + if (sA.units.length < sB.units.length) { + a = sA; b = sB; + } else { + a = sB; b = sA; + } const aU = a.units, bU = b.unitMap; @@ -64,8 +67,11 @@ export function structureIntersect(sA: Structure, sB: Structure): Structure { if (!structureAreIntersecting(sA, sB)) return Structure.Empty; let a, b; - if (sA.units.length < sB.units.length) { a = sA; b = sB; } - else { a = sB; b = sA; } + if (sA.units.length < sB.units.length) { + a = sA; b = sB; + } else { + a = sB; b = sA; + } const aU = a.units, bU = b.unitMap; const units: Unit[] = []; diff --git a/src/mol-model/structure/structure/unit/bonds/inter-compute.ts b/src/mol-model/structure/structure/unit/bonds/inter-compute.ts index de7bb5f48a866bd459810fd446886a22837ce689..e76a48ba563a57a4bbe7f14d687072c3cb0d5bc9 100644 --- a/src/mol-model/structure/structure/unit/bonds/inter-compute.ts +++ b/src/mol-model/structure/structure/unit/bonds/inter-compute.ts @@ -59,6 +59,8 @@ function findPairBonds(unitA: Unit.Atomic, unitB: Unit.Atomic, props: BondComput const testDistanceSq = (bRadius + MAX_RADIUS) * (bRadius + MAX_RADIUS); builder.startUnitPair(unitA, unitB) + const symmUnitA = unitA.conformation.operator.name + const symmUnitB = unitB.conformation.operator.name for (let _aI = 0 as StructureElement.UnitIndex; _aI < atomCount; _aI++) { const aI = atomsA[_aI]; @@ -67,10 +69,14 @@ function findPairBonds(unitA: Unit.Atomic, unitB: Unit.Atomic, props: BondComput if (Vec3.squaredDistance(imageA, bCenter) > testDistanceSq) continue; if (!props.forceCompute && indexPairs) { + const { order, symmetryA, symmetryB } = indexPairs.edgeProps for (let i = indexPairs.offset[aI], il = indexPairs.offset[aI + 1]; i < il; ++i) { const _bI = SortedArray.indexOf(unitA.elements, indexPairs.b[i]) as StructureElement.UnitIndex; if (_bI < 0) continue; - builder.add(_aI, _bI, { order: indexPairs.edgeProps.order[i], flag: BondType.Flag.Covalent }); + if (symmetryA[i] === symmetryB[i]) continue; + if (symmUnitA === symmetryA[i] && symmUnitB === symmetryB[i]) { + builder.add(_aI, _bI, { order: order[i], flag: BondType.Flag.Covalent }); + } } continue // assume `indexPairs` supplies all bonds } @@ -137,7 +143,9 @@ function findPairBonds(unitA: Unit.Atomic, unitB: Unit.Atomic, props: BondComput const thresholdAB = getElementPairThreshold(aeI, beI); const pairingThreshold = thresholdAB > 0 ? thresholdAB - : beI < 0 ? thresholdA : Math.max(thresholdA, getElementThreshold(beI)); + : beI < 0 + ? thresholdA + : (thresholdA + getElementThreshold(beI)) / 2; // not sure if avg or min but max is too big if (dist <= pairingThreshold) { const atomIdB = label_atom_idB.value(bI); diff --git a/src/mol-model/structure/structure/unit/bonds/intra-compute.ts b/src/mol-model/structure/structure/unit/bonds/intra-compute.ts index 7aee9b943ade919b54d5e5948470850f66d2d167..6ba3b6246e2626083145b321ed06eb16de86fdc1 100644 --- a/src/mol-model/structure/structure/unit/bonds/intra-compute.ts +++ b/src/mol-model/structure/structure/unit/bonds/intra-compute.ts @@ -60,12 +60,14 @@ function _computeBonds(unit: Unit.Atomic, props: BondComputationProps): IntraUni const aI = atoms[_aI]; if (!props.forceCompute && indexPairs) { + const { edgeProps } = indexPairs for (let i = indexPairs.offset[aI], il = indexPairs.offset[aI + 1]; i < il; ++i) { const _bI = SortedArray.indexOf(unit.elements, indexPairs.b[i]) as StructureElement.UnitIndex; if (_bI < 0) continue; + if (edgeProps.symmetryA[i] !== edgeProps.symmetryB[i]) continue; atomA[atomA.length] = _aI; atomB[atomB.length] = _bI; - order[order.length] = indexPairs.edgeProps.order[i]; + order[order.length] = edgeProps.order[i]; flags[flags.length] = BondType.Flag.Covalent; } continue // assume `indexPairs` supplies all bonds @@ -157,7 +159,9 @@ function _computeBonds(unit: Unit.Atomic, props: BondComputationProps): IntraUni const thresholdAB = getElementPairThreshold(aeI, beI); const pairingThreshold = thresholdAB > 0 ? thresholdAB - : beI < 0 ? thresholdA : Math.max(thresholdA, getElementThreshold(beI)); + : beI < 0 + ? thresholdA + : (thresholdA + getElementThreshold(beI)) / 2; // not sure if avg or min but max is too big if (dist <= pairingThreshold) { atomA[atomA.length] = _aI; diff --git a/src/mol-model/structure/structure/util/subset-builder.ts b/src/mol-model/structure/structure/util/subset-builder.ts index 56f1d14c656537a228f79861e535ef3d4c67e6c5..13c082495431cd77080ffcf69dac37d03060e1fb 100644 --- a/src/mol-model/structure/structure/util/subset-builder.ts +++ b/src/mol-model/structure/structure/util/subset-builder.ts @@ -21,8 +21,9 @@ export class StructureSubsetBuilder { addToUnit(parentId: number, e: ElementIndex) { const unit = this.unitMap.get(parentId); - if (!!unit) { unit[unit.length] = e; } - else { + if (!!unit) { + unit[unit.length] = e; + } else { this.unitMap.set(parentId, [e]); this.ids[this.ids.length] = parentId; } diff --git a/src/mol-model/structure/structure/util/unique-subset-builder.ts b/src/mol-model/structure/structure/util/unique-subset-builder.ts index 21ebf6788f3e3ab062685525f5ae9b09620bd1da..fb713a805fac427478b187ee865737c98db487b4 100644 --- a/src/mol-model/structure/structure/util/unique-subset-builder.ts +++ b/src/mol-model/structure/structure/util/unique-subset-builder.ts @@ -24,8 +24,7 @@ export class StructureUniqueSubsetBuilder { const unit = this.unitMap.get(parentId); if (!!unit) { if (UniqueArray.add(unit, e, e)) this.elementCount++; - } - else { + } else { const arr: UArray = UniqueArray.create(); UniqueArray.add(arr, e, e); this.unitMap.set(parentId, arr); diff --git a/src/mol-plugin-state/actions.ts b/src/mol-plugin-state/actions.ts index 5491ef5cfdcb1f0e9c4c7575c888199f9e38f4cc..ce6c13d4193c80a5749755e8d74048991eca8e86 100644 --- a/src/mol-plugin-state/actions.ts +++ b/src/mol-plugin-state/actions.ts @@ -6,7 +6,7 @@ import * as Structure from './actions/structure' import * as Volume from './actions/volume' -import * as DataFormat from './actions/data-format' +import * as DataFormat from './actions/file' export const StateActions = { Structure, diff --git a/src/mol-plugin-state/actions/data-format.ts b/src/mol-plugin-state/actions/data-format.ts deleted file mode 100644 index 3008bf6d0310ebe553a3d27004ee42d49993af97..0000000000000000000000000000000000000000 --- a/src/mol-plugin-state/actions/data-format.ts +++ /dev/null @@ -1,170 +0,0 @@ -/** - * Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info. - * - * @author Alexander Rose <alexander.rose@weirdbyte.de> - */ - -import msgpackDecode from '../../mol-io/common/msgpack/decode'; -import { PluginContext } from '../../mol-plugin/context'; -import { State, StateAction, StateObjectRef } from '../../mol-state'; -import { Task } from '../../mol-task'; -import { FileInfo, getFileInfo } from '../../mol-util/file-info'; -import { ParamDefinition as PD } from '../../mol-util/param-definition'; -import { PluginStateObject } from '../objects'; -import { PlyProvider } from './shape'; -import { DcdProvider, GroProvider, MmcifProvider, PdbProvider, Provider3dg, PsfProvider, MolProvider } from './structure'; -import { Ccp4Provider, DscifProvider, Dsn6Provider } from './volume'; - -export class DataFormatRegistry<D extends PluginStateObject.Data.Binary | PluginStateObject.Data.String> { - private _list: { name: string, provider: DataFormatProvider<D> }[] = [] - private _map = new Map<string, DataFormatProvider<D>>() - private _extensions: Set<string> | undefined = undefined - private _binaryExtensions: Set<string> | undefined = undefined - private _options: [string, string][] | undefined = undefined - - get types(): [string, string][] { - return this._list.map(e => [e.name, e.provider.label] as [string, string]); - } - - get extensions() { - if (this._extensions) return this._extensions - const extensions = new Set<string>() - this._list.forEach(({ provider }) => { - provider.stringExtensions.forEach(ext => extensions.add(ext)) - provider.binaryExtensions.forEach(ext => extensions.add(ext)) - }) - this._extensions = extensions - return extensions - } - - get binaryExtensions() { - if (this._binaryExtensions) return this._binaryExtensions - const binaryExtensions = new Set<string>() - this._list.forEach(({ provider }) => provider.binaryExtensions.forEach(ext => binaryExtensions.add(ext))) - this._binaryExtensions = binaryExtensions - return binaryExtensions - } - - get options() { - if (this._options) return this._options - const options: [string, string][] = [['auto', 'Automatic']] - this._list.forEach(({ name, provider }) => options.push([ name, provider.label ])) - this._options = options - return options - } - - constructor() { - this.add('3dg', Provider3dg) - this.add('ccp4', Ccp4Provider) - this.add('dcd', DcdProvider) - this.add('dscif', DscifProvider) - this.add('dsn6', Dsn6Provider) - this.add('gro', GroProvider) - this.add('mol', MolProvider) - this.add('mmcif', MmcifProvider) - this.add('pdb', PdbProvider) - this.add('ply', PlyProvider) - this.add('psf', PsfProvider) - }; - - private _clear() { - this._extensions = undefined - this._binaryExtensions = undefined - this._options = undefined - } - - add(name: string, provider: DataFormatProvider<D>) { - this._clear() - this._list.push({ name, provider }) - this._map.set(name, provider) - } - - remove(name: string) { - this._clear() - this._list.splice(this._list.findIndex(e => e.name === name), 1) - this._map.delete(name) - } - - auto(info: FileInfo, dataStateObject: D) { - for (let i = 0, il = this.list.length; i < il; ++i) { - const { provider } = this._list[i] - if (provider.isApplicable(info, dataStateObject.data)) return provider - } - throw new Error('no compatible data format provider available') - } - - get(name: string): DataFormatProvider<D> { - if (this._map.has(name)) { - return this._map.get(name)! - } else { - throw new Error(`unknown data format name '${name}'`) - } - } - - get list() { - return this._list - } -} - -export type DataFormatBuilderOptions = { visuals: boolean } - -export interface DataFormatProvider<D extends PluginStateObject.Data.Binary | PluginStateObject.Data.String> { - label: string - description: string - stringExtensions: string[] - binaryExtensions: string[] - isApplicable(info: FileInfo, data: string | Uint8Array): boolean - getDefaultBuilder(ctx: PluginContext, data: StateObjectRef<D>, options: DataFormatBuilderOptions, state: State): Task<void> -} - -// - -export const OpenFiles = StateAction.build({ - display: { name: 'Open Files', description: 'Load one or more files and optionally create default visuals' }, - from: PluginStateObject.Root, - params: (a, ctx: PluginContext) => { - const { extensions, options } = ctx.dataFormat.registry - return { - files: PD.FileList({ accept: Array.from(extensions.values()).map(e => `.${e}`).join(',') + ',.gz,.zip', multiple: true }), - format: PD.Select('auto', options), - visuals: PD.Boolean(true, { description: 'Add default visuals' }), - } - } -})(({ params, state }, plugin: PluginContext) => Task.create('Open Files', async taskCtx => { - await state.transaction(async () => { - for (let i = 0, il = params.files.length; i < il; ++i) { - try { - const file = params.files[i] - const info = getFileInfo(file) - const isBinary = plugin.dataFormat.registry.binaryExtensions.has(info.ext) - const { data } = await plugin.builders.data.readFile({ file, isBinary }); - // const data = state.build().toRoot().apply(StateTransforms.Data.ReadFile, { file, isBinary }); - // const dataStateObject = await state.updateTree(data).runInContext(taskCtx); - const provider = params.format === 'auto' - ? plugin.dataFormat.registry.auto(info, data.cell?.obj!) - : plugin.dataFormat.registry.get(params.format) - - // need to await so that the enclosing Task finishes after the update is done. - await provider.getDefaultBuilder(plugin, data, { visuals: params.visuals }, state).runInContext(taskCtx) - } catch (e) { - plugin.log.error(e) - } - } - }).runInContext(taskCtx); -})); - -// - -type cifVariants = 'dscif' | -1 -export function guessCifVariant(info: FileInfo, data: Uint8Array | string): cifVariants { - if (info.ext === 'bcif') { - try { - // TODO: find a way to run msgpackDecode only once - // now it is run twice, here and during file parsing - if (msgpackDecode(data as Uint8Array).encoder.startsWith('VolumeServer')) return 'dscif' - } catch { } - } else if (info.ext === 'cif') { - if ((data as string).startsWith('data_SERVER\n#\n_density_server_result')) return 'dscif' - } - return -1 -} \ No newline at end of file diff --git a/src/mol-plugin-state/actions/file.ts b/src/mol-plugin-state/actions/file.ts new file mode 100644 index 0000000000000000000000000000000000000000..693a315ebd1e6c5d7c877a0545913846443c978f --- /dev/null +++ b/src/mol-plugin-state/actions/file.ts @@ -0,0 +1,58 @@ +/** + * Copyright (c) 2019-2020 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author Alexander Rose <alexander.rose@weirdbyte.de> + */ + +import { PluginContext } from '../../mol-plugin/context'; +import { StateAction } from '../../mol-state'; +import { Task } from '../../mol-task'; +import { getFileInfo } from '../../mol-util/file-info'; +import { ParamDefinition as PD } from '../../mol-util/param-definition'; +import { PluginStateObject } from '../objects'; + +export const OpenFiles = StateAction.build({ + display: { name: 'Open Files', description: 'Load one or more files and optionally create default visuals' }, + from: PluginStateObject.Root, + params: (a, ctx: PluginContext) => { + const { extensions, options } = ctx.dataFormats + return { + files: PD.FileList({ accept: Array.from(extensions.values()).map(e => `.${e}`).join(',') + ',.gz,.zip', multiple: true }), + format: PD.Select('auto', options), + visuals: PD.Boolean(true, { description: 'Add default visuals' }), + } + } +})(({ params, state }, plugin: PluginContext) => Task.create('Open Files', async taskCtx => { + plugin.behaviors.layout.leftPanelTabName.next('data'); + + await state.transaction(async () => { + if (params.files === null) { + plugin.log.error('No file(s) selected') + return + } + for (let i = 0, il = params.files.length; i < il; ++i) { + try { + const file = params.files[i] + const info = getFileInfo(file) + const isBinary = plugin.dataFormats.binaryExtensions.has(info.ext) + const { data } = await plugin.builders.data.readFile({ file, isBinary }); + const provider = params.format === 'auto' + ? plugin.dataFormats.auto(info, data.cell?.obj!) + : plugin.dataFormats.get(params.format) + + if (!provider) { + plugin.log.warn(`OpenFiles: could not find data provider for '${info.name}.${info.ext}'`); + continue; + } + + // need to await so that the enclosing Task finishes after the update is done. + const parsed = await provider.parse(plugin, data); + if (params.visuals) { + await provider.visuals?.(plugin, parsed); + } + } catch (e) { + plugin.log.error(e) + } + } + }).runInContext(taskCtx); +})); \ No newline at end of file diff --git a/src/mol-plugin-state/actions/shape.ts b/src/mol-plugin-state/actions/shape.ts deleted file mode 100644 index 9db4c919b32f09aa0f574c4e17127ed1d4f41b01..0000000000000000000000000000000000000000 --- a/src/mol-plugin-state/actions/shape.ts +++ /dev/null @@ -1,33 +0,0 @@ -/** - * Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info. - * - * @author Alexander Rose <alexander.rose@weirdbyte.de> - */ - -import { PluginContext } from '../../mol-plugin/context'; -import { State, StateBuilder } from '../../mol-state'; -import { Task } from '../../mol-task'; -import { FileInfo } from '../../mol-util/file-info'; -import { StateTransforms } from '../transforms'; -import { DataFormatProvider, DataFormatBuilderOptions } from './data-format'; - -export const PlyProvider: DataFormatProvider<any> = { - label: 'PLY', - description: 'PLY', - stringExtensions: ['ply'], - binaryExtensions: [], - isApplicable: (info: FileInfo, data: string) => { - return info.ext === 'ply' - }, - getDefaultBuilder: (ctx: PluginContext, data, options: DataFormatBuilderOptions, state: State) => { - return Task.create('PLY default builder', async taskCtx => { - let tree: StateBuilder.To<any> = state.build().to(data) - .apply(StateTransforms.Data.ParsePly) - .apply(StateTransforms.Model.ShapeFromPly) - if (options.visuals) { - tree = tree.apply(StateTransforms.Representation.ShapeRepresentation3D) - } - await state.updateTree(tree).runInContext(taskCtx) - }) - } -} \ No newline at end of file diff --git a/src/mol-plugin-state/actions/structure.ts b/src/mol-plugin-state/actions/structure.ts index 297850ad1376b33cc770ca32fb03a02020fedd64..171504fce8e4cd4760885e8e67c60e21ebdeee02 100644 --- a/src/mol-plugin-state/actions/structure.ts +++ b/src/mol-plugin-state/actions/structure.ts @@ -8,140 +8,14 @@ import { PluginContext } from '../../mol-plugin/context'; import { StateAction, StateSelection, StateTransformer } from '../../mol-state'; import { Task } from '../../mol-task'; -import { FileInfo } from '../../mol-util/file-info'; import { ParamDefinition as PD } from '../../mol-util/param-definition'; -import { BuiltInTrajectoryFormat, BuildInTrajectoryFormats } from '../formats/trajectory'; +import { PresetStructureRepresentations } from '../builder/structure/representation-preset'; +import { BuiltInTrajectoryFormat, BuiltInTrajectoryFormats } from '../formats/trajectory'; import { RootStructureDefinition } from '../helpers/root-structure'; import { PluginStateObject } from '../objects'; import { StateTransforms } from '../transforms'; -import { Download, ParsePsf } from '../transforms/data'; -import { CoordinatesFromDcd, CustomModelProperties, CustomStructureProperties, TopologyFromPsf, TrajectoryFromModelAndCoordinates } from '../transforms/model'; -import { DataFormatProvider, guessCifVariant } from './data-format'; -import { PresetStructureRepresentations } from '../builder/structure/representation-preset'; - -// TODO make unitcell creation part of preset - -export const MmcifProvider: DataFormatProvider<PluginStateObject.Data.String | PluginStateObject.Data.Binary> = { - label: 'mmCIF', - description: 'mmCIF', - stringExtensions: ['cif', 'mmcif', 'mcif'], - binaryExtensions: ['bcif'], - isApplicable: (info: FileInfo, data: Uint8Array | string) => { - if (info.ext === 'mmcif' || info.ext === 'mcif') return true - // assume cif/bcif files that are not DensityServer CIF are mmCIF - if (info.ext === 'cif' || info.ext === 'bcif') return guessCifVariant(info, data) !== 'dscif' - return false - }, - getDefaultBuilder: (ctx: PluginContext, data, options) => { - return Task.create('mmCIF default builder', async () => { - const trajectory = await ctx.builders.structure.parseTrajectory(data, 'mmcif'); - const representationPreset = options.visuals ? 'auto' : 'empty'; - await ctx.builders.structure.hierarchy.applyPreset(trajectory, 'default', { showUnitcell: options.visuals, representationPreset }); - }) - } -} - -export const PdbProvider: DataFormatProvider<any> = { - label: 'PDB', - description: 'PDB', - stringExtensions: ['pdb', 'ent'], - binaryExtensions: [], - isApplicable: (info: FileInfo, data: string) => { - return info.ext === 'pdb' || info.ext === 'ent' - }, - getDefaultBuilder: (ctx: PluginContext, data, options) => { - return Task.create('PDB default builder', async () => { - const trajectory = await ctx.builders.structure.parseTrajectory(data, 'pdb'); - const representationPreset = options.visuals ? 'auto' : 'empty'; - await ctx.builders.structure.hierarchy.applyPreset(trajectory, 'default', { showUnitcell: options.visuals, representationPreset }); - }) - } -} - -export const GroProvider: DataFormatProvider<any> = { - label: 'GRO', - description: 'GRO', - stringExtensions: ['gro'], - binaryExtensions: [], - isApplicable: (info: FileInfo, data: string) => { - return info.ext === 'gro' - }, - getDefaultBuilder: (ctx: PluginContext, data, options) => { - return Task.create('GRO default builder', async () => { - const trajectory = await ctx.builders.structure.parseTrajectory(data, 'gro'); - const representationPreset = options.visuals ? 'auto' : 'empty'; - await ctx.builders.structure.hierarchy.applyPreset(trajectory, 'default', { showUnitcell: options.visuals, representationPreset }); - }) - } -} - -export const MolProvider: DataFormatProvider<any> = { - label: 'MOL', - description: 'MOL', - stringExtensions: ['mol', 'sdf'], - binaryExtensions: [], - isApplicable: (info: FileInfo, data: string) => { - return info.ext === 'mol' || info.ext === 'sdf' - }, - getDefaultBuilder: (ctx: PluginContext, data, options) => { - return Task.create('MOL default builder', async () => { - const trajectory = await ctx.builders.structure.parseTrajectory(data, 'mol'); - const representationPreset = options.visuals ? 'atomic-detail' : 'empty'; - await ctx.builders.structure.hierarchy.applyPreset(trajectory, 'default', { showUnitcell: options.visuals, representationPreset }); - }) - } -} - -export const Provider3dg: DataFormatProvider<any> = { - label: '3DG', - description: '3DG', - stringExtensions: ['3dg'], - binaryExtensions: [], - isApplicable: (info: FileInfo, data: string) => { - return info.ext === '3dg' - }, - getDefaultBuilder: (ctx: PluginContext, data, options) => { - return Task.create('3DG default builder', async () => { - const trajectory = await ctx.builders.structure.parseTrajectory(data, '3dg'); - const representationPreset = options.visuals ? 'auto' : 'empty'; - await ctx.builders.structure.hierarchy.applyPreset(trajectory, 'default', { showUnitcell: options.visuals, representationPreset }); - }) - } -} - -export const PsfProvider: DataFormatProvider<any> = { - label: 'PSF', - description: 'PSF', - stringExtensions: ['psf'], - binaryExtensions: [], - isApplicable: (info: FileInfo, data: string) => { - return info.ext === 'psf' - }, - getDefaultBuilder: (ctx: PluginContext, data, options, state) => { - return Task.create('PSF default builder', async taskCtx => { - const build = state.build().to(data).apply(ParsePsf, {}, { state: { isGhost: true } }).apply(TopologyFromPsf) - await state.updateTree(build).runInContext(taskCtx) - }) - } -} - -export const DcdProvider: DataFormatProvider<any> = { - label: 'DCD', - description: 'DCD', - stringExtensions: [], - binaryExtensions: ['dcd'], - isApplicable: (info: FileInfo, data: string) => { - return info.ext === 'dcd' - }, - getDefaultBuilder: (ctx: PluginContext, data, options, state) => { - return Task.create('DCD default builder', async taskCtx => { - const build = state.build().to(data).apply(CoordinatesFromDcd); - await state.updateTree(build).runInContext(taskCtx) - }) - } -} - -// +import { Download } from '../transforms/data'; +import { CustomModelProperties, CustomStructureProperties, TrajectoryFromModelAndCoordinates } from '../transforms/model'; const DownloadModelRepresentationOptions = (plugin: PluginContext) => PD.Group({ type: RootStructureDefinition.getParams(void 0, 'auto').type, @@ -192,7 +66,7 @@ const DownloadStructure = StateAction.build({ }, { isFlat: true, label: 'PubChem', description: 'Loads 3D conformer from PubChem.' }), 'url': PD.Group({ url: PD.Text(''), - format: PD.Select<BuiltInTrajectoryFormat>('mmcif', PD.arrayToOptions(BuildInTrajectoryFormats.map(f => f[0]), f => f)), + format: PD.Select<BuiltInTrajectoryFormat>('mmcif', PD.arrayToOptions(BuiltInTrajectoryFormats.map(f => f[0]), f => f)), isBinary: PD.Boolean(false), options }, { isFlat: true, label: 'URL' }) diff --git a/src/mol-plugin-state/actions/volume.ts b/src/mol-plugin-state/actions/volume.ts index 2fc544875129850a318709e869fdc3e31c63d88b..6a5383c38ef38216495ed86a060d2646084577a2 100644 --- a/src/mol-plugin-state/actions/volume.ts +++ b/src/mol-plugin-state/actions/volume.ts @@ -5,102 +5,14 @@ * @author Alexander Rose <alexander.rose@weirdbyte.de> */ -import { VolumeIsoValue } from '../../mol-model/volume'; import { PluginContext } from '../../mol-plugin/context'; -import { State, StateAction, StateBuilder, StateTransformer } from '../../mol-state'; +import { StateAction, StateTransformer } from '../../mol-state'; import { Task } from '../../mol-task'; -import { ColorNames } from '../../mol-util/color/names'; -import { FileInfo, getFileInfo } from '../../mol-util/file-info'; +import { getFileInfo } from '../../mol-util/file-info'; import { ParamDefinition as PD } from '../../mol-util/param-definition'; import { PluginStateObject } from '../objects'; -import { StateTransforms } from '../transforms'; import { Download } from '../transforms/data'; -import { VolumeRepresentation3DHelpers } from '../transforms/representation'; -import { DataFormatProvider, guessCifVariant, DataFormatBuilderOptions } from './data-format'; - -export const Ccp4Provider: DataFormatProvider<any> = { - label: 'CCP4/MRC/BRIX', - description: 'CCP4/MRC/BRIX', - stringExtensions: [], - binaryExtensions: ['ccp4', 'mrc', 'map'], - isApplicable: (info: FileInfo, data: Uint8Array) => { - return info.ext === 'ccp4' || info.ext === 'mrc' || info.ext === 'map' - }, - getDefaultBuilder: (ctx: PluginContext, data, options: DataFormatBuilderOptions, state: State) => { - return Task.create('CCP4/MRC/BRIX default builder', async taskCtx => { - let tree: StateBuilder.To<any> = state.build().to(data) - .apply(StateTransforms.Data.ParseCcp4, {}, { state: { isGhost: true } }) - .apply(StateTransforms.Volume.VolumeFromCcp4) - if (options.visuals) { - tree = tree.apply(StateTransforms.Representation.VolumeRepresentation3D) - } - await state.updateTree(tree).runInContext(taskCtx) - }) - } -} - -export const Dsn6Provider: DataFormatProvider<any> = { - label: 'DSN6/BRIX', - description: 'DSN6/BRIX', - stringExtensions: [], - binaryExtensions: ['dsn6', 'brix'], - isApplicable: (info: FileInfo, data: Uint8Array) => { - return info.ext === 'dsn6' || info.ext === 'brix' - }, - getDefaultBuilder: (ctx: PluginContext, data, options: DataFormatBuilderOptions, state: State) => { - return Task.create('DSN6/BRIX default builder', async taskCtx => { - let tree: StateBuilder.To<any> = state.build().to(data) - .apply(StateTransforms.Data.ParseDsn6, {}, { state: { isGhost: true } }) - .apply(StateTransforms.Volume.VolumeFromDsn6) - if (options.visuals) { - tree = tree.apply(StateTransforms.Representation.VolumeRepresentation3D) - } - await state.updateTree(tree).runInContext(taskCtx) - }) - } -} - -export const DscifProvider: DataFormatProvider<any> = { - label: 'DensityServer CIF', - description: 'DensityServer CIF', - stringExtensions: ['cif'], - binaryExtensions: ['bcif'], - isApplicable: (info: FileInfo, data: Uint8Array | string) => { - return guessCifVariant(info, data) === 'dscif' ? true : false - }, - getDefaultBuilder: (ctx: PluginContext, data, options: DataFormatBuilderOptions, state: State) => { - return Task.create('DensityServer CIF default builder', async taskCtx => { - const cifBuilder = state.build().to(data).apply(StateTransforms.Data.ParseCif) - const cifStateObject = await state.updateTree(cifBuilder).runInContext(taskCtx) - const b = state.build().to(cifBuilder.ref); - const blocks = cifStateObject.data.blocks.slice(1); // zero block contains query meta-data - let tree: StateBuilder.To<any, any> - if (blocks.length === 1) { - tree = b - .apply(StateTransforms.Volume.VolumeFromDensityServerCif, { blockHeader: blocks[0].header }) - if (options.visuals) { - tree = tree.apply(StateTransforms.Representation.VolumeRepresentation3D, VolumeRepresentation3DHelpers.getDefaultParamsStatic(ctx, 'isosurface', { isoValue: VolumeIsoValue.relative(1.5), alpha: 0.3 }, 'uniform', { value: ColorNames.teal })) - } - } else if (blocks.length === 2) { - tree = b - .apply(StateTransforms.Volume.VolumeFromDensityServerCif, { blockHeader: blocks[0].header }) - if (options.visuals) { - tree = tree.apply(StateTransforms.Representation.VolumeRepresentation3D, VolumeRepresentation3DHelpers.getDefaultParamsStatic(ctx, 'isosurface', { isoValue: VolumeIsoValue.relative(1.5), alpha: 0.3 }, 'uniform', { value: ColorNames.blue })) - } - const vol = tree.to(cifBuilder.ref) - .apply(StateTransforms.Volume.VolumeFromDensityServerCif, { blockHeader: blocks[1].header }) - const posParams = VolumeRepresentation3DHelpers.getDefaultParamsStatic(ctx, 'isosurface', { isoValue: VolumeIsoValue.relative(3), alpha: 0.3 }, 'uniform', { value: ColorNames.green }) - tree = vol.apply(StateTransforms.Representation.VolumeRepresentation3D, posParams) - const negParams = VolumeRepresentation3DHelpers.getDefaultParamsStatic(ctx, 'isosurface', { isoValue: VolumeIsoValue.relative(-3), alpha: 0.3 }, 'uniform', { value: ColorNames.red }) - tree = tree.to(vol.ref).apply(StateTransforms.Representation.VolumeRepresentation3D, negParams) - } else { - throw new Error('unknown number of blocks') - } - - await state.updateTree(tree).runInContext(taskCtx); - }) - } -} +import { DataFormatProvider } from '../formats/provider'; export { DownloadDensity }; type DownloadDensity = typeof DownloadDensity @@ -108,7 +20,7 @@ const DownloadDensity = StateAction.build({ from: PluginStateObject.Root, display: { name: 'Download Density', description: 'Load a density from the provided source and create its default visual.' }, params: (a, ctx: PluginContext) => { - const { options } = ctx.dataFormat.registry + const { options } = ctx.dataFormats return { source: PD.MappedStatic('pdb-xray', { 'pdb-xray': PD.Group({ @@ -147,10 +59,10 @@ const DownloadDensity = StateAction.build({ }) } } -})(({ params, state }, ctx: PluginContext) => Task.create('Download Density', async taskCtx => { +})(({ params }, plugin: PluginContext) => Task.create('Download Density', async taskCtx => { const src = params.source; let downloadParams: StateTransformer.Params<Download>; - let provider: DataFormatProvider<any> + let provider: DataFormatProvider | undefined; switch (src.name) { case 'url': @@ -196,24 +108,30 @@ const DownloadDensity = StateAction.build({ default: throw new Error(`${(src as any).name} not supported.`); } - const data = await ctx.builders.data.download(downloadParams); + const data = await plugin.builders.data.download(downloadParams); switch (src.name) { case 'url': downloadParams = src.params; - provider = src.params.format === 'auto' ? ctx.dataFormat.registry.auto(getFileInfo(downloadParams.url), data.cell?.obj!) : ctx.dataFormat.registry.get(src.params.format) + provider = src.params.format === 'auto' ? plugin.dataFormats.auto(getFileInfo(downloadParams.url), data.cell?.obj!) : plugin.dataFormats.get(src.params.format) break; case 'pdb-xray': provider = src.params.provider.server === 'pdbe' - ? ctx.dataFormat.registry.get('ccp4') - : ctx.dataFormat.registry.get('dsn6') + ? plugin.dataFormats.get('ccp4') + : plugin.dataFormats.get('dsn6') break; case 'pdb-emd-ds': case 'pdb-xray-ds': - provider = ctx.dataFormat.registry.get('dscif') + provider = plugin.dataFormats.get('dscif') break; default: throw new Error(`${(src as any).name} not supported.`); } - await provider.getDefaultBuilder(ctx, data, { visuals: true }, state).runInContext(taskCtx) + if (!provider) { + plugin.log.warn('DownloadDensity: Format provider not found.'); + return; + } + + const volumes = await provider.parse(plugin, data); + await provider.visuals?.(plugin, volumes); })); \ No newline at end of file diff --git a/src/mol-plugin-state/animation/built-in.ts b/src/mol-plugin-state/animation/built-in.ts index f804def83bf3acc7c89461d0527636d1c8653c1b..f3fde2bb93d40f71e35ef8a1dac3943eeb0dfae2 100644 --- a/src/mol-plugin-state/animation/built-in.ts +++ b/src/mol-plugin-state/animation/built-in.ts @@ -105,7 +105,7 @@ export const AnimateAssemblyUnwind = PluginStateAnimation.create({ }; }, initialState: () => ({ t: 0 }), - async setup(params, plugin) { + setup(params, plugin) { const state = plugin.state.data; const root = !params.target || params.target === 'all' ? StateTransform.RootRef : params.target; const reprs = state.select(StateSelection.Generators.ofType(PluginStateObject.Molecule.Structure.Representation3D, root)); @@ -123,9 +123,9 @@ export const AnimateAssemblyUnwind = PluginStateAnimation.create({ if (!changed) return; - return plugin.updateDataState(update, { doNotUpdateCurrent: true }); + return update.commit({ doNotUpdateCurrent: true }); }, - async teardown(_, plugin) { + teardown(_, plugin) { const state = plugin.state.data; const reprs = state.select(StateSelection.Generators.ofType(PluginStateObject.Molecule.Structure.Representation3DState) .withTag('animate-assembly-unwind')); @@ -133,7 +133,7 @@ export const AnimateAssemblyUnwind = PluginStateAnimation.create({ const update = state.build(); for (const r of reprs) update.delete(r.transform.ref); - return plugin.updateDataState(update); + return update.commit(); }, async apply(animState, t, ctx) { const state = ctx.plugin.state.data; @@ -190,9 +190,9 @@ export const AnimateUnitsExplode = PluginStateAnimation.create({ if (!changed) return; - return plugin.updateDataState(update, { doNotUpdateCurrent: true }); + return update.commit({ doNotUpdateCurrent: true }); }, - async teardown(_, plugin) { + teardown(_, plugin) { const state = plugin.state.data; const reprs = state.select(StateSelection.Generators.ofType(PluginStateObject.Molecule.Structure.Representation3DState) .withTag('animate-units-explode')); @@ -200,7 +200,7 @@ export const AnimateUnitsExplode = PluginStateAnimation.create({ const update = state.build(); for (const r of reprs) update.delete(r.transform.ref); - return plugin.updateDataState(update); + return update.commit(); }, async apply(animState, t, ctx) { const state = ctx.plugin.state.data; diff --git a/src/mol-plugin-state/builder/data.ts b/src/mol-plugin-state/builder/data.ts index 45508c2526a1fd2e9139d10662f1ab2a8c0ae905..e628bf7aa9be06d73c001a60a36d4b4aead3349a 100644 --- a/src/mol-plugin-state/builder/data.ts +++ b/src/mol-plugin-state/builder/data.ts @@ -14,29 +14,25 @@ export class DataBuilder { return this.plugin.state.data; } - async rawData(params: StateTransformer.Params<RawData>, options?: Partial<StateTransform.Options>) { + rawData(params: StateTransformer.Params<RawData>, options?: Partial<StateTransform.Options>) { const data = this.dataState.build().toRoot().apply(RawData, params, options); - await this.plugin.updateDataState(data, { revertOnError: true }); - return data.selector; + return data.commit({ revertOnError: true }); } - async download(params: StateTransformer.Params<Download>, options?: Partial<StateTransform.Options>) { + download(params: StateTransformer.Params<Download>, options?: Partial<StateTransform.Options>) { const data = this.dataState.build().toRoot().apply(Download, params, options); - await this.plugin.updateDataState(data, { revertOnError: true }); - return data.selector; + return data.commit({ revertOnError: true }); } - async downloadBlob(params: StateTransformer.Params<DownloadBlob>, options?: Partial<StateTransform.Options>) { + downloadBlob(params: StateTransformer.Params<DownloadBlob>, options?: Partial<StateTransform.Options>) { const data = this.dataState.build().toRoot().apply(DownloadBlob, params, options); - await this.plugin.updateDataState(data, { revertOnError: true }); - return data.selector; + return data.commit({ revertOnError: true }); } async readFile(params: StateTransformer.Params<ReadFile>, options?: Partial<StateTransform.Options>) { - const data = this.dataState.build().toRoot().apply(ReadFile, params, options); - const fileInfo = getFileInfo(params.file); - await this.plugin.updateDataState(data, { revertOnError: true }); - return { data: data.selector, fileInfo }; + const data = await this.dataState.build().toRoot().apply(ReadFile, params, options).commit({ revertOnError: true }); + const fileInfo = getFileInfo(params.file || ''); + return { data: data, fileInfo }; } constructor(public plugin: PluginContext) { diff --git a/src/mol-plugin-state/builder/structure.ts b/src/mol-plugin-state/builder/structure.ts index 82bb1af27c6d479f883a788893e5d16eeb6f0dad..001312e7aafc6de9dd299605d8be2fd0eb769467 100644 --- a/src/mol-plugin-state/builder/structure.ts +++ b/src/mol-plugin-state/builder/structure.ts @@ -26,27 +26,26 @@ export class StructureBuilder { } private async parseTrajectoryData(data: StateObjectRef<SO.Data.Binary | SO.Data.String>, format: BuiltInTrajectoryFormat | TrajectoryFormatProvider) { - const provider = typeof format === 'string' ? this.plugin.dataFormat.trajectory.get(format) : format; + const provider = typeof format === 'string' ? this.plugin.dataFormats.get(format) as TrajectoryFormatProvider : format; if (!provider) throw new Error(`'${format}' is not a supported data format.`); const { trajectory } = await provider.parse(this.plugin, data); return trajectory; } - private async parseTrajectoryBlob(data: StateObjectRef<SO.Data.Blob>, params: StateTransformer.Params<StateTransforms['Data']['ParseBlob']>) { + private parseTrajectoryBlob(data: StateObjectRef<SO.Data.Blob>, params: StateTransformer.Params<StateTransforms['Data']['ParseBlob']>) { const state = this.dataState; const trajectory = state.build().to(data) .apply(StateTransforms.Data.ParseBlob, params, { state: { isGhost: true } }) .apply(StateTransforms.Model.TrajectoryFromBlob, void 0); - await this.plugin.updateDataState(trajectory, { revertOnError: true }); - return trajectory.selector; + return trajectory.commit({ revertOnError: true }); } readonly hierarchy = new TrajectoryHierarchyBuilder(this.plugin); readonly representation = new StructureRepresentationBuilder(this.plugin); - async parseTrajectory(data: StateObjectRef<SO.Data.Binary | SO.Data.String>, format: BuiltInTrajectoryFormat | TrajectoryFormatProvider): Promise<StateObjectSelector<SO.Molecule.Trajectory>> - async parseTrajectory(blob: StateObjectRef<SO.Data.Blob>, params: StateTransformer.Params<StateTransforms['Data']['ParseBlob']>): Promise<StateObjectSelector<SO.Molecule.Trajectory>> - async parseTrajectory(data: StateObjectRef, params: any) { + parseTrajectory(data: StateObjectRef<SO.Data.Binary | SO.Data.String>, format: BuiltInTrajectoryFormat | TrajectoryFormatProvider): Promise<StateObjectSelector<SO.Molecule.Trajectory>> + parseTrajectory(blob: StateObjectRef<SO.Data.Blob>, params: StateTransformer.Params<StateTransforms['Data']['ParseBlob']>): Promise<StateObjectSelector<SO.Molecule.Trajectory>> + parseTrajectory(data: StateObjectRef, params: any) { const cell = StateObjectRef.resolveAndCheck(this.dataState, data as StateObjectRef); if (!cell) throw new Error('Invalid data cell.'); @@ -57,24 +56,22 @@ export class StructureBuilder { } } - async createModel(trajectory: StateObjectRef<SO.Molecule.Trajectory>, params?: StateTransformer.Params<StateTransforms['Model']['ModelFromTrajectory']>, initialState?: Partial<StateTransform.State>) { + createModel(trajectory: StateObjectRef<SO.Molecule.Trajectory>, params?: StateTransformer.Params<StateTransforms['Model']['ModelFromTrajectory']>, initialState?: Partial<StateTransform.State>) { const state = this.dataState; const model = state.build().to(trajectory) .apply(StateTransforms.Model.ModelFromTrajectory, params || { modelIndex: 0 }, { state: initialState }); - await this.plugin.updateDataState(model, { revertOnError: true }); - return model.selector; + return model.commit({ revertOnError: true }); } - async insertModelProperties(model: StateObjectRef<SO.Molecule.Model>, params?: StateTransformer.Params<StateTransforms['Model']['CustomModelProperties']>, initialState?: Partial<StateTransform.State>) { + insertModelProperties(model: StateObjectRef<SO.Molecule.Model>, params?: StateTransformer.Params<StateTransforms['Model']['CustomModelProperties']>, initialState?: Partial<StateTransform.State>) { const state = this.dataState; const props = state.build().to(model) .apply(StateTransforms.Model.CustomModelProperties, params, { state: initialState }); - await this.plugin.updateDataState(props, { revertOnError: true }); - return props.selector; + return props.commit({ revertOnError: true }); } - async tryCreateUnitcell(model: StateObjectRef<SO.Molecule.Model>, params?: StateTransformer.Params<StateTransforms['Representation']['ModelUnitcell3D']>, initialState?: Partial<StateTransform.State>) { + tryCreateUnitcell(model: StateObjectRef<SO.Molecule.Model>, params?: StateTransformer.Params<StateTransforms['Representation']['ModelUnitcell3D']>, initialState?: Partial<StateTransform.State>) { const state = this.dataState; const m = StateObjectRef.resolveAndCheck(state, model)?.obj?.data; if (!m) return; @@ -83,11 +80,10 @@ export class StructureBuilder { const unitcell = state.build().to(model) .apply(StateTransforms.Representation.ModelUnitcell3D, params, { state: initialState }); - await this.plugin.updateDataState(unitcell, { revertOnError: true }); - return unitcell.selector; + return unitcell.commit({ revertOnError: true }); } - async createStructure(modelRef: StateObjectRef<SO.Molecule.Model>, params?: RootStructureDefinition.Params, initialState?: Partial<StateTransform.State>) { + createStructure(modelRef: StateObjectRef<SO.Molecule.Model>, params?: RootStructureDefinition.Params, initialState?: Partial<StateTransform.State>) { const state = this.dataState; if (!params) { @@ -101,16 +97,14 @@ export class StructureBuilder { const structure = state.build().to(modelRef) .apply(StateTransforms.Model.StructureFromModel, { type: params || { name: 'assembly', params: { } } }, { state: initialState }); - await this.plugin.updateDataState(structure, { revertOnError: true }); - return structure.selector; + return structure.commit({ revertOnError: true }); } - async insertStructureProperties(structure: StateObjectRef<SO.Molecule.Structure>, params?: StateTransformer.Params<StateTransforms['Model']['CustomStructureProperties']>) { + insertStructureProperties(structure: StateObjectRef<SO.Molecule.Structure>, params?: StateTransformer.Params<StateTransforms['Model']['CustomStructureProperties']>) { const state = this.dataState; const props = state.build().to(structure) .apply(StateTransforms.Model.CustomStructureProperties, params); - await this.plugin.updateDataState(props, { revertOnError: true }); - return props.selector; + return props.commit({ revertOnError: true }); } isComponentTransform(cell: StateObjectCell) { @@ -128,13 +122,12 @@ export class StructureBuilder { tags: tags ? [...tags, keyTag] : [keyTag] }); - await this.plugin.updateDataState(component); + await component.commit(); const selector = component.selector; if (!selector.isOk || selector.cell?.obj?.data.elementCount === 0) { - const del = state.build().delete(selector.ref); - await this.plugin.updateDataState(del); + await state.build().delete(selector.ref).commit(); return; } diff --git a/src/mol-plugin-state/builder/structure/representation-preset.ts b/src/mol-plugin-state/builder/structure/representation-preset.ts index a9088281f6afe32730a01f5784ca224b3c539de7..2def1be284f8fc8a97c5051888fe6dc1fd972428 100644 --- a/src/mol-plugin-state/builder/structure/representation-preset.ts +++ b/src/mol-plugin-state/builder/structure/representation-preset.ts @@ -121,7 +121,8 @@ const polymerAndLigand = StructureRepresentationPresetProvider({ coarse: builder.buildRepresentation(update, components.coarse, { type: 'spacefill', typeParams, color: color || 'polymer-id' }, { tag: 'coarse' }) }; - await plugin.updateDataState(update, { revertOnError: false }); + await update.commit({ revertOnError: false }); + return { components, representations }; } }); @@ -148,7 +149,7 @@ const proteinAndNucleic = StructureRepresentationPresetProvider({ nucleic: builder.buildRepresentation(update, components.nucleic, { type: 'gaussian-surface', typeParams, color }, { tag: 'nucleic' }) }; - await plugin.updateDataState(update, { revertOnError: true }); + await update.commit({ revertOnError: true }); return { components, representations }; } }); @@ -189,7 +190,7 @@ const coarseSurface = StructureRepresentationPresetProvider({ polymer: builder.buildRepresentation(update, components.polymer, { type: 'gaussian-surface', typeParams: { ...typeParams, ...gaussianProps }, color }, { tag: 'polymer' }) }; - await plugin.updateDataState(update, { revertOnError: true }); + await update.commit({ revertOnError: true }); return { components, representations }; } }); @@ -214,7 +215,7 @@ const polymerCartoon = StructureRepresentationPresetProvider({ polymer: builder.buildRepresentation(update, components.polymer, { type: 'cartoon', typeParams, color }, { tag: 'polymer' }) }; - await plugin.updateDataState(update, { revertOnError: true }); + await update.commit({ revertOnError: true }); return { components, representations }; } }); @@ -239,7 +240,7 @@ const atomicDetail = StructureRepresentationPresetProvider({ all: builder.buildRepresentation(update, components.all, { type: 'ball-and-stick', typeParams, color }, { tag: 'all' }) }; - await plugin.updateDataState(update, { revertOnError: true }); + await update.commit({ revertOnError: true }); return { components, representations }; } }); diff --git a/src/mol-plugin-state/builder/structure/representation.ts b/src/mol-plugin-state/builder/structure/representation.ts index 7bde496a8f0d5c61a776c63ef2904313185a3782..ef15aaa5b492920f6fb77501c398f5b5b0c9e2d4 100644 --- a/src/mol-plugin-state/builder/structure/representation.ts +++ b/src/mol-plugin-state/builder/structure/representation.ts @@ -117,7 +117,7 @@ export class StructureRepresentationBuilder { const selector = this.buildRepresentation(repr, structure, props, options); if (!selector) return; - await this.plugin.updateDataState(repr); + await repr.commit(); return selector; } diff --git a/src/mol-plugin-state/formats/provider.ts b/src/mol-plugin-state/formats/provider.ts new file mode 100644 index 0000000000000000000000000000000000000000..eaa33bfa06698efe82b3f7cdda12604a5dbe1adf --- /dev/null +++ b/src/mol-plugin-state/formats/provider.ts @@ -0,0 +1,41 @@ +/** + * Copyright (c) 2019-2020 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 msgpackDecode from '../../mol-io/common/msgpack/decode'; +import { PluginContext } from '../../mol-plugin/context'; +import { StateObjectRef } from '../../mol-state'; +import { FileInfo } from '../../mol-util/file-info'; +import { PluginStateObject } from '../objects'; + +export interface DataFormatProvider<P = any, R = any, V = any> { + label: string, + description: string, + category?: string, + stringExtensions?: string[], + binaryExtensions?: string[], + isApplicable?(info: FileInfo, data: string | Uint8Array): boolean, + parse(plugin: PluginContext, data: StateObjectRef<PluginStateObject.Data.Binary | PluginStateObject.Data.String>, params?: P): Promise<R>, + visuals?(plugin: PluginContext, data: R): Promise<V> | undefined +} + +export function DataFormatProvider<P extends DataFormatProvider>(provider: P): P { return provider; } + +type cifVariants = 'dscif' | 'coreCif' | -1 +export function guessCifVariant(info: FileInfo, data: Uint8Array | string): cifVariants { + if (info.ext === 'bcif') { + try { + // TODO: find a way to run msgpackDecode only once + // now it is run twice, here and during file parsing + if (msgpackDecode(data as Uint8Array).encoder.startsWith('VolumeServer')) return 'dscif' + } catch { } + } else if (info.ext === 'cif') { + const str = data as string + if (str.startsWith('data_SERVER\n#\n_density_server_result')) return 'dscif' + if (str.includes('atom_site_fract_x') || str.includes('atom_site.fract_x')) return 'coreCif' + } + return -1 +} \ No newline at end of file diff --git a/src/mol-plugin-state/formats/registry.ts b/src/mol-plugin-state/formats/registry.ts index 5b6c3bc5c1328e31e940127f6997182825233e4e..9ed9f3fdf4e0f3f313f436fcb59b6ec6ba80d002 100644 --- a/src/mol-plugin-state/formats/registry.ts +++ b/src/mol-plugin-state/formats/registry.ts @@ -5,18 +5,20 @@ * @author David Sehnal <david.sehnal@gmail.com> */ -import msgpackDecode from '../../mol-io/common/msgpack/decode'; -import { PluginContext } from '../../mol-plugin/context'; -import { StateObjectRef } from '../../mol-state'; import { FileInfo } from '../../mol-util/file-info'; import { PluginStateObject } from '../objects'; +import { DataFormatProvider } from './provider'; +import { BuiltInTrajectoryFormats } from './trajectory'; +import { BuiltInVolumeFormats } from './volume'; +import { BuiltInShapeFormats } from './shape'; +import { BuiltInStructureFormats } from './structure'; -export class DataFormatRegistry<Provider extends DataFormatProvider> { - private _list: { name: string, provider: Provider }[] = [] - private _map = new Map<string, Provider>() +export class DataFormatRegistry { + private _list: { name: string, provider: DataFormatProvider }[] = [] + private _map = new Map<string, DataFormatProvider>() private _extensions: Set<string> | undefined = undefined private _binaryExtensions: Set<string> | undefined = undefined - private _options: [string, string][] | undefined = undefined + private _options: [string, string, string][] | undefined = undefined get types(): [string, string][] { return this._list.map(e => [e.name, e.provider.label] as [string, string]); @@ -26,8 +28,8 @@ export class DataFormatRegistry<Provider extends DataFormatProvider> { if (this._extensions) return this._extensions const extensions = new Set<string>() this._list.forEach(({ provider }) => { - provider.stringExtensions.forEach(ext => extensions.add(ext)) - provider.binaryExtensions.forEach(ext => extensions.add(ext)) + provider.stringExtensions?.forEach(ext => extensions.add(ext)) + provider.binaryExtensions?.forEach(ext => extensions.add(ext)) }) this._extensions = extensions return extensions @@ -36,21 +38,24 @@ export class DataFormatRegistry<Provider extends DataFormatProvider> { get binaryExtensions() { if (this._binaryExtensions) return this._binaryExtensions const binaryExtensions = new Set<string>() - this._list.forEach(({ provider }) => provider.binaryExtensions.forEach(ext => binaryExtensions.add(ext))) + this._list.forEach(({ provider }) => provider.binaryExtensions?.forEach(ext => binaryExtensions.add(ext))) this._binaryExtensions = binaryExtensions return binaryExtensions } get options() { if (this._options) return this._options - const options: [string, string][] = [['auto', 'Automatic']] - this._list.forEach(({ name, provider }) => options.push([ name, provider.label ])) + const options: [string, string, string][] = [['auto', 'Automatic', '']] + this._list.forEach(({ name, provider }) => options.push([ name, provider.label, provider.category || '' ])) this._options = options return options } - constructor(buildInFormats: ReadonlyArray<readonly [string, Provider]>) { - for (const [id, p] of buildInFormats) this.add(id, p); + constructor() { + for (const [id, p] of BuiltInVolumeFormats) this.add(id, p); + for (const [id, p] of BuiltInStructureFormats) this.add(id, p); + for (const [id, p] of BuiltInShapeFormats) this.add(id, p); + for (const [id, p] of BuiltInTrajectoryFormats) this.add(id, p); }; private _clear() { @@ -59,7 +64,7 @@ export class DataFormatRegistry<Provider extends DataFormatProvider> { this._options = undefined } - add(name: string, provider: Provider) { + add(name: string, provider: DataFormatProvider) { this._clear() this._list.push({ name, provider }) this._map.set(name, provider) @@ -74,12 +79,16 @@ export class DataFormatRegistry<Provider extends DataFormatProvider> { auto(info: FileInfo, dataStateObject: PluginStateObject.Data.Binary | PluginStateObject.Data.String) { for (let i = 0, il = this.list.length; i < il; ++i) { const { provider } = this._list[i]; - if (provider.isApplicable(info, dataStateObject.data)) return provider; + + let hasExt = false; + if (provider.binaryExtensions && provider.binaryExtensions.indexOf(info.ext) >= 0) hasExt = true; + else if (provider.stringExtensions && provider.stringExtensions.indexOf(info.ext) >= 0) hasExt = true; + if (hasExt && (!provider.isApplicable || provider.isApplicable(info, dataStateObject.data))) return provider; } return; } - get(name: string): Provider | undefined { + get(name: string): DataFormatProvider | undefined { if (this._map.has(name)) { return this._map.get(name)! } else { @@ -90,27 +99,4 @@ export class DataFormatRegistry<Provider extends DataFormatProvider> { get list() { return this._list } -} - -export interface DataFormatProvider<P = any, R = any> { - label: string - description: string - stringExtensions: string[] - binaryExtensions: string[] - isApplicable(info: FileInfo, data: string | Uint8Array): boolean - parse(plugin: PluginContext, data: StateObjectRef<PluginStateObject.Data.Binary | PluginStateObject.Data.String>, params?: P): Promise<R> -} - -type cifVariants = 'dscif' | -1 -export function guessCifVariant(info: FileInfo, data: Uint8Array | string): cifVariants { - if (info.ext === 'bcif') { - try { - // TODO: find a way to run msgpackDecode only once - // now it is run twice, here and during file parsing - if (msgpackDecode(data as Uint8Array).encoder.startsWith('VolumeServer')) return 'dscif' - } catch { } - } else if (info.ext === 'cif') { - if ((data as string).startsWith('data_SERVER\n#\n_density_server_result')) return 'dscif' - } - return -1 } \ No newline at end of file diff --git a/src/mol-plugin-state/formats/shape.ts b/src/mol-plugin-state/formats/shape.ts new file mode 100644 index 0000000000000000000000000000000000000000..cc02ee10c1a667708a130af62246f3b7174e25fc --- /dev/null +++ b/src/mol-plugin-state/formats/shape.ts @@ -0,0 +1,35 @@ +/** + * Copyright (c) 2018-2020 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 { StateTransforms } from '../transforms'; +import { DataFormatProvider } from './provider'; + +const Category = 'Shape'; + +export const PlyProvider = DataFormatProvider({ + label: 'PLY', + description: 'PLY', + category: Category, + stringExtensions: ['ply'], + parse: async (plugin, data) => { + const format = plugin.state.data.build() + .to(data) + .apply(StateTransforms.Data.ParsePly, {}, { state: { isGhost: true } }); + + const shape = format.apply(StateTransforms.Model.ShapeFromPly); + + await format.commit(); + + return { format: format.selector, shape: shape.selector }; + } +}); + +export const BuiltInShapeFormats = [ + ['ply', PlyProvider] as const, +] as const + +export type BuildInShapeFormat = (typeof BuiltInShapeFormats)[number][0] \ No newline at end of file diff --git a/src/mol-plugin-state/formats/structure.ts b/src/mol-plugin-state/formats/structure.ts new file mode 100644 index 0000000000000000000000000000000000000000..f534e20da4c09f68de15c62cc0ad558fd2c7fb8e --- /dev/null +++ b/src/mol-plugin-state/formats/structure.ts @@ -0,0 +1,49 @@ +/** + * Copyright (c) 2018-2020 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 { StateTransforms } from '../transforms'; +import { DataFormatProvider } from './provider'; + +const Category = 'Structure'; + +export const PsfProvider = DataFormatProvider({ + label: 'PSF', + description: 'PSF', + category: Category, + stringExtensions: ['psf'], + parse: async (plugin, data) => { + const format = plugin.state.data.build() + .to(data) + .apply(StateTransforms.Data.ParsePsf, {}, { state: { isGhost: true } }); + const topology = format.apply(StateTransforms.Model.TopologyFromPsf); + + await format.commit(); + + return { format: format.selector, topology: topology.selector }; + } +}); + +export const DcdProvider = DataFormatProvider({ + label: 'DCD', + description: 'DCD', + category: Category, + binaryExtensions: ['dcd'], + parse: (plugin, data) => { + const coordinates = plugin.state.data.build() + .to(data) + .apply(StateTransforms.Model.CoordinatesFromDcd); + + return coordinates.commit(); + } +}); + +export const BuiltInStructureFormats = [ + ['psf', PsfProvider] as const, + ['dcd', DcdProvider] as const, +] as const + +export type BuildInStructureFormat = (typeof BuiltInStructureFormats)[number][0] \ No newline at end of file diff --git a/src/mol-plugin-state/formats/trajectory.ts b/src/mol-plugin-state/formats/trajectory.ts index 9a735feab4a41f9ab53741e31097c2b49cd903b3..c888550f2c1808fefb31880dd10b630bb5d32141 100644 --- a/src/mol-plugin-state/formats/trajectory.ts +++ b/src/mol-plugin-state/formats/trajectory.ts @@ -5,105 +5,131 @@ * @author Alexander Rose <alexander.rose@weirdbyte.de> */ -import { FileInfo } from '../../mol-util/file-info'; import { StateTransforms } from '../transforms'; -import { guessCifVariant, DataFormatProvider, DataFormatRegistry } from './registry'; +import { guessCifVariant, DataFormatProvider } from './provider'; import { StateTransformer, StateObjectRef } from '../../mol-state'; import { PluginStateObject } from '../objects'; +import { PluginContext } from '../../mol-plugin/context'; export interface TrajectoryFormatProvider<P extends { trajectoryTags?: string | string[] } = { trajectoryTags?: string | string[] }, R extends { trajectory: StateObjectRef<PluginStateObject.Molecule.Trajectory> } = { trajectory: StateObjectRef<PluginStateObject.Molecule.Trajectory> }> extends DataFormatProvider<P, R> { } -export function TrajectoryFormatRegistry() { - return new DataFormatRegistry<TrajectoryFormatProvider>(BuildInTrajectoryFormats); +const Category = 'Trajectory'; + +function defaultVisuals(plugin: PluginContext, data: { trajectory: StateObjectRef<PluginStateObject.Molecule.Trajectory> }) { + return plugin.builders.structure.hierarchy.applyPreset(data.trajectory, 'default'); } export const MmcifProvider: TrajectoryFormatProvider = { label: 'mmCIF', description: 'mmCIF', + category: Category, stringExtensions: ['cif', 'mmcif', 'mcif'], binaryExtensions: ['bcif'], - isApplicable: (info: FileInfo, data: Uint8Array | string) => { + isApplicable: (info, data) => { if (info.ext === 'mmcif' || info.ext === 'mcif') return true - // assume cif/bcif files that are not DensityServer CIF are mmCIF - if (info.ext === 'cif' || info.ext === 'bcif') return guessCifVariant(info, data) !== 'dscif' + // assume undetermined cif/bcif files are mmCIF + if (info.ext === 'cif' || info.ext === 'bcif') return guessCifVariant(info, data) === -1 return false }, parse: async (plugin, data, params) => { const state = plugin.state.data; const cif = state.build().to(data) .apply(StateTransforms.Data.ParseCif, void 0, { state: { isGhost: true } }) - const trajectory = cif.apply(StateTransforms.Model.TrajectoryFromMmCif, void 0, { tags: params?.trajectoryTags }) - await plugin.updateDataState(trajectory, { revertOnError: true }); + const trajectory = await cif + .apply(StateTransforms.Model.TrajectoryFromMmCif, void 0, { tags: params?.trajectoryTags }) + .commit({ revertOnError: true }); + if ((cif.selector.cell?.obj?.data.blocks.length || 0) > 1) { plugin.state.data.updateCellState(cif.ref, { isGhost: false }); } - return { trajectory: trajectory.selector }; - } + + return { trajectory }; + }, + visuals: defaultVisuals +} + +export const CifCoreProvider: TrajectoryFormatProvider = { + label: 'cifCore', + description: 'CIF Core', + category: Category, + stringExtensions: ['cif'], + isApplicable: (info, data) => { + if (info.ext === 'cif') return guessCifVariant(info, data) === 'coreCif' + return false + }, + parse: async (plugin, data, params) => { + const state = plugin.state.data; + const cif = state.build().to(data) + .apply(StateTransforms.Data.ParseCif, void 0, { state: { isGhost: true } }) + const trajectory = await cif + .apply(StateTransforms.Model.TrajectoryFromCifCore, void 0, { tags: params?.trajectoryTags }) + .commit({ revertOnError: true }); + + if ((cif.selector.cell?.obj?.data.blocks.length || 0) > 1) { + plugin.state.data.updateCellState(cif.ref, { isGhost: false }); + } + return { trajectory }; + }, + visuals: defaultVisuals } function directTrajectory(transformer: StateTransformer<PluginStateObject.Data.String | PluginStateObject.Data.Binary, PluginStateObject.Molecule.Trajectory>): TrajectoryFormatProvider['parse'] { return async (plugin, data, params) => { const state = plugin.state.data; - const trajectory = state.build().to(data) + const trajectory = await state.build().to(data) .apply(transformer, void 0, { tags: params?.trajectoryTags }) - await plugin.updateDataState(trajectory, { revertOnError: true }); - return { trajectory: trajectory.selector }; + .commit({ revertOnError: true }); + return { trajectory }; } } export const PdbProvider: TrajectoryFormatProvider = { label: 'PDB', description: 'PDB', + category: Category, stringExtensions: ['pdb', 'ent'], - binaryExtensions: [], - isApplicable: (info: FileInfo, data: string) => { - return info.ext === 'pdb' || info.ext === 'ent' - }, - parse: directTrajectory(StateTransforms.Model.TrajectoryFromPDB) + parse: directTrajectory(StateTransforms.Model.TrajectoryFromPDB), + visuals: defaultVisuals } export const GroProvider: TrajectoryFormatProvider = { label: 'GRO', description: 'GRO', + category: Category, stringExtensions: ['gro'], binaryExtensions: [], - isApplicable: (info: FileInfo, data: string) => { - return info.ext === 'gro' - }, - parse: directTrajectory(StateTransforms.Model.TrajectoryFromGRO) + parse: directTrajectory(StateTransforms.Model.TrajectoryFromGRO), + visuals: defaultVisuals } export const Provider3dg: TrajectoryFormatProvider = { label: '3DG', description: '3DG', + category: Category, stringExtensions: ['3dg'], - binaryExtensions: [], - isApplicable: (info: FileInfo, data: string) => { - return info.ext === '3dg' - }, - parse: directTrajectory(StateTransforms.Model.TrajectoryFrom3DG) + parse: directTrajectory(StateTransforms.Model.TrajectoryFrom3DG), + visuals: defaultVisuals } export const MolProvider: TrajectoryFormatProvider = { label: 'MOL', description: 'MOL', + category: Category, stringExtensions: ['mol', 'sdf'], - binaryExtensions: [], - isApplicable: (info: FileInfo, data: string) => { - return info.ext === 'mol' || info.ext === 'sdf' - }, - parse: directTrajectory(StateTransforms.Model.TrajectoryFromMOL) + parse: directTrajectory(StateTransforms.Model.TrajectoryFromMOL), + visuals: defaultVisuals } -export const BuildInTrajectoryFormats = [ +export const BuiltInTrajectoryFormats = [ ['mmcif', MmcifProvider] as const, + ['cifCore', CifCoreProvider] as const, ['pdb', PdbProvider] as const, ['gro', GroProvider] as const, ['3dg', Provider3dg] as const, ['mol', MolProvider] as const ] as const -export type BuiltInTrajectoryFormat = (typeof BuildInTrajectoryFormats)[number][0] \ No newline at end of file +export type BuiltInTrajectoryFormat = (typeof BuiltInTrajectoryFormats)[number][0] \ No newline at end of file diff --git a/src/mol-plugin-state/formats/volume.ts b/src/mol-plugin-state/formats/volume.ts new file mode 100644 index 0000000000000000000000000000000000000000..a70f3d285264b1dbba6dd5858e6530bda9a0e5f6 --- /dev/null +++ b/src/mol-plugin-state/formats/volume.ts @@ -0,0 +1,118 @@ +/** + * Copyright (c) 2018-2020 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 { StateTransforms } from '../transforms'; +import { DataFormatProvider, guessCifVariant } from './provider'; +import { PluginContext } from '../../mol-plugin/context'; +import { StateObjectSelector } from '../../mol-state'; +import { PluginStateObject } from '../objects'; +import { VolumeRepresentation3DHelpers } from '../transforms/representation'; +import { ColorNames } from '../../mol-util/color/names'; +import { VolumeIsoValue } from '../../mol-model/volume'; + +const Category = 'Volume'; + +async function defaultVisuals(plugin: PluginContext, data: { volume: StateObjectSelector<PluginStateObject.Volume.Data> }) { + const visual = plugin.build().to(data.volume).apply(StateTransforms.Representation.VolumeRepresentation3D); + return [await visual.commit()]; +} + +export const Ccp4Provider = DataFormatProvider({ + label: 'CCP4/MRC/BRIX', + description: 'CCP4/MRC/BRIX', + category: Category, + binaryExtensions: ['ccp4', 'mrc', 'map'], + parse: async (plugin, data) => { + const format = plugin.build() + .to(data) + .apply(StateTransforms.Data.ParseCcp4, {}, { state: { isGhost: true } }); + + const volume = format.apply(StateTransforms.Volume.VolumeFromCcp4); + + await format.commit({ revertOnError: true }); + + return { format: format.selector, volume: volume.selector }; + }, + visuals: defaultVisuals +}); + +export const Dsn6Provider = DataFormatProvider({ + label: 'DSN6/BRIX', + description: 'DSN6/BRIX', + category: Category, + binaryExtensions: ['dsn6', 'brix'], + parse: async (plugin, data) => { + const format = plugin.build() + .to(data) + .apply(StateTransforms.Data.ParseDsn6, {}, { state: { isGhost: true } }); + + const volume = format.apply(StateTransforms.Volume.VolumeFromDsn6); + + await format.commit({ revertOnError: true }); + + return { format: format.selector, volume: volume.selector }; + }, + visuals: defaultVisuals +}); + +export const DscifProvider = DataFormatProvider({ + label: 'DensityServer CIF', + description: 'DensityServer CIF', + category: Category, + stringExtensions: ['cif'], + binaryExtensions: ['bcif'], + isApplicable: (info, data) => { + return guessCifVariant(info, data) === 'dscif' ? true : false + }, + parse: async (plugin, data) => { + const cifCell = await plugin.build().to(data).apply(StateTransforms.Data.ParseCif).commit() + const b = plugin.build().to(cifCell); + const blocks = cifCell.obj!.data.blocks.slice(1); // zero block contains query meta-data + + if (blocks.length !== 1 && blocks.length !== 2) throw new Error('unknown number of blocks') + + const volumes: StateObjectSelector<PluginStateObject.Volume.Data>[] = []; + for (const block of blocks) { + volumes.push(b.apply(StateTransforms.Volume.VolumeFromDensityServerCif, { blockHeader: block.header }).selector); + } + + await b.commit(); + + return { volumes }; + }, + visuals: async (plugin, data: { volumes: StateObjectSelector<PluginStateObject.Volume.Data>[] }) => { + const { volumes } = data; + const tree = plugin.build(); + const visuals: StateObjectSelector<PluginStateObject.Volume.Representation3D>[] = []; + + if (volumes.length > 0) { + visuals[0] = tree + .to(volumes[0]) + .apply(StateTransforms.Representation.VolumeRepresentation3D, VolumeRepresentation3DHelpers.getDefaultParamsStatic(plugin, 'isosurface', { isoValue: VolumeIsoValue.relative(1.5), alpha: 0.3 }, 'uniform', { value: ColorNames.teal })) + .selector; + } + + if (volumes.length > 1) { + const posParams = VolumeRepresentation3DHelpers.getDefaultParamsStatic(plugin, 'isosurface', { isoValue: VolumeIsoValue.relative(3), alpha: 0.3 }, 'uniform', { value: ColorNames.green }) + const negParams = VolumeRepresentation3DHelpers.getDefaultParamsStatic(plugin, 'isosurface', { isoValue: VolumeIsoValue.relative(-3), alpha: 0.3 }, 'uniform', { value: ColorNames.red }) + visuals[visuals.length] = tree.to(volumes[1]).apply(StateTransforms.Representation.VolumeRepresentation3D, posParams).selector; + visuals[visuals.length] = tree.to(volumes[1]).apply(StateTransforms.Representation.VolumeRepresentation3D, negParams).selector; + } + + await tree.commit(); + + return visuals; + } +}); + +export const BuiltInVolumeFormats = [ + ['ccp4', Ccp4Provider] as const, + ['dns6', Dsn6Provider] as const, + ['dscif', DscifProvider] as const, +] as const + +export type BuildInVolumeFormat = (typeof BuiltInVolumeFormats)[number][0] \ No newline at end of file diff --git a/src/mol-plugin-state/helpers/structure-overpaint.ts b/src/mol-plugin-state/helpers/structure-overpaint.ts index cc977e3972750639d86c9099c2cfdeebaaa7e355..2049ee21756b5c77938ec8354bc772e4e4ee637f 100644 --- a/src/mol-plugin-state/helpers/structure-overpaint.ts +++ b/src/mol-plugin-state/helpers/structure-overpaint.ts @@ -55,7 +55,7 @@ export async function clearStructureOverpaint(plugin: PluginContext, components: }); } -async function eachRepr(plugin: PluginContext, components: StructureComponentRef[], callback: OverpaintEachReprCallback) { +function eachRepr(plugin: PluginContext, components: StructureComponentRef[], callback: OverpaintEachReprCallback) { const state = plugin.state.data; const update = state.build(); for (const c of components) { @@ -65,7 +65,7 @@ async function eachRepr(plugin: PluginContext, components: StructureComponentRef } } - await plugin.updateDataState(update, { doNotUpdateCurrent: true }); + return update.commit({ doNotUpdateCurrent: true }); } /** filter overpaint layers for given structure */ diff --git a/src/mol-plugin-state/manager/structure/component.ts b/src/mol-plugin-state/manager/structure/component.ts index 2adb9f75bac4f1e2c80a0d023b50cddef440c275..87868522ada71f7ed7dd75f41dffc5de39e8d00c 100644 --- a/src/mol-plugin-state/manager/structure/component.ts +++ b/src/mol-plugin-state/manager/structure/component.ts @@ -60,7 +60,7 @@ class StructureComponentManager extends StatefulPluginComponent<StructureCompone } return this.plugin.dataTransaction(async () => { - await this.plugin.updateDataState(update); + await update.commit(); if (interactionChanged) await this.updateInterationProps(); }); } @@ -89,11 +89,11 @@ class StructureComponentManager extends StatefulPluginComponent<StructureCompone const oldParams = s.properties.cell.transform.params?.properties[InteractionsProvider.descriptor.name]; if (PD.areEqual(interactionParams, oldParams, this.state.options.interactions)) continue; - const b = this.dataState.build(); - b.to(s.properties.cell).update(old => { - old.properties[InteractionsProvider.descriptor.name] = this.state.options.interactions; - }); - await this.plugin.updateDataState(b); + await this.dataState.build().to(s.properties.cell) + .update(old => { + old.properties[InteractionsProvider.descriptor.name] = this.state.options.interactions; + }) + .commit(); } else { const pd = this.plugin.customStructureProperties.getParams(s.cell.obj?.data); const params = PD.getDefaultValues(pd); @@ -113,7 +113,7 @@ class StructureComponentManager extends StatefulPluginComponent<StructureCompone }, { canUndo: 'Preset' }); } - private async syncPreset(root: StructureRef, preset?: StructureRepresentationPresetProvider.Result) { + private syncPreset(root: StructureRef, preset?: StructureRepresentationPresetProvider.Result) { if (!preset || !preset.components) return this.clearComponents([root]); const keptRefs = new Set<string>(); @@ -153,7 +153,7 @@ class StructureComponentManager extends StatefulPluginComponent<StructureCompone } } - if (changed) return this.plugin.updateDataState(update); + if (changed) return update.commit(); } clear(structures: ReadonlyArray<StructureRef>) { @@ -247,7 +247,7 @@ class StructureComponentManager extends StatefulPluginComponent<StructureCompone update.to(repr.cell).update(params); } - return this.plugin.updateDataState(update, { canUndo: 'Update Representation' }); + return update.commit({ canUndo: 'Update Representation' }); } /** @@ -288,7 +288,7 @@ class StructureComponentManager extends StatefulPluginComponent<StructureCompone } } - return this.plugin.updateDataState(update, { canUndo: 'Update Theme' }); + return update.commit({ canUndo: 'Update Theme' }); } addRepresentation(components: ReadonlyArray<StructureComponentRef>, type: string) { @@ -313,6 +313,10 @@ class StructureComponentManager extends StatefulPluginComponent<StructureCompone const xs = structures || this.currentStructures; if (xs.length === 0) return; + const { showHydrogens, visualQuality: quality } = this.state.options; + const ignoreHydrogens = !showHydrogens; + const typeParams = { ignoreHydrogens, quality }; + const componentKey = UUID.create22(); for (const s of xs) { const component = await this.plugin.builders.structure.tryCreateComponentFromSelection(s.cell, params.selection, componentKey, { @@ -320,7 +324,8 @@ class StructureComponentManager extends StatefulPluginComponent<StructureCompone }); if (params.representation === 'none' || !component) continue; await this.plugin.builders.structure.representation.addRepresentation(component, { - type: this.plugin.representation.structure.registry.get(params.representation) + type: this.plugin.representation.structure.registry.get(params.representation), + typeParams }); } }, { canUndo: 'Add Selection' }); @@ -379,7 +384,7 @@ class StructureComponentManager extends StatefulPluginComponent<StructureCompone deletes.delete(c.cell.transform.ref); } } - return this.plugin.updateDataState(deletes, { canUndo: 'Clear Selections' }); + return deletes.commit({ canUndo: 'Clear Selections' }); } constructor(public plugin: PluginContext) { diff --git a/src/mol-plugin-state/manager/structure/hierarchy.ts b/src/mol-plugin-state/manager/structure/hierarchy.ts index 4eef242c5336979492db34327c679b2f57fff19f..51d78520263f4bf60ed01dc8f027313d380a4c82 100644 --- a/src/mol-plugin-state/manager/structure/hierarchy.ts +++ b/src/mol-plugin-state/manager/structure/hierarchy.ts @@ -155,7 +155,7 @@ export class StructureHierarchyManager extends PluginComponent { if (refs.length === 0) return; const deletes = this.plugin.state.data.build(); for (const r of refs) deletes.delete(typeof r === 'string' ? r : r.cell.transform.ref); - return this.plugin.updateDataState(deletes, { canUndo: canUndo ? 'Remove' : false }); + return deletes.commit({ canUndo: canUndo ? 'Remove' : false }); } toggleVisibility(refs: ReadonlyArray<HierarchyRef>, action?: 'show' | 'hide') { @@ -196,7 +196,7 @@ export class StructureHierarchyManager extends PluginComponent { for (const m of trajectory.models) { builder.delete(m.cell); } - return this.plugin.updateDataState(builder); + return builder.commit(); } constructor(private plugin: PluginContext) { diff --git a/src/mol-plugin-state/snapshots.ts b/src/mol-plugin-state/snapshots.ts index b34bb3c97b59917cb8dcd363b06b6fe244f92a7f..9ae2b8ad4b4a05107e5fbeb6b95db8525db801a7 100644 --- a/src/mol-plugin-state/snapshots.ts +++ b/src/mol-plugin-state/snapshots.ts @@ -210,8 +210,9 @@ class PluginStateSnapshotManager extends StatefulPluginComponent<{ if (this.state.isPlaying) { this.stop(); this.plugin.state.animation.stop(); + } else { + this.play(); } - else this.play(); } constructor(private plugin: PluginContext) { diff --git a/src/mol-plugin-state/transforms/data.ts b/src/mol-plugin-state/transforms/data.ts index d1b8ecf51bf98bfd652adf05597185587cf96399..b588dd00f0feeda11a281d76d1e5f438fc157be4 100644 --- a/src/mol-plugin-state/transforms/data.ts +++ b/src/mol-plugin-state/transforms/data.ts @@ -1,5 +1,5 @@ /** - * Copyright (c) 2018-2019 mol* contributors, licensed under MIT, See LICENSE file for more info. + * Copyright (c) 2018-2020 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> @@ -11,7 +11,7 @@ import { Task } from '../../mol-task'; import { CIF } from '../../mol-io/reader/cif' import { PluginContext } from '../../mol-plugin/context'; import { ParamDefinition as PD } from '../../mol-util/param-definition'; -import { StateTransformer } from '../../mol-state'; +import { StateTransformer, StateObject } from '../../mol-state'; import { readFromFile, ajaxGetMany } from '../../mol-util/data-source'; import * as CCP4 from '../../mol-io/reader/ccp4/parser' import * as DSN6 from '../../mol-io/reader/dsn6/parser' @@ -143,8 +143,12 @@ const ReadFile = PluginStateTransform.BuiltIn({ isBinary: PD.Optional(PD.Boolean(false, { description: 'If true, open file as as binary (string otherwise)' })) } })({ - apply({ params: p }) { + apply({ params: p }, plugin: PluginContext) { return Task.create('Open File', async ctx => { + if (p.file === null) { + plugin.log.error('No file(s) selected') + return StateObject.Null; + } const data = await readFromFile(p.file, p.isBinary ? 'binary' : 'string').runInContext(ctx); return p.isBinary ? new SO.Data.Binary(data as Uint8Array, { label: p.label ? p.label : p.file.name }) @@ -153,7 +157,7 @@ const ReadFile = PluginStateTransform.BuiltIn({ }, update({ oldParams, newParams, b }) { if (oldParams.label !== newParams.label) { - (b.label as string) = newParams.label || oldParams.file.name; + (b.label as string) = newParams.label || oldParams.file?.name || ''; return StateTransformer.UpdateResult.Updated; } return StateTransformer.UpdateResult.Unchanged; diff --git a/src/mol-plugin-state/transforms/model.ts b/src/mol-plugin-state/transforms/model.ts index defe06b40b618fbbd92629418b82badf1a210918..2e0839e8fe2743e40aef426116d462d504da3ffc 100644 --- a/src/mol-plugin-state/transforms/model.ts +++ b/src/mol-plugin-state/transforms/model.ts @@ -34,6 +34,7 @@ import { StructureSelectionQueries } from '../helpers/structure-selection-query' import { PluginStateObject as SO, PluginStateTransform } from '../objects'; import { parseMol } from '../../mol-io/reader/mol/parser'; import { trajectoryFromMol } from '../../mol-model-formats/structure/mol'; +import { trajectoryFromCifCore } from '../../mol-model-formats/structure/cif-core'; export { CoordinatesFromDcd }; export { TopologyFromPsf }; @@ -43,6 +44,7 @@ export { TrajectoryFromMmCif }; export { TrajectoryFromPDB }; export { TrajectoryFromGRO }; export { TrajectoryFromMOL }; +export { TrajectoryFromCifCore }; export { TrajectoryFrom3DG }; export { ModelFromTrajectory }; export { StructureFromTrajectory }; @@ -233,6 +235,37 @@ const TrajectoryFromMOL = PluginStateTransform.BuiltIn({ } }); +type TrajectoryFromCifCore = typeof TrajectoryFromCifCore +const TrajectoryFromCifCore = PluginStateTransform.BuiltIn({ + name: 'trajectory-from-cif-core', + display: { name: 'Parse CIF Core', description: 'Identify and create all separate models in the specified CIF data block' }, + from: SO.Format.Cif, + to: SO.Molecule.Trajectory, + params(a) { + if (!a) { + return { + blockHeader: PD.Optional(PD.Text(void 0, { description: 'Header of the block to parse. If none is specifed, the 1st data block in the file is used.' })) + }; + } + const { blocks } = a.data; + return { + blockHeader: PD.Optional(PD.Select(blocks[0] && blocks[0].header, blocks.map(b => [b.header, b.header] as [string, string]), { description: 'Header of the block to parse' })) + }; + } +})({ + apply({ a, params }) { + return Task.create('Parse CIF Core', async ctx => { + 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 trajectoryFromCifCore(block).runInContext(ctx); + if (models.length === 0) throw new Error('No models found.'); + const props = { label: `${models[0].entry}`, description: `${models.length} model${models.length === 1 ? '' : 's'}` }; + return new SO.Molecule.Trajectory(models, props); + }); + } +}); + type TrajectoryFrom3DG = typeof TrajectoryFrom3DG const TrajectoryFrom3DG = PluginStateTransform.BuiltIn({ name: 'trajectory-from-3dg', diff --git a/src/mol-plugin-ui/controls/line-graph/line-graph-component.tsx b/src/mol-plugin-ui/controls/line-graph/line-graph-component.tsx index f623e05c9e4ed876884e5bd97a02743e49a4873e..1503f5531a09fe3935bab6858e651e82711a02a2 100644 --- a/src/mol-plugin-ui/controls/line-graph/line-graph-component.tsx +++ b/src/mol-plugin-ui/controls/line-graph/line-graph-component.tsx @@ -15,7 +15,7 @@ interface LineGraphComponentState { } export default class LineGraphComponent extends React.Component<any, LineGraphComponentState> { - private myRef:any; + private myRef: any; private height: number; private width: number; private padding: number; @@ -123,11 +123,11 @@ export default class LineGraphComponent extends React.Component<any, LineGraphCo // TODO: SET canSelectMultiple = fasle } - private handleClick = (id:number) => (event:any) => { + private handleClick = (id: number) => (event: any) => { // TODO: add point to selected array } - private handleMouseDown = (id:number) => (event: any) => { + private handleMouseDown = (id: number) => (event: any) => { if(id === 0 || id === this.state.points.length-1){ return; } @@ -164,17 +164,13 @@ export default class LineGraphComponent extends React.Component<any, LineGraphCo if ((svgP.x < (padding) || svgP.x > (this.width+(padding))) && (svgP.y > (this.height+(padding)) || svgP.y < (padding))) { updatedCopyPoint = Vec2.create(this.updatedX, this.updatedY); - } - else if (svgP.x < padding) { + } else if (svgP.x < padding) { updatedCopyPoint = Vec2.create(padding, svgP.y); - } - else if( svgP.x > (this.width+(padding))) { + } else if( svgP.x > (this.width+(padding))) { updatedCopyPoint = Vec2.create(this.width+padding, svgP.y); - } - else if (svgP.y > (this.height+(padding))) { + } else if (svgP.y > (this.height+(padding))) { updatedCopyPoint = Vec2.create(svgP.x, this.height+padding); - } - else if (svgP.y < (padding)) { + } else if (svgP.y < (padding)) { updatedCopyPoint = Vec2.create(svgP.x, padding); } else { updatedCopyPoint = Vec2.create(svgP.x, svgP.y); @@ -230,8 +226,8 @@ export default class LineGraphComponent extends React.Component<any, LineGraphCo this.change(points); this.gElement.innerHTML = ''; this.ghostPoints = []; - document.removeEventListener("mousemove", this.handleDrag, true); - document.removeEventListener("mouseup", this.handlePointUpdate, true); + document.removeEventListener('mousemove', this.handleDrag, true); + document.removeEventListener('mouseup', this.handlePointUpdate, true); } private handleDoubleClick(event: any) { @@ -267,8 +263,8 @@ export default class LineGraphComponent extends React.Component<any, LineGraphCo this.change(points); } - private deletePoint = (i:number) => (event: any) => { - if(i===0 || i===this.state.points.length-1){ return; } + private deletePoint = (i: number) => (event: any) => { + if(i===0 || i===this.state.points.length-1){ return; } const points = this.state.points.filter((_,j) => j !== i); points.sort((a, b) => { if(a[0] === b[0]){ @@ -334,21 +330,21 @@ export default class LineGraphComponent extends React.Component<any, LineGraphCo const points: any[] = []; let point: Vec2; for (let i = 0; i < this.state.points.length; i++){ - if(i != 0 && i != this.state.points.length-1){ + if(i !== 0 && i !== this.state.points.length-1){ point = this.normalizePoint(this.state.points[i]); points.push(<PointComponent - key={i} - id={i} - x={point[0]} - y={point[1]} - nX={this.state.points[i][0]} - nY={this.state.points[i][1]} - selected={false} - delete={this.deletePoint} - onmouseover={this.props.onHover} - onmousedown={this.handleMouseDown(i)} - onclick={this.handleClick(i)} - />); + key={i} + id={i} + x={point[0]} + y={point[1]} + nX={this.state.points[i][0]} + nY={this.state.points[i][1]} + selected={false} + delete={this.deletePoint} + onmouseover={this.props.onHover} + onmousedown={this.handleMouseDown(i)} + onclick={this.handleClick(i)} + />); } } return points; @@ -357,8 +353,8 @@ export default class LineGraphComponent extends React.Component<any, LineGraphCo private renderLines() { const points: Vec2[] = []; let lines = []; - let min:number; - let maxX:number; + let min: number; + let maxX: number; let maxY: number; let normalizedX: number; let normalizedY: number; diff --git a/src/mol-plugin-ui/controls/slider.tsx b/src/mol-plugin-ui/controls/slider.tsx index ba53065ece5c5435ce6577fe41c89495b137e5e0..12a66eeef40d88d2f402f7d7183fcffe7436651c 100644 --- a/src/mol-plugin-ui/controls/slider.tsx +++ b/src/mol-plugin-ui/controls/slider.tsx @@ -733,7 +733,7 @@ export class SliderBase extends React.Component<SliderBaseProps, SliderBaseState ref: this.handleElements[i] })); if (!range) { handles.shift(); } - + const sliderClassName = classNames({ [prefixCls!]: true, [`${prefixCls}-with-marks`]: Object.keys(marks).length, @@ -743,8 +743,8 @@ export class SliderBase extends React.Component<SliderBaseProps, SliderBaseState }); return ( - <div ref={this.sliderElement} className={sliderClassName} - onTouchStart={disabled ? noop : this.onTouchStart as any} + <div ref={this.sliderElement} className={sliderClassName} + onTouchStart={disabled ? noop : this.onTouchStart as any} onMouseDown={disabled ? noop : this.onMouseDown as any} > <div className={`${prefixCls}-rail`} /> diff --git a/src/mol-plugin/behavior/dynamic/custom-props/rcsb/assembly-symmetry.ts b/src/mol-plugin/behavior/dynamic/custom-props/rcsb/assembly-symmetry.ts index 3d2cad2628ee2dce12dc17ea9a840b6c5efb7c3b..34aafbe151d4c7a34d7883f4faf11226a3a787dd 100644 --- a/src/mol-plugin/behavior/dynamic/custom-props/rcsb/assembly-symmetry.ts +++ b/src/mol-plugin/behavior/dynamic/custom-props/rcsb/assembly-symmetry.ts @@ -188,10 +188,9 @@ export const AssemblySymmetryPreset = StructureRepresentationPresetProvider({ } }); -export async function tryCreateAssemblySymmetry(plugin: PluginContext, structure: StateObjectRef<PluginStateObject.Molecule.Structure>, params?: StateTransformer.Params<AssemblySymmetry3D>, initialState?: Partial<StateTransform.State>) { +export function tryCreateAssemblySymmetry(plugin: PluginContext, structure: StateObjectRef<PluginStateObject.Molecule.Structure>, params?: StateTransformer.Params<AssemblySymmetry3D>, initialState?: Partial<StateTransform.State>) { const state = plugin.state.data; const assemblySymmetry = state.build().to(structure) .applyOrUpdateTagged(AssemblySymmetry.Tag.Representation, AssemblySymmetry3D, params, { state: initialState }); - await plugin.updateDataState(assemblySymmetry, { revertOnError: true }); - return assemblySymmetry.selector + return assemblySymmetry.commit({ revertOnError: true }); } \ No newline at end of file diff --git a/src/mol-plugin/behavior/dynamic/custom-props/rcsb/ui/assembly-symmetry.tsx b/src/mol-plugin/behavior/dynamic/custom-props/rcsb/ui/assembly-symmetry.tsx index df69ba8f7e6fe0d301267ae809cc644716a92775..8931de1f6c53b9bc7a4d652d1eda63779d32689d 100644 --- a/src/mol-plugin/behavior/dynamic/custom-props/rcsb/ui/assembly-symmetry.tsx +++ b/src/mol-plugin/behavior/dynamic/custom-props/rcsb/ui/assembly-symmetry.tsx @@ -96,7 +96,7 @@ export class AssemblySymmetryControls extends CollapsableControls<{}, AssemblySy b.to(s.properties.cell).update(old => { old.properties[AssemblySymmetryProvider.descriptor.name] = values; }); - await this.plugin.updateDataState(b); + await b.commit(); } else { const pd = this.plugin.customStructureProperties.getParams(s.cell.obj?.data); const params = PD.getDefaultValues(pd); diff --git a/src/mol-plugin/behavior/dynamic/custom-props/rcsb/validation-report.ts b/src/mol-plugin/behavior/dynamic/custom-props/rcsb/validation-report.ts index f5d23feae4bb21ab7aa3fbc43a0d6f4162fc352f..da8ff9dfb3c4f73134fe62bded4ba98fb2f03f81 100644 --- a/src/mol-plugin/behavior/dynamic/custom-props/rcsb/validation-report.ts +++ b/src/mol-plugin/behavior/dynamic/custom-props/rcsb/validation-report.ts @@ -329,7 +329,7 @@ export const ValidationReportGeometryQualityPreset = StructureRepresentationPres clashesSnfg3d = builder.buildRepresentation<any>(update, clashes, { type: ClashesRepresentationProvider.name, typeParams, color }, { tag: 'clashes-snfg-3d' }); } - await plugin.updateDataState(update, { revertOnError: false }); + await update.commit({ revertOnError: true }); return { components: { ...components, clashes }, representations: { ...representations, clashesBallAndStick, clashesSnfg3d } }; } }); diff --git a/src/mol-plugin/behavior/dynamic/representation.ts b/src/mol-plugin/behavior/dynamic/representation.ts index 41b0ad9abefdb2b2376131fd5a21ed910753f281..88494345213c5352da8ea9191042b01500ff05e9 100644 --- a/src/mol-plugin/behavior/dynamic/representation.ts +++ b/src/mol-plugin/behavior/dynamic/representation.ts @@ -184,7 +184,7 @@ export const DefaultLociLabelProvider = PluginBehavior.create({ label.push(name) } label.push(lociLabel(loci)) - return label.join('</br>') + return label.filter(l => !!l).join('</br>') }, group: (label: LociLabel) => label.toString().replace(/Model [0-9]+/g, 'Models'), priority: 100 diff --git a/src/mol-plugin/behavior/dynamic/volume-streaming/transformers.ts b/src/mol-plugin/behavior/dynamic/volume-streaming/transformers.ts index 9b9bd36e33be45b4256b415f0d68d8b10a83e137..c4a44118c52c170a42ccfb5e7f4b7b2fa9c6bc2c 100644 --- a/src/mol-plugin/behavior/dynamic/volume-streaming/transformers.ts +++ b/src/mol-plugin/behavior/dynamic/volume-streaming/transformers.ts @@ -99,7 +99,7 @@ export const InitVolumeStreaming = StateAction.build({ entries }); - await plugin.updateDataState(infoTree); + await infoTree.commit(); const info = infoTree.selector; if (!info.isOk) return; diff --git a/src/mol-plugin/context.ts b/src/mol-plugin/context.ts index 1fd0735ec242773a71ea7581e7a7e29e3e4c6375..0009f66874e40495ae93ab646f07c7b593c8fe6b 100644 --- a/src/mol-plugin/context.ts +++ b/src/mol-plugin/context.ts @@ -11,10 +11,9 @@ import { merge } from 'rxjs'; import { Canvas3D, DefaultCanvas3DParams } from '../mol-canvas3d/canvas3d'; import { CustomProperty } from '../mol-model-props/common/custom-property'; import { Model, Structure } from '../mol-model/structure'; -import { DataFormatRegistry } from '../mol-plugin-state/actions/data-format'; import { DataBuilder } from '../mol-plugin-state/builder/data'; import { StructureBuilder } from '../mol-plugin-state/builder/structure'; -import { TrajectoryFormatRegistry } from '../mol-plugin-state/formats/trajectory'; +import { DataFormatRegistry } from '../mol-plugin-state/formats/registry'; import { StructureSelectionQueryRegistry } from '../mol-plugin-state/helpers/structure-selection-query'; import { CameraManager } from '../mol-plugin-state/manager/camera'; import { InteractivityManager } from '../mol-plugin-state/manager/interactivity'; @@ -30,7 +29,7 @@ import { StateTransformParameters } from '../mol-plugin-ui/state/common'; import { Representation } from '../mol-repr/representation'; import { StructureRepresentationRegistry } from '../mol-repr/structure/registry'; import { VolumeRepresentationRegistry } from '../mol-repr/volume/registry'; -import { State, StateBuilder, StateTree, StateTransform } from '../mol-state'; +import { StateTransform } from '../mol-state'; import { Progress, Task } from '../mol-task'; import { ColorTheme } from '../mol-theme/color'; import { SizeTheme } from '../mol-theme/size'; @@ -45,7 +44,7 @@ import { BuiltInPluginBehaviors } from './behavior'; import { PluginBehavior } from './behavior/behavior'; import { PluginCommandManager } from './command'; import { PluginCommands } from './commands'; -import { PluginConfigManager, PluginConfig } from './config'; +import { PluginConfig, PluginConfigManager } from './config'; import { LeftPanelTabName, PluginLayout } from './layout'; import { PluginSpec } from './spec'; import { PluginState } from './state'; @@ -56,6 +55,8 @@ import { ViewportScreenshotHelper } from './util/viewport-screenshot'; import { PLUGIN_VERSION, PLUGIN_VERSION_DATE } from './version'; export class PluginContext { + runTask = <T>(task: Task<T>) => this.tasks.run(task); + private disposed = false; private ev = RxEventHelper.create(); private tasks = new TaskManager(); @@ -127,17 +128,17 @@ export class PluginContext { } } as const; - readonly dataFormat = { - trajectory: TrajectoryFormatRegistry(), - // TODO: separate registries for format catgories - registry: new DataFormatRegistry() - } as const + readonly dataFormats = new DataFormatRegistry(); readonly builders = { data: new DataBuilder(this), structure: void 0 as any as StructureBuilder }; + build() { + return this.state.data.build(); + } + readonly managers = { structure: { hierarchy: new StructureHierarchyManager(this), @@ -218,18 +219,10 @@ export class PluginContext { this.behaviors.interaction.selectionMode.next(mode); } - runTask<T>(task: Task<T>) { - return this.tasks.run(task); - } - dataTransaction(f: () => Promise<void> | void, options?: { canUndo?: string | boolean }) { return this.runTask(this.state.data.transaction(f, options)); } - updateDataState(tree: StateTree | StateBuilder, options?: Partial<State.UpdateOptions>) { - return this.runTask(this.state.data.updateTree(tree, options)); - } - requestTaskAbort(progress: Progress, reason?: string) { this.tasks.requestAbort(progress, reason); } diff --git a/src/mol-plugin/index.ts b/src/mol-plugin/index.ts index d756ca8844610b7bf3ffa067140a0aa6d1a3c681..9b0e4d9d4ac447b920f6a30cdad7da9fa56db2e2 100644 --- a/src/mol-plugin/index.ts +++ b/src/mol-plugin/index.ts @@ -39,6 +39,7 @@ export const DefaultPluginSpec: PluginSpec = { PluginSpec.Action(StateTransforms.Data.ParseDsn6), PluginSpec.Action(StateTransforms.Model.TrajectoryFromMmCif), + PluginSpec.Action(StateTransforms.Model.TrajectoryFromCifCore), PluginSpec.Action(StateTransforms.Model.TrajectoryFromPDB), PluginSpec.Action(StateTransforms.Model.TransformStructureConformation), PluginSpec.Action(StateTransforms.Model.StructureCoordinateSystem), diff --git a/src/mol-plugin/state.ts b/src/mol-plugin/state.ts index f88a067f668a31270ff944cabb6bafb3b79e8a5f..d9bc28c7511dd34d4966640412de0d102bf228c1 100644 --- a/src/mol-plugin/state.ts +++ b/src/mol-plugin/state.ts @@ -108,8 +108,8 @@ class PluginState { constructor(private plugin: import('./context').PluginContext) { this.snapshots = new PluginStateSnapshotManager(plugin); - this.data = State.create(new SO.Root({ }), { globalContext: plugin }); - this.behaviors = State.create(new PluginBehavior.Root({ }), { globalContext: plugin, rootState: { isLocked: true } }); + this.data = State.create(new SO.Root({ }), { runTask: plugin.runTask, globalContext: plugin }); + this.behaviors = State.create(new PluginBehavior.Root({ }), { runTask: plugin.runTask, globalContext: plugin, rootState: { isLocked: true } }); this.animation = new PluginAnimationManager(plugin); } diff --git a/src/mol-repr/structure/representation/ellipsoid.ts b/src/mol-repr/structure/representation/ellipsoid.ts index 2ef2eca8a4cc2c9c4de4f98f90a0ce20d36401b0..90af41838d2ef6d1be2f46e6bb7a33074bdc16d1 100644 --- a/src/mol-repr/structure/representation/ellipsoid.ts +++ b/src/mol-repr/structure/representation/ellipsoid.ts @@ -28,7 +28,7 @@ export const EllipsoidParams = { unitKinds: getUnitKindsParam(['atomic']), sizeFactor: PD.Numeric(1, { min: 0.01, max: 10, step: 0.01 }), sizeAspectRatio: PD.Numeric(0.1, { min: 0.01, max: 3, step: 0.01 }), - bondCap: PD.Boolean(true), + linkCap: PD.Boolean(true), visuals: PD.MultiSelect(['ellipsoid-mesh', 'intra-bond', 'inter-bond'], PD.objectToOptions(EllipsoidVisuals)), } export type EllipsoidParams = typeof EllipsoidParams @@ -50,5 +50,5 @@ export const EllipsoidRepresentationProvider = StructureRepresentationProvider({ defaultValues: PD.getDefaultValues(EllipsoidParams), defaultColorTheme: { name: 'element-symbol' }, defaultSizeTheme: { name: 'uniform' }, - isApplicable: (structure: Structure) => structure.elementCount > 0 && structure.models.some(m => m.customProperties.has(AtomSiteAnisotrop.Descriptor)) + isApplicable: (structure: Structure) => structure.elementCount > 0 && structure.models.some(m => AtomSiteAnisotrop.Provider.isApplicable(m)) }) \ No newline at end of file diff --git a/src/mol-script/runtime/query/table.ts b/src/mol-script/runtime/query/table.ts index dbd42f193e2027b5ebe389c7ca7272f0d3e6a90e..4cac2762a67249d07aabb2e29d9e8c0427605205 100644 --- a/src/mol-script/runtime/query/table.ts +++ b/src/mol-script/runtime/query/table.ts @@ -228,36 +228,46 @@ const symbols = [ })(ctx)), // ============= GENERATORS ================ - D(MolScript.structureQuery.generator.atomGroups, function structureQuery_generator_atomGroups(ctx, xs) { return Queries.generators.atoms({ - entityTest: xs['entity-test'], - chainTest: xs['chain-test'], - residueTest: xs['residue-test'], - atomTest: xs['atom-test'], - groupBy: xs['group-by'] - })(ctx) }), + D(MolScript.structureQuery.generator.atomGroups, function structureQuery_generator_atomGroups(ctx, xs) { + return Queries.generators.atoms({ + entityTest: xs['entity-test'], + chainTest: xs['chain-test'], + residueTest: xs['residue-test'], + atomTest: xs['atom-test'], + groupBy: xs['group-by'] + })(ctx) + }), D(MolScript.structureQuery.generator.all, function structureQuery_generator_all(ctx) { return Queries.generators.all(ctx) }), D(MolScript.structureQuery.generator.empty, function structureQuery_generator_empty(ctx) { return Queries.generators.none(ctx) }), - D(MolScript.structureQuery.generator.bondedAtomicPairs, function structureQuery_generator_bondedAtomicPairs(ctx, xs) { return Queries.generators.bondedAtomicPairs(xs && xs[0])(ctx) }), - D(MolScript.structureQuery.generator.rings, function structureQuery_generator_rings(ctx, xs) { return Queries.generators.rings(xs?.['fingerprint']?.(ctx) as any, xs?.['only-aromatic']?.(ctx))(ctx) }), + D(MolScript.structureQuery.generator.bondedAtomicPairs, function structureQuery_generator_bondedAtomicPairs(ctx, xs) { + return Queries.generators.bondedAtomicPairs(xs && xs[0])(ctx) + }), + D(MolScript.structureQuery.generator.rings, function structureQuery_generator_rings(ctx, xs) { + return Queries.generators.rings(xs?.['fingerprint']?.(ctx) as any, xs?.['only-aromatic']?.(ctx))(ctx) + }), // ============= MODIFIERS ================ - D(MolScript.structureQuery.modifier.includeSurroundings, function structureQuery_modifier_includeSurroundings(ctx, xs) { return Queries.modifiers.includeSurroundings(xs[0] as any, { - radius: xs['radius'](ctx), - wholeResidues: !!(xs['as-whole-residues'] && xs['as-whole-residues'](ctx)), - elementRadius: xs['atom-radius'] - })(ctx) }), + D(MolScript.structureQuery.modifier.includeSurroundings, function structureQuery_modifier_includeSurroundings(ctx, xs) { + return Queries.modifiers.includeSurroundings(xs[0] as any, { + radius: xs['radius'](ctx), + wholeResidues: !!(xs['as-whole-residues'] && xs['as-whole-residues'](ctx)), + elementRadius: xs['atom-radius'] + })(ctx) + }), D(MolScript.structureQuery.modifier.wholeResidues, function structureQuery_modifier_wholeResidues(ctx, xs) { return Queries.modifiers.wholeResidues(xs[0] as any)(ctx) }), D(MolScript.structureQuery.modifier.union, function structureQuery_modifier_union(ctx, xs) { return Queries.modifiers.union(xs[0] as any)(ctx) }), D(MolScript.structureQuery.modifier.expandProperty, function structureQuery_modifier_expandProperty(ctx, xs) { return Queries.modifiers.expandProperty(xs[0] as any, xs['property'])(ctx) }), D(MolScript.structureQuery.modifier.exceptBy, function structureQuery_modifier_exceptBy(ctx, xs) { return Queries.modifiers.exceptBy(xs[0] as any, xs['by'] as any)(ctx) }), - D(MolScript.structureQuery.modifier.includeConnected, function structureQuery_modifier_includeConnected(ctx, xs) { return Queries.modifiers.includeConnected({ - query: xs[0] as any, - bondTest: xs['bond-test'], - wholeResidues: !!(xs['as-whole-residues'] && xs['as-whole-residues'](ctx)), - layerCount: (xs['layer-count'] && xs['layer-count'](ctx)) || 1 - })(ctx) }), + D(MolScript.structureQuery.modifier.includeConnected, function structureQuery_modifier_includeConnected(ctx, xs) { + return Queries.modifiers.includeConnected({ + query: xs[0] as any, + bondTest: xs['bond-test'], + wholeResidues: !!(xs['as-whole-residues'] && xs['as-whole-residues'](ctx)), + layerCount: (xs['layer-count'] && xs['layer-count'](ctx)) || 1 + })(ctx) + }), // ============= COMBINATORS ================ diff --git a/src/mol-state/action.ts b/src/mol-state/action.ts index 07547cf5a3d7b91c34afd74b31a26f36c2b0bc44..bd1d25767421c5a47ec8d85bd59f9c2251b23d80 100644 --- a/src/mol-state/action.ts +++ b/src/mol-state/action.ts @@ -78,7 +78,7 @@ namespace StateAction { : void 0, run({ cell, state, params }) { const tree = state.build().to(cell.transform.ref).apply(transformer, params); - return state.updateTree(tree) as Task<void>; + return state.updateTree(tree) as unknown as Task<void>; } }) } diff --git a/src/mol-state/state.ts b/src/mol-state/state.ts index 4383be5045aed5093b9775dc80391210ccf26c56..b72476c25ec0ffdb1a37039f3bd409c790ee402d 100644 --- a/src/mol-state/state.ts +++ b/src/mol-state/state.ts @@ -4,7 +4,7 @@ * @author David Sehnal <david.sehnal@gmail.com> */ -import { StateObject, StateObjectCell } from './object'; +import { StateObject, StateObjectCell, StateObjectSelector } from './object'; import { StateTree } from './tree'; import { StateTransform } from './transform'; import { StateTransformer } from './transformer'; @@ -58,6 +58,8 @@ class State { readonly actions = new StateActionManager(); + readonly runTask: <T>(task: Task<T>) => Promise<T>; + get tree(): StateTree { return this._tree; } get transforms() { return (this._tree as StateTree).transforms; } get current() { return this.behaviors.currentObject.value.ref; } @@ -232,7 +234,7 @@ class State { * @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: StateBuilder.To<T, any>, options?: Partial<State.UpdateOptions>): Task<T> + updateTree<T extends StateObject>(tree: StateBuilder.To<T, any>, options?: Partial<State.UpdateOptions>): Task<StateObjectSelector<T>> updateTree(tree: StateTree | StateBuilder, options?: Partial<State.UpdateOptions>): Task<void> updateTree(tree: StateTree | StateBuilder, options?: Partial<State.UpdateOptions>): Task<any> { const params: UpdateParams = { tree, options }; @@ -247,6 +249,11 @@ class State { if (!this.inTransaction) this.behaviors.isUpdating.next(true); try { + if (StateBuilder.is(tree)) { + if (tree.editInfo.applied) throw new Error('This builder has already been applied. Create a new builder for further state updates'); + tree.editInfo.applied = true; + } + this.reverted = false; const ret = options && (options.revertIfAborted || options.revertOnError) ? await this._revertibleTreeUpdate(taskCtx, params, options) @@ -255,7 +262,9 @@ class State { if (ret.ctx.hadError) this.inTransactionError = true; - return ret.cell; + if (!ret.cell) return; + + return new StateObjectSelector(ret.cell.transform.ref, this); } finally { this._inUpdate = false; this.updateQueue.handled(params); @@ -294,7 +303,7 @@ class State { updated = await update(ctx); if (StateBuilder.isTo(params.tree)) { const cell = this.select(params.tree.ref)[0]; - return { ctx, cell: cell && cell.obj }; + return { ctx, cell }; } return { ctx }; } finally { @@ -335,10 +344,11 @@ class State { return ctx; } - constructor(rootObject: StateObject, params?: { globalContext?: unknown, rootState?: StateTransform.State, historyCapacity?: number }) { + constructor(rootObject: StateObject, params: State.Params) { this._tree = StateTree.createEmpty(StateTransform.createRoot(params && params.rootState)).asTransient(); const tree = this._tree; const root = tree.root; + this.runTask = params.runTask; if (params?.historyCapacity !== void 0) this.historyCapacity = params.historyCapacity; @@ -363,6 +373,17 @@ class State { } namespace State { + export interface Params { + runTask<T>(task: Task<T>): Promise<T>, + globalContext?: unknown, + rootState?: StateTransform.State, + historyCapacity?: number + } + + export function create(rootObject: StateObject, params: Params) { + return new State(rootObject, params); + } + export type Cells = ReadonlyMap<StateTransform.Ref, StateObjectCell> export type Tree = StateTree @@ -390,10 +411,6 @@ namespace State { revertOnError: boolean, canUndo: boolean | string } - - export function create(rootObject: StateObject, params?: { globalContext?: unknown, rootState?: StateTransform.State }) { - return new State(rootObject, params); - } } const StateUpdateDefaultOptions: State.UpdateOptions = { @@ -885,12 +902,7 @@ function runTask<T>(t: T | Task<T>, ctx: RuntimeContext) { return t as T; } -function createObject(ctx: UpdateContext, cell: StateObjectCell, transformer: StateTransformer, a: StateObject, params: any) { - if (!cell.cache) cell.cache = Object.create(null); - return runTask(transformer.definition.apply({ a, params, cache: cell.cache, spine: ctx.spine, dependencies: resolveDependencies(ctx, cell) }, ctx.parent.globalContext), ctx.taskCtx); -} - -function resolveDependencies(ctx: UpdateContext, cell: StateObjectCell) { +function resolveDependencies(cell: StateObjectCell) { if (cell.dependencies.dependsOn.length === 0) return void 0; const deps = Object.create(null); @@ -905,10 +917,15 @@ function resolveDependencies(ctx: UpdateContext, cell: StateObjectCell) { return deps; } +function createObject(ctx: UpdateContext, cell: StateObjectCell, transformer: StateTransformer, a: StateObject, params: any) { + if (!cell.cache) cell.cache = Object.create(null); + return runTask(transformer.definition.apply({ a, params, cache: cell.cache, spine: ctx.spine, dependencies: resolveDependencies(cell) }, ctx.parent.globalContext), ctx.taskCtx); +} + async function updateObject(ctx: UpdateContext, cell: StateObjectCell, transformer: StateTransformer, a: StateObject, b: StateObject, oldParams: any, newParams: any) { if (!transformer.definition.update) { return StateTransformer.UpdateResult.Recreate; } if (!cell.cache) cell.cache = Object.create(null); - return runTask(transformer.definition.update({ a, oldParams, b, newParams, cache: cell.cache, spine: ctx.spine, dependencies: resolveDependencies(ctx, cell) }, ctx.parent.globalContext), ctx.taskCtx); + return runTask(transformer.definition.update({ a, oldParams, b, newParams, cache: cell.cache, spine: ctx.spine, dependencies: resolveDependencies(cell) }, ctx.parent.globalContext), ctx.taskCtx); } \ No newline at end of file diff --git a/src/mol-state/state/builder.ts b/src/mol-state/state/builder.ts index 1d168ae15598642b1dee94ceea85c53805cb2f05..e249ee738ec42ddcebf871da6dccf74aca584f81 100644 --- a/src/mol-state/state/builder.ts +++ b/src/mol-state/state/builder.ts @@ -21,6 +21,7 @@ interface StateBuilder { namespace StateBuilder { export interface EditInfo { + applied: boolean, sourceTree: StateTree, count: number, lastUpdate?: StateTransform.Ref @@ -102,7 +103,13 @@ namespace StateBuilder { return this; } getTree(): StateTree { return buildTree(this.state); } - constructor(tree: StateTree, state?: State) { this.state = { state, tree: tree.asTransient(), actions: [], editInfo: { sourceTree: tree, count: 0, lastUpdate: void 0 } } } + + commit(options?: Partial<State.UpdateOptions>) { + if (!this.state.state) throw new Error('Cannot commit template tree'); + return this.state.state.runTask(this.state.state.updateTree(this, options)); + } + + constructor(tree: StateTree, state?: State) { this.state = { state, tree: tree.asTransient(), actions: [], editInfo: { applied: false, sourceTree: tree, count: 0, lastUpdate: void 0 } } } } export class To<A extends StateObject, T extends StateTransformer = StateTransformer> implements StateBuilder { @@ -252,6 +259,12 @@ namespace StateBuilder { getTree(): StateTree { return buildTree(this.state); } + /** Returns selector to this node. */ + commit(options?: Partial<State.UpdateOptions>): Promise<StateObjectSelector<A>> { + if (!this.state.state) throw new Error('Cannot commit template tree'); + return this.state.state.runTask(this.state.state.updateTree(this, options)); + } + constructor(private state: BuildState, ref: StateTransform.Ref, private root: Root) { this.ref = ref; if (!this.state.tree.transforms.has(ref)) { diff --git a/src/mol-theme/label.ts b/src/mol-theme/label.ts index e6f8276b00a6d64962cf97fadbea43d2ca686ff3..7152d13ea8dddd87bae874482aea6e02f1ebd9b7 100644 --- a/src/mol-theme/label.ts +++ b/src/mol-theme/label.ts @@ -28,7 +28,7 @@ export type LabelOptions = typeof DefaultLabelOptions export function lociLabel(loci: Loci, options: Partial<LabelOptions> = {}): string { switch (loci.kind) { case 'structure-loci': - return loci.structure.models.map(m => m.entry).join(', ') + return loci.structure.models.map(m => m.entry).filter(l => !!l).join(', ') case 'element-loci': return structureElementStatsLabel(StructureElement.Stats.ofLoci(loci), options) case 'bond-loci': @@ -148,7 +148,7 @@ export function _bundleLabel(bundle: Loci.Bundle<any>, options: LabelOptions) { const labels = locations.map(l => _elementLabel(l, granularity, hidePrefix, reverse || condensed)) if (condensed) { - return labels.map(l => l[0].replace(/\[.*\]/g, '').trim()).join(' \u2014 ') + return labels.map(l => l[0].replace(/\[.*\]/g, '').trim()).filter(l => !!l).join(' \u2014 ') } let offset = 0 @@ -167,22 +167,22 @@ export function _bundleLabel(bundle: Loci.Bundle<any>, options: LabelOptions) { if (offset > 0) { const offsetLabels = [labels[0].join(' | ')] for (let j = 1, jl = labels.length; j < jl; ++j) { - offsetLabels.push(labels[j].slice(offset).join(' | ')) + offsetLabels.push(labels[j].slice(offset).filter(l => !!l).join(' | ')) } return offsetLabels.join(' \u2014 ') } else { - return labels.map(l => l.join(' | ')).join('</br>') + return labels.map(l => l.filter(l => !!l).join(' | ')).filter(l => !!l).join('</br>') } } else { const labels = bundle.loci.map(l => lociLabel(l, options)) - return labels.join(condensed ? ' \u2014 ' : '</br>') + return labels.filter(l => !!l).join(condensed ? ' \u2014 ' : '</br>') } } export function elementLabel(location: StructureElement.Location, options: Partial<LabelOptions> = {}): string { const o = { ...DefaultLabelOptions, ...options } const _label = _elementLabel(location, o.granularity, o.hidePrefix, o.reverse || o.condensed) - const label = o.condensed ? _label[0].replace(/\[.*\]/g, '').trim() : _label.join(' | ') + const label = o.condensed ? _label[0].replace(/\[.*\]/g, '').trim() : _label.filter(l => !!l).join(' | ') return o.htmlStyling ? label : stripTags(label) } diff --git a/src/mol-util/make-dir.ts b/src/mol-util/make-dir.ts index 051203d65191bfae0cd8c89b45409991b24119b7..a8f4ed6fb2c50a94e331f88583f71181a60f67bb 100644 --- a/src/mol-util/make-dir.ts +++ b/src/mol-util/make-dir.ts @@ -12,8 +12,9 @@ export function makeDir(path: string, root?: string): boolean { root = (root || '') + dir + '/'; - try { fs.mkdirSync(root); } - catch (e) { + try { + fs.mkdirSync(root); + } catch (e) { if (!fs.statSync(root).isDirectory()) throw new Error(e); } diff --git a/src/mol-util/param-definition.ts b/src/mol-util/param-definition.ts index b008677758a4837244ee0749b0b498cd2566266b..c98526d2f0535ea664f952aef4c60331c16ed392 100644 --- a/src/mol-util/param-definition.ts +++ b/src/mol-util/param-definition.ts @@ -147,22 +147,22 @@ export namespace ParamDefinition { return setInfo<Mat4>({ type: 'mat4', defaultValue }, info) } - export interface FileParam extends Base<File> { + export interface FileParam extends Base<File | null> { type: 'file' accept?: string } export function File(info?: Info & { accept?: string, multiple?: boolean }): FileParam { - const ret = setInfo<FileParam>({ type: 'file', defaultValue: void 0 as any }, info); + const ret = setInfo<FileParam>({ type: 'file', defaultValue: null }, info); if (info?.accept) ret.accept = info.accept; return ret; } - export interface FileListParam extends Base<FileList> { + export interface FileListParam extends Base<FileList | null> { type: 'file-list' accept?: string } export function FileList(info?: Info & { accept?: string, multiple?: boolean }): FileListParam { - const ret = setInfo<FileListParam>({ type: 'file-list', defaultValue: void 0 as any }, info); + const ret = setInfo<FileListParam>({ type: 'file-list', defaultValue: null }, info); if (info?.accept) ret.accept = info.accept; return ret; } diff --git a/src/mol-util/param-mapping.ts b/src/mol-util/param-mapping.ts index 14f985e7931f1624ebd0921359b136883337bcbf..861bb64c30795f7cd611a29d80c6da912ff4a43f 100644 --- a/src/mol-util/param-mapping.ts +++ b/src/mol-util/param-mapping.ts @@ -23,8 +23,7 @@ export function ParamMapping<S, T, Ctx>(def: { values(t: T, ctx: Ctx): S, update(s: S, t: Mutable<T>, ctx: Ctx): void, apply?(t: T, ctx: Ctx): void | Promise<void> - }) => ParamMapping<S, T, Ctx> -{ + }) => ParamMapping<S, T, Ctx> { return ({ values, update, apply }) => ({ params: typeof def.params === 'function' ? def.params as any : ctx => def.params, getTarget: def.target, diff --git a/src/mol-util/zip/bin.ts b/src/mol-util/zip/bin.ts index c3a41a16939fa40855fbec64b75657378a99c61f..6edbdc1c67d9831ce0a8d2db2b491c4fa1654fb6 100644 --- a/src/mol-util/zip/bin.ts +++ b/src/mol-util/zip/bin.ts @@ -91,11 +91,17 @@ export function sizeUTF8(str: string) { let i = 0; for(let ci = 0; ci < strl; ci++) { const code = str.charCodeAt(ci); - if ((code&(0xffffffff-(1<< 7)+1)) === 0) { i++ ; } - else if((code&(0xffffffff-(1<<11)+1)) === 0) { i+=2; } - else if((code&(0xffffffff-(1<<16)+1)) === 0) { i+=3; } - else if((code&(0xffffffff-(1<<21)+1)) === 0) { i+=4; } - else throw 'e'; + if ((code&(0xffffffff-(1<< 7)+1)) === 0) { + i++ ; + } else if((code&(0xffffffff-(1<<11)+1)) === 0) { + i+=2; + } else if((code&(0xffffffff-(1<<16)+1)) === 0) { + i+=3; + } else if((code&(0xffffffff-(1<<21)+1)) === 0) { + i+=4; + } else { + throw 'e'; + } } return i; } diff --git a/src/mol-util/zip/deflate.ts b/src/mol-util/zip/deflate.ts index 464805474b8c7a6d1539fdb44e2e857576e83b43..25cec469ff31594089636c6020d0137e73425538 100644 --- a/src/mol-util/zip/deflate.ts +++ b/src/mol-util/zip/deflate.ts @@ -96,8 +96,9 @@ export function _deflateRaw(data: Uint8Array, out: Uint8Array, opos: number, lvl const dgi = _goodIndex(dst, U.df0); U.dhst[ dgi]++; ebits += U.exb[lgi] + U.dxb[dgi]; lits[li] = (len<<23)|(i-cvrd); lits[li+1] = (dst<<16)|(lgi<<8)|dgi; li+=2; cvrd = i + len; + } else { + U.lhst[data[i]]++; } - else { U.lhst[data[i]]++; } lc++; } } @@ -302,8 +303,9 @@ function _lenCodes(tree: number[], set: number[]) { const zc = Math.min((lz+1-i)>>>1, 6); set.push(16, zc-3); i += zc*2-2; + } else { + set.push(l, 0); } - else set.push(l, 0); } return len>>>1; } diff --git a/src/mol-util/zip/zip.ts b/src/mol-util/zip/zip.ts index 8d43d47719feab6fe21ab4bd50a7440f279225c0..081451dd0216d0a0402cb850997a3af241d20a27 100644 --- a/src/mol-util/zip/zip.ts +++ b/src/mol-util/zip/zip.ts @@ -105,8 +105,9 @@ async function _readLocal(runtime: RuntimeContext, data: Uint8Array, o: number, const buf = new Uint8Array(usize); await inflateRaw(runtime, file, buf); out[name] = buf; + } else { + throw `unknown compression method: ${cmpr}`; } - else throw `unknown compression method: ${cmpr}`; } export async function inflateRaw(runtime: RuntimeContext, file: Uint8Array, buf?: Uint8Array) { diff --git a/src/servers/volume/common/file.ts b/src/servers/volume/common/file.ts index 753512601af44476e82b6833e19752416dd97aac..d522224b160e3394acf7f312dd9a0239002089c9 100644 --- a/src/servers/volume/common/file.ts +++ b/src/servers/volume/common/file.ts @@ -34,8 +34,9 @@ function makeDir(path: string, root?: string): boolean { root = (root || '') + dir + '/'; - try { fs.mkdirSync(root); } - catch (e) { + try { + fs.mkdirSync(root); + } catch (e) { if (!fs.statSync(root).isDirectory()) throw new Error(e); } diff --git a/src/servers/volume/server/local-api.ts b/src/servers/volume/server/local-api.ts index de87df318ec3e7cf457a92aa932598e2bd7b666b..930aa0c33a3d2b532d770267fe7770aa0151a215 100644 --- a/src/servers/volume/server/local-api.ts +++ b/src/servers/volume/server/local-api.ts @@ -104,8 +104,9 @@ function makeDir(path: string, root?: string): boolean { root = (root || '') + dir + '/'; - try { fs.mkdirSync(root); } - catch (e) { + try { + fs.mkdirSync(root); + } catch (e) { if (!fs.statSync(root).isDirectory()) throw new Error(e); }